admin管理员组

文章数量:1530334

设计模式概述及分类:https://blog.csdn/qq_34896730/article/details/105324092

面向对象设计原则:https://editor.csdn/md/?articleId=105352240

适配器模式目录

    • 1 结构型模式
    • 2 适配器模式概述
    • 3 适配器模式结构与实现
      • 3.1 适配器模式结构
      • 3.2 适配器模式实现
    • 4 适配器模式应用实例
    • 5 缺省适配器模式
    • 7 适配器模式优/缺点与适用环境
      • 7.1适配器模式优点
      • 7.2 适配器模式缺点
      • 7.3 适配者模式适用环境

1 结构型模式

      在面向对象软件系统中,每个类/对象都承担了一定的职责,它们可以相互协作,实现一些复杂的功能。结构型模式(Structural Pattern)关注如何将现有类或对象组织在一起形成更强大的结构。不同的结构型模式从不同的角度来组合类或对象,它们在尽可能满足各种面向对象设计原则的同时为类或对象的组合提供一系列巧妙的解决方案。
      结构型模式可以描述两种不同的东西——类与类的实例(即对象)。根据这一点,结构型模式可以分为类结构型模式和对象结构型模式。类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系;而对象结构型模式挂念类与对象的组合,通过关联关系在一个类中定义另一个类的实例对象,然后通过该对象调用相应的方法。根据合成复用原则,在系统中尽量使用关联关系来提来继承关系,因此大部分结构型模式都是对象结构型模式。
      在GoF设计模式中包含7种结构型模式,它们的名称、定义、学习难度和使用频度如1-1所示。

表1-1 结构型模式一览表
模式名称定 义学习难度使用频率
适配器模式
(Adapter Pattern)
将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以在一起工作★★☆☆☆★★★★☆
桥接模式
(Bridge Pattern)
将抽象部分与它的实现部分解耦,使得两者都能够独立变化★★★☆☆★★★☆☆
组合模式
(Composite Pattern)
组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象★★★☆☆★★★★☆
装饰模式
(Decorator Pattern)
动态地给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案★★★☆☆★★★☆☆
外观模式
(Facade Pattern)
为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一系统更加容易使用★☆☆☆☆★★★★★
享元模式
(Flyweight Pattern)
运用共享技术有效地支持大量细粒度对象的复用★★★★☆★☆☆☆☆
代理模式
(Proxy Pattern)
给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问★★★☆☆★★★★☆

2 适配器模式概述

      众所周知,我们国家的生活用电的电压是220V,而笔记本电脑、手机等电子设备的工作电压没有那么高,为了使笔记本、手机等设备可以使用220V的生活用电,就需要使用电源适配器,也就是充电器或变压器,有了这个电源适配器,原本不能直接工作的生活用电和笔记本电脑可以兼容了。在这里电源适配器充当了一个适配器的角色,如图2-1所示。

图2-1 电源适配器示意图
      在软件开发中有时也存在类似这种不兼容的情况,也可以像引入一个电源适配器那样引入一个称为适配器的角色来协调这些存在不兼容的结构,这种设计方案即为适配器模式。       与电源适配器相似,在适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装的对象称为适配者(Adapter),即被适配的类。适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说,客户类并不直接访问适配者类。因此,适配器让那些由于接口不兼容而不能交互的类可以在一起工作。       适配器模式可以将一个类的接口和另一个类的接口匹配起来,而无须修改原来的适配者接口和抽象目标类接口。

      适配器模式: 将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。
      适配器模式的别名为包装类(Wrapper)模式,它既可以作为类结构型模式,也可以作为对象结构型模式。在适配器模式的定义中所提及的接口是指广义的接口,它可以表示一个方法或者方法的集合。

3 适配器模式结构与实现

      适配器模式包括类适配器和对象适配器。在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承关系(或实现)关系。下面分别分析这两种适配器的结构。

3.1 适配器模式结构

      类适配器模式的结构图如图3-1所示。
      对象适配器模式的结构图如图3-2所示。

图3-1 类适配器模式结构图
图3-2 对象适配器模式结构图
      由图3-1和图3-2可知,适配器模式包含以下3个角色。

      (1) Target(目标抽象类): 目标抽象类定义客户所需的接口,可以是一个抽象类或接口,也可以是具体类。在类适配器中,由于Java语言不支持多重继承,它只能是接口
      (2) Adapter(适配器类): 它可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配。适配器Adapter是适配器模式的核心,在类适配器中,它通过实现Target接口并继承Adaptee类来使二者产生联系,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
      (3) Adapter(适配器类): 适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下甚至没有适配者类的源代码。

3.2 适配器模式实现

      由于适配器模式包括类适配器模式和对象适配器模式两种像是,下面分别介绍这两种适配器模式的实现机制。
      1. 类适配器
      根据如图3-1所示的类适配器模式结构图,在类适配器中适配者类Adaptee没有request()方法,而客户端期待这个方法,但在适配者类中实现了specificRequest()方法,该方法提供的实现是客户端所需要的。为了使客户端能够使用适配者类,提供了一个中间类,即适配器类Adapter,适配器类实现了抽象目标类接口Target,并继承了适配者类,在适配器类的request()方法中调用所继承的适配者类的specificRequest()方法,达到了适配的目的。因为适配器类与适配者类是继承关系,所以这种适配器称为类适配器模式。典型的类适配器模式代码如下:

