程序员潇然 发表于 2022-8-2 11:17:24

适配器模式 adapter 结构型 设计模式(九)

### 现实世界中的适配器模型

先来看下来几个图片,截图自淘宝

!(data/attachment/forum/202208/02/111112h01d0vb6bzx03xll.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

上图为港版的插头与港版的插座

!(data/attachment/forum/202208/02/111142fykvvlp8lp5ft8zy.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

上图为插座适配器卖家的描述图

!(data/attachment/forum/202208/02/111159ob8788zojddt1y8d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

上图为适配后的结果

#### 现实世界中适配器模式 角色分类

这就是适配器模式在电源插座上的应用

我们看下在插座适配器中的几个重要角色

!(data/attachment/forum/202208/02/111216xn5f8q9pte9qfff2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

可以看得出来,大陆和港版插座面板,都是作为电源的角色,他们的功能是相似的或者说相近的

插头要使用插座,进而接通电流

### 现实世界到代码的转换 电源插座代码示例

#### 港版插座面板

```java
package adapter;
/**目标角色 Target 接口
* 香港地区使用的插座面板,提供输出电流的功能
* @author crazybytex
*
*/
public interface TargetHongkongPanelInterface {
public void offerHongKongElectricity();
}

package adapter;
/**目标角色 Target 某个具体的港版插座面板 实现类
* 香港地区使用的插座面板,提供输出电流的功能
* @author crazybytex
*
*/
public class TargetHongkongPanel implements TargetHongkongPanelInterface{
@Override
public void offerHongKongElectricity() {
System.out.println("港版面板 提供电流");
}
}
```

#### 大陆地区插座面板

```java
package adapter;
/**被适配角色 Adaptee 接口
* 大陆地区使用的插座面板,提供输出电流的功能
* @author crazybytex
*
*/
public interface AdapteeChinaMainlandPanelInterface {
public void offerChinaMainlandElectricity();
}

package adapter;
/**被适配角色 Adaptee 某种具体类型的插座面板 实现类
* 大陆地区使用的插座面板,提供输出电流的功能
* @author crazybytex
*/

public class AdapteeChinaMainlandPanel implements AdapteeChinaMainlandPanelInterface{
@Override
public void offerChinaMainlandElectricity() {
   System.out.println("国标面板 提供电流");
}
}
```

#### 港版插头

```java
package adapter;
/**客户角色 Client 港版插头
* @author crazybytex
*
*/
public class ClientHongKongSocket {
/**接受港版插座面板作为参数
* 港版插头,插入到港版插座面板
* @param targetHongkongPanel
*/
public void plugIn(TargetHongkongPanelInterface targetHongkongPanel) {
targetHongkongPanel.offerHongKongElectricity();
}
/*
* 测试主程序,港版插头 插入到适配器上
* 适配器插入到大陆面板上
*/
public static void main(String ...args) {
    //港版插头
    ClientHongKongSocket socket = new ClientHongKongSocket();
    //大陆面板
    AdapteeChinaMainlandPanel adapteeChinaMainlandPanel = new AdapteeChinaMainlandPanel();
    //适配器
    Adapter adapter = new Adapter(adapteeChinaMainlandPanel);
    //港版插头 插到 适配器上
    socket.plugIn(adapter);
}
}
```

#### 插头适配器

```java
package adapter;
/**适配器角色 Adapter
* 实现目标角色 TargetHongkongPanelInterface
* 组合使用被适配角色 AdapteeChinaMainlandPanelInterface
* 将对目标角色的方法调用转换为被适配角色的方法调用
* @author crazybytex 程序员潇然
*
*/
public class Adapter implements TargetHongkongPanelInterface{
private AdapteeChinaMainlandPanelInterface adapteeChinaMainlandPanel;

Adapter(AdapteeChinaMainlandPanel adapteeChinaMainlandPanel){
this.adapteeChinaMainlandPanel = adapteeChinaMainlandPanel;
}

@Override
public void offerHongKongElectricity() {
adapteeChinaMainlandPanel.offerChinaMainlandElectricity();
}
}
```

#### 执行港版插头的测试main方法

!(data/attachment/forum/202208/02/111539mzqfbffjx6lda8pd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

#### **UML图**

港版插头ClientHongKongSocket与港版插座面板 TargetHongKongPanelInterface接口关联

Adapter 实现了港版插座面板 TargetHongKongPanelInterface接口

并且包含一个      大陆插座面板AdapteeChinaMainlandPanelInterface 接口

适配器将对 港版插座面板的方法调用转换为大陆插座面板的方法调用

这就是整个适配器结构( 可以不关注实现类)

!(data/attachment/forum/202208/02/111555kznrqs3xwhy9iy2h.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

!(data/attachment/forum/202208/02/111603igdz88d3n1d8scff.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

客户角色Client 要使用 目标角色Target

适配器模式就是要**冒充**目标角色Target,看起来有目标角色的行为

在OOP中,想要做到 就是实现或者继承或者拥有一个成员

总之:

适配器就是把被适配者转换为为目标

### OOP中的适配器模式详解

意图:

将一个类的接口转换成客户希望的另外一个接口。<br />

适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

<br />注意:

此处说的接口,并不是单纯的指Interface,而是指一切可以提供方法调用的类型,可能是接口也可能是类

客户使用适配器的过程:

客户通过目标接口调用适配器的方法,对适配器发出请求

适配器使用被适配者接口把请求进行处理

客户接收到调用的结果,但是并未察觉这一切是适配器在起转换作用。

#### 适配器分类

* 类适配器
* 对象适配器
* 接口适配器

想要把一个类的接口转换为客户希望的另外一个接口
必须要有输入输出,有目标有源
所以作为一个适配器,必须要 一手拿着被适配者也就是源,另一手拿着的是目标
想要转变为目标,那么必须得同目标时一样的类型,在oop中想要成为目标类型 要么继承 要么实现
想要拥有被适配者,要么继承,要么实现,要么就是关联(拥有一个对象)
三种方式可以理解为按照拥有被适配者 的方式进行划分的
如果继承Adaptee,那么就是类 适配器
如果拥有一个Adaptee,也就是拥有一个Adaptee对象,那么就是对象 适配器
如果实现Adaptee,那么就是 接口适配器

现在回想下,我们上面的例子
适配器 实现了目标接口,并且拥有一个Adaptee对象 作为属性,很显然就是对象适配器

#### 类适配器

根据上面的描述,如果继承Adaptee,那么就是类 适配器
在Java中不允许多继承,既然已经继承了Adaptee,那么就必须要求目标是一个接口(此处接口就是Interface)
这就有一定的局限性
而且
既然是继承被适配者类,那么被适配者的子类拥有的方法和行为,他并不能拥有,也就是说不能适配被适配者的子类

优点
那就是适配器作为被适配者的子类,自然拥有更多的操作空间,比如重写方法

#### 对象适配器

如同我们上面的例子一样,如果把被适配者当做一个属性对象放到适配器中,这就是对象适配器
显然,他不要求目标一定是接口,继承还是实现都可以
同类适配器比较的话,他不能对被适配者 原来的一些方法进行操作,只能进行使用,不过也无伤大雅,不算缺点
因为他是拥有一个被适配者类型的对象
被适配者和他的子类显然都可以作为具体的对象传入

#### 接口适配器

按照我们的描述,如果实现了被适配者 Adaptee那么就是接口适配器
具体说来:
当不需要全部实现接口提供的方法时
可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法)
那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
它适用于一个接口不想使用其所有的方法的情况

### 接口适配器示例

#### 接口

```java
package interfaceadapter;
public interface Interfaces {
public void method1();
public void method2();
public void method3();
public void method4();
public void method5();
}
```

#### 抽象类

```java
package interfaceadapter;
/**
* @author 程序员潇然 crazybytex
*
*/
public abstract class AbstractClass implements Interfaces {
@Override
public void method1() { }
@Override
public void method2() { }
@Override
public void method3() { }
@Override
public void method4() { }
@Override
public void method5() { }
}
```

#### 两个实现类

```java
package interfaceadapter;
public class ImplementClass1 extends AbstractClass {
@Override
public void method1() {
    System.out.println("method1 called ");
}
@Override
public void method3() {
    System.out.println("method3 called ");
}
@Override
    public void method5() {
System.out.println("method5 called ");
}
}

package interfaceadapter;
public class ImplementClass2 extends AbstractClass {
@Override
public void method2() {
    System.out.println("method2 called");
}

@Override
public void method4() {
    System.out.println("method4 called");
}
}
```

#### 测试类-客户角色

```java
package interfaceadapter;
public class Test {
    public static void main(String[] args) {
    Interfaces class1 = new ImplementClass1();
    Interfaces class2 = new ImplementClass2();
    class1.method1();
    class1.method2();
    class1.method3();
    class1.method4();
    class1.method5();
    System.out.println("------");
    class2.method1();
    class2.method2();
    class2.method3();
    class2.method4();
    class2.method5();
}
}
```

!(data/attachment/forum/202208/02/112533trztr8q6qtz8drq6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

接口适配器的行为相当于适配了自己

把原来的接口 当做被适配者

目标则是一个实现了接口部分功能的类

调用这个接口的部分方法场景下,上面的形式是非常方便的

从这个示例中或许应该更加能理解适配器的本意:

将一个类的接口转换成客户希望的另外一个接口。

适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作

所以说,但凡是涉及到转换这一概念,你都可以考虑这个思维模式

三种常用形式,只是概念的表现形式而已

而且,实际的问题场景将会有很多种,也不可能完全都按照某种格式

再比如双向适配器

即可以将被适配者转换为目标

也可以把目标转换为被适配者

### 双向适配器

#### 目标接口/目标实现类

```java
package doubleadapter;
public interface TargetInterface {
    void targetRequest();
}



package doubleadapter;
public class TargetImplClass implements TargetInterface{
    @Override
    public void targetRequest() {
      System.out.println("targetRequest ... ");
    }
}
```

#### 被适配者接口/被适配者实现类

```java
package doubleadapter;
public interface AdapteeInterface {
    void adapteeRequest();
}

package doubleadapter;
public class AdapteeImplClass implements AdapteeInterface{
    @Override
    public void adapteeRequest() {
      System.out.println("adapteeRequest ... ");
    }
}
```

#### 适配器

```java
package doubleadapter;
public class Adapter implements TargetInterface,AdapteeInterface {
    private TargetInterface target;
    private AdapteeInterface adaptee;
    Adapter(TargetInterface target){
      this.target = target;
    }

    Adapter(AdapteeInterface adaptee){
      this.adaptee = adaptee;
    }
    @Override
    public void adapteeRequest() {
      target.targetRequest();
    }
    @Override
    public void targetRequest() {
      adaptee.adapteeRequest();
    }
}
```

#### Client 客户端角色

Main方法就相当于Client 客户端角色
!(data/attachment/forum/202208/02/112827cz2tbwa2nrzteb2t.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

适配器Adapter模式的宗旨是:
保留现有类所提供的服务,向客户提供接口,以满足客户的期望,也就是将现有接口转换为客户希望的另外的一个接口
本质在于转换

### JDK中的小应用

!(data/attachment/forum/202208/02/112852miiqi2evaqvefvvv.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

```java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.commons.collections.iterators;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
public class EnumerationIterator implements Iterator {
    private Collection collection;
    private Enumeration enumeration;
    private Object last;

    public EnumerationIterator() {
      this((Enumeration)null, (Collection)null);
    }
    public EnumerationIterator(Enumeration enumeration) {
      this(enumeration, (Collection)null);
    }

    public EnumerationIterator(Enumeration enumeration, Collection collection) {
      this.enumeration = enumeration;
      this.collection = collection;
      this.last = null;
    }

    public boolean hasNext() {
      return this.enumeration.hasMoreElements();
    }

    public Object next() {
      this.last = this.enumeration.nextElement();
      return this.last;
    }

    public void remove() {
      if (this.collection != null) {
            if (this.last != null) {
                this.collection.remove(this.last);
            } else {
                throw new IllegalStateException("next() must have been called for remove() to function");
            }

      } else {
            throw new UnsupportedOperationException("No Collection associated with this Iterator");
      }
    }

    public Enumeration getEnumeration() {
      return this.enumeration;
    }

    public void setEnumeration(Enumeration enumeration) {
      this.enumeration = enumeration;
    }
}
```

Enumeration 和 Iterator 大家应该都听过,Enumeration算是遗留的老代码了
很显然,我们希望能够使用新世界的Iterator
怎么办呢?
答案就是适配器
目标是 Iterator   被适配者是 Enumeration
看代码可知:

```java
public class EnumerationIterator implements Iterator {

    private Enumeration enumeration;
```

他实现了Iterator并且有一个Enumeration 的成员,是对象适配器
nextElement()与next()
hasMoreElements与hasNext()
他们可以说是匹配的
但是Enumeration 是不能删除的,没办法搞remove方法
所以说源码中提供了可以传入集合的构造方法,把对应的集合也传入进去
并且设置last变量记住刚才的位置
如果传递了集合 并且last存在,那么可以执行remove
否则抛异常

> 设计模式是作为解决问题或者设计类层级结构时的一种思维的存在,而不是公式一样的存在!

!(data/attachment/forum/202206/16/141330jha7st9soow8772i.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "common_log.png")
`转载务必注明出处:程序员潇然,疯狂的字节X,https://crazybytex.com/thread-108-1-1.html `






页: [1]
查看完整版本: 适配器模式 adapter 结构型 设计模式(九)