public class Adapter extends Adaptee implements Target{
   public void request(){
      super.specificRequest(); 
   }
}

      2. 对象适配器
      根据如图3-2所示的对象适配器模式结构图,在对象适配器中客户端需要调用request()方法,而适配者类Adaptee没有该方法,但是它提供的specificRequest()方法却是客户端所需要的。为了使客户端能够使用适配者类,需要提供一个包装类Adapter,即适配器类。这个包装类包装了一个适配者的实例。从而将客户端与适配者衔接起来,在适配器的request()方法中调用适配者的sepcificRequest()方法,因为适配器类与适配者类是关联关系(也可称为委派关系),所以这种适配器模式称为对象适配器模式。典型的对象适配器代码如下:

public class Adapter extends Target{
  private Adaptee adaptee;//维持一个对适配器对象的引用
  public Adapter(Adaptee adaptee){
       this.adaptee = adaptee;
  }
  public void request(){
      adaptee.specificRequest();//转发调用
  }
}

      适配器模式可以将一个类的接口和另一个类的接口匹配起来,使用的前提是不能或不想修改原来的适配者接口和抽象目标接口。例如购买了一些第三方类库或控件,但是没有源代码,此时使用适配器模式可以统一对象访问接口。
      适配器模式更多的是强调对代码的组织,而不是功能的实现。在实际开发中,对象适配器的使用频率更高。

4 适配器模式应用实例

      下面通过一个应用来进一步学习和理解适配器模式。
      1. 实例说明
      某公司要开发一款儿童玩具汽车,为了更好地吸引小朋友的注意力,该玩具汽车在移动过程中伴随着灯光闪烁和声音提示。在该公司以往的产品中已经实现了控制灯光闪烁(例如警灯闪烁)和声音提示(例如警笛音效)的程序,为了重用先前的代码并且使汽车控制软件具有更好的灵活性和扩展性,现使用适配器模式设计该玩具汽车控制软件。
      2. 实例类图
      通过分析,本实例可采用对象适配器模式来实现,其结构图如图4-1所示。

图4-1 汽车控件软件结构图
      在图4-1中,CarController类充当抽象目标,PoliceSound和PoliceLamp类充当适配者,PoliceCarAdapter充当适配器。

      3. 实例代码
      (1) CarController: 汽车控制类,充当目标抽象类。

public abstract class CarController{
   public void move(){
      System.out.println("玩具汽车移动!");
   }
   public abstract void phonate();//发出声音
   public abstract void twinkle();//灯光闪烁
}

      (2) PoliceSound: 警笛类,充当适配者。

public class PoliceSound{
    public void alarmSound{
       System.out.println("发出警笛声音!");
    }
}

      (3) PoliceLamp: 警灯类,充当适配者。

public class PoliceLamp{
   public void alarmLamp(){
      System.out.println("呈现警灯闪烁!"); 
   }
}

      (4) PoliceCarAdapter:警车适配器,充当适配器。

public class PoliceCarAdapter extends CarController{
   private PoliceSound sound;//定义适配者PoliceSound对象
   private PoliceLamp lamp;//定义适配者PoliceLamp对象
   public PoliceCarAdapter(){
      sound = new PoliceSound();
      lamp = new PoliceLamp();
   }
   //发出警笛声音
   public void phonate(){
       sound.alarmSound();//调用适配者类PoliceSound的方法
   }
   //呈现警灯闪烁
   public void twinkle(){
      lamp.alarmLamp();//调用适配者类PoliceLamp的方法
   }
}

      (5) 配置文件config.xml,在配置文件中存储了适配器类的类名。

<?xml version="1.0"?>
<config>
   <className>designpatterns.adapter.PoliceCarAdapter</className>
</config>

      (6) XMLUtil: 工具类

public class XMLUtil{
   //该方法用于从XML配置文件中提取具体类的类名,并返回一个实例对象
 public static Object getBean(){
	 try{
	 //创建DOM文档对象
	   DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
	   DocumentBuilder builder = dFactory.newDocumentBuilder();
	   Document doc;
	   doc = builder.parse(new File("src://designpatterns//adapter//config.xml"));
	   //获取包含类名的文本节点
	   NodeList nl = doc.getElementsByTagName("className");
	   Node classNode = nl.item(0).getFirstChild();
	   //通过类名生成实例对象并将其返回
	   Class c = Class.forName(cName);
	   Object obj = c.newInstance();
	   return object;
	   }catch(Exception e){
	     e.printStackTrace();
	     return null;
	   }
   }
}

      (7) Client: 客户端测试类。

public class Client{
  public static void main(String args[]){
     CarController car;
     car = (CarController)XMLUtil.getBean();
     car.remove();
     car.phonate();
     car.twinkle();
  }
}

      4. 结果及分析
      编译并运行程序,输出结果如下:

玩具汽车移动!
发出警笛声音!
呈现警灯闪烁!

      在本实例中使用了对象适配器模式,同时引入了配置文件,将适配器类的类名存储在配置文件config.xml中。如果需要使用其他声音类或者灯光类,可以增加一个新的适配器类,使用新的适配器来适配新的声音类和灯光类,原有代码无须修改。通过引入配置文件和反射机制可以在不修改客户端代码的情况下使用新的适配器,无须修改源代码,符合开闭原则。
      在本实例中目标抽象类是一个抽象类,而不是接口,并且实例中的适配器类PoliceCarAdapter同时适配了两个适配者,由于Java语言不支持多重继承,因此本实例只能通过对象适配器来实现,而不能使用类适配器。在实际开发中对象适配器比类适配器更加灵活,其使用频率更高。

5 缺省适配器模式

      缺省适配器模式是适配器模式的一种变体,其应用也较为广泛。
      缺省适配器模式的定义:当不需要实现一个接口提供的所有方法时,可设计一个抽象类实现接口,并为接口中的每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。
      缺省适配器模式的结构图如图5-1所示。

图5-1 缺省适配器模式结构图
      由图5-1可知,在缺省适配器模式中包含以下3个角色。

      (1) ServiceInterface(适配者接口): 它是一个接口,通常在该接口中声明了大量的方法。
      (2) AbstractServiceClass(缺省适配器类): 它是缺省适配器模式的核心类,使用空方法的形式实现了在ServiceInterface接口中声明的方法。通常将它定义为抽象类,因为对它进行实例化没有任何意义。
      (3) ConcreteServiceClass(具体业务类): 它是缺省适配器类的子类,在没有引入适配器之前它需要实现适配者接口,因此需要实现在适配者接口中定义的所有方法,而对于一些无须使用的方法不得不提供空实现。在有了缺省适配器之后可以直接继承该适配器类,根据需要由选择地覆盖在适配器类中定义的方法。
      其中,缺省适配器类的典型代码片段如下:

public abstract class AbstractServiceClass implements ServiceInterface{
   public void serviceMethod1(){}//空方法
   public void serviceMethod2(){}//空方法
   public void serviceMethod3(){}//空方法
}

      在JDK类库的事件处理包java.awt.event中广泛使用了缺省适配器模式,例如WindowAdapter、KeyAdapter、MouseAdapter等。 下面以处理窗口事件为例进行说明:在Java语言中一般可以使用两种方式来实现窗口事件处理类,一种是通过实现WindowListener接口,另一种是通过继承WindowAdapter适配器类。如果是使用第一种方式直接实现WindowListener接口,事件处理需要实现该接口中定义的7个方法,而对于大部分方法需求可能值需要实现一两个方法,其他方法都无须实现,但由于语言特性不得不为其他方法也提供一个简单的实现(通常是空实现),这给使用带来了麻烦,但由于缺省适配器可以很好地解决这一问题,在JDK中提供了一个适配器类WindowAdapter来实现WindowListener接口,该适配器类为接口中的每一个方法都提供了一个空实现,此时时间处理类可以继承WindowAdapter类,而无须再为接口中的每个方法都提供实现,如图5-2所示。

图5-2 WindowListener和WindowAdapter结构图
## 6 双向适配器       在对象适配器的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么该适配器就是一个双向适配器,其结构示意图如图6-1所示。
图6-1 双向适配器结构图
      双向适配器的实现较为复杂,其典型代码如下:
public class Adapter implements Target,Adaptee{
   //同时维持抽象目标类和适配者的引用
   private Target target;
   private Adaptee adaptee;
   public Adapter(Target target){
       this.target = target;
   }
   public Adapter(Adaptee adaptee){
       this.adaptee = adaptee;
   }
   public void request(){
       adaptee.specificRequest();
   }
   public void specificRequest(){
       target.request();
   }
}

7 适配器模式优/缺点与适用环境

      适配器模式将现有接口转化为客户类所期望的接口,实现了对现有类的复用。

7.1适配器模式优点

      无论是对象适配器模式还是类适配器模式都具有以下优点:
      (1) 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
      (2) 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
      (3) 灵活性和扩展性都非常好,通过使用配置文件可以具体很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合开闭原则。
      具体来说,类适配器模式还有以下优点:
      由于适配器类是适配器者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
      对象适配器模式还有以下优点:
      (1) 一个对象适配器可以把多个不同的适配者适配到同一个目标。
      (2) 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据里氏代换原则,适配者的子类也可以通过该适配器进行适配。

7.2 适配器模式缺点

      类适配器模式的缺点主要如下:
      (1) 对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者。
      (2) 适配者类不能为最终类,例如在Java中不能为final类。
      (3) 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
      对象适配器模式的缺点如下:
      与类适配器模式相比,在该模式下要在适配器中置换支配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当成真正的适配者进行适配,实现过程较为复杂。

7.3 适配者模式适用环境

      (1) 系统需要使用一些现有的类,而这些类的接口(例如方法名)不符合系统的需要,甚至没有这些类的源代码。
      (2) 想创建一个可以重复使用的类,用于和一些彼此之间没有太大关联的类(包括可能在将来引进的类)一起工作。

本文标签: 模式适配器结构Java