0%

设计模式之Adapter模式

适配器模式(Adapter Pattern) 是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。适配器模式将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
如果学过Android开发的对适配器应该不陌生.

什么时候使用Adapter模式

那么如果某个方法就是我们所需要的方法,那么直接在程序中使用不就可以了吗?为什么还要考虑使用Adapter模式呢?那么,究竟应当在什么时候使用Adapter模式呢?

Adapter模式会对现有的类进行适配,生成新的类.通过该模式可以很方便地创建我们需要的方法群.当出现Bug时,由于我们很明确地知道Bug不在现有的类(Adaptee角色)中,所以只需调查扮演Adaptee角色的类即可.这样一来,代码问题的排查就会变得非常简单.

介绍

  • 意图: 将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

  • 何时使用:

    • 系统需要使用现有的类,而此类的接口不符合系统的需要。
    • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。
    • 通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
  • 应用实例:

    • 美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。 - JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。
    • 在 LINUX 上运行 WINDOWS 程序。
    • JAVA 中的 jdbc。
  • 优点:

    • 单一职责原则:你可以将接口或数据转换代码从程序主要业务逻辑中分离。
    • 开闭原则:只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。
  • 缺点:

    • 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。
    • 由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类

实现

适配器模式分为两类,所谓“适配”就是适当的配合或者恰当的配合,想一下电源的适配器,完成的作用是将交流电220V转化成不同的直流电压,来对手机、电脑、台灯等充电,如果没有这些适配器,我们的设备早就着火了或者报废了,是一件很可怕的事情,那么适配器就是起到一个转换的作用,将物质(数据)由某种形式转换成另一种形式,使得适配后的一方能够接收和使用,那么我们通过分析也知道,适配器中的角色大体上分为三种使用适配器的角色(手机等)、适配器本身(适配器)、被适配的角色(交流电)。可是作为高内聚低耦合的设计思想指导,我们在手机和适配器中间再加上一层接口,手机直接使用这个接口来使用适配器,而不是高耦合的直接使用,因此主要的角色就有四个了。再考虑到适配器和被适配的角色之间的适配方式,我们可以使用继承来适配也可以使用组合来适配,因此,适配器也就分为通过使用继承的适配器——类适配器和使用组合(委托)的适配器——对象适配器。

  • 示例 1 (对象适配器)

我们有一个 MediaPlayer 接口和一个实现了 MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。
我们还有另一个接口 AdvancedMediaPlayer 和实现了AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件。
我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter,并使用 AdvancedMediaPlayer 对象来播放所需的格式。
AudioPlayer 使用适配器类 MediaAdapter 传递所需的音频类型,不需要知道能播放所需格式音频的实际类。AdapterPatternDemo,我们的演示类使用 AudioPlayer 类来播放各种格式。

  • 为媒体播放器和更高级的媒体播放器创建接口
    MediaPlayer.java
    1
    2
    3
    4
    5
    package com.edu.tju.GOF.Adapter;

    public interface MediaPlayer {
    public void play(String audioType, String fileName);
    }
    AdvancedMediaPlayer.java
    1
    2
    3
    4
    5
    6
    7
    package com.edu.tju.GOF.Adapter;

    public interface AdvancedMediaPlayer {
    public void playVlc(String fileName);

    public void playMp4(String fileName);
    }
  • 创建实现了AdvancedMediaPlayer接口的实体类
    VlcPlayer.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package com.edu.tju.GOF.Adapter;

    public class VlcPlayer implements AdvancedMediaPlayer {
    @Override
    public void playVlc(String fileName) {
    System.out.println("Playing vlc file. Name: " + fileName);
    }

    @Override
    public void playMp4(String fileName) {
    // 什么也不做
    }
    }
    Mp4Player.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package com.edu.tju.GOF.Adapter;

    public class Mp4Player implements AdvancedMediaPlayer {

    @Override
    public void playVlc(String fileName) {
    // 什么也不做
    }

    @Override
    public void playMp4(String fileName) {
    System.out.println("Playing mp4 file. Name: " + fileName);
    }
    }
  • 创建实现了MediaPlayer接口的适配器类
    MediaAdapter.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.edu.tju.GOF.Adapter;

    public class MediaAdapter implements MediaPlayer {

    AdvancedMediaPlayer advancedMusicPlayer;

    public MediaAdapter(String audioType) {
    if (audioType.equalsIgnoreCase("vlc")) {
    advancedMusicPlayer = new VlcPlayer();
    } else if (audioType.equalsIgnoreCase("mp4")) {
    advancedMusicPlayer = new Mp4Player();
    }
    }

    @Override
    public void play(String audioType, String fileName) {
    if (audioType.equalsIgnoreCase("vlc")) {
    advancedMusicPlayer.playVlc(fileName);
    } else if (audioType.equalsIgnoreCase("mp4")) {
    advancedMusicPlayer.playMp4(fileName);
    }
    }
    }
  • 创建实现了 MediaPlayer 接口的实体类。

AudioPlayer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.edu.tju.GOF.Adapter;

public class AudioPlayer implements MediaPlayer {
MediaAdapter mediaAdapter;

@Override
public void play(String audioType, String fileName) {

// 播放 mp3 音乐文件的内置支持
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing mp3 file. Name: " + fileName);
}
// mediaAdapter 提供了播放其他文件格式的支持
else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Invalid media. " + audioType + " format not supported");
}
}
}
  • 使用 AudioPlayer 来播放不同类型的音频格式。

AdapterPatternDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.edu.tju.GOF.Adapter;

public class AdapterPatternDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();

audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}

输出结果

Playing mp3 file. Name: beyond the horizon.mp3
Playing mp4 file. Name: alone.mp4
Playing vlc file. Name: far far away.vlc
Invalid media. avi format not supported

  • 示例 2 (类适配器)


Banner 类代码:

1
2
3
4
5
6
7
8
9
10
11
12
public class Banner {
private String name;
public Banner(String name){
this.name=name;
}
public void showWithParen(){
System.out.println("("+name+")");
}
public void showWithAster(){
System.out.println("*"+name+"*");
}
}

Print接口

1
2
3
4
public interface Print {
public abstract void printWeak();
public abstract void printStrong();
}

PrintBanner类的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

public class PrintBanner extends Banner implements Print {

public PrintBanner(String name) {
super(name);
}

public void printWeak() {
System.out.println("...开始弱适配...");
showWithParen();
System.out.println("...弱适配成功...");
System.out.println();
}

public void printStrong() {
System.out.println("...开始强适配...");
showWithAster();
System.out.println("...强适配成功...");
System.out.println();
}
}

Main类代码

1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}
  • 示例3 (对象适配器)

  • Banner 类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Banner {
    private String name;
    public Banner(String name){
    this.name=name;
    }
    public void showWithParen(){
    System.out.println("("+name+")");
    }
    public void showWithAster(){
    System.out.println("*"+name+"*");
    }
    }
  • Print抽象类
    1
    2
    3
    4
    public abstract class Print {
    public abstract void printWeak();
    public abstract void printStrong();
    }
  • PrintBanner类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class PrintBanner extends Print {

    Banner banner;
    public PrintBanner(String name) {
    banner=new Banner(name);
    }

    public void printWeak() {
    System.out.println("...开始弱适配...");
    banner.showWithParen();
    System.out.println("...弱适配成功...");
    System.out.println();
    }

    public void printStrong() {
    System.out.println("...开始强适配...");
    banner.showWithAster();
    System.out.println("...强适配成功...");
    System.out.println();
    }
    }
  • Main函数
    1
    2
    3
    4
    5
    6
    7
    8
    public class Main {

    public static void main(String[] args) {
    Print p=new PrintBanner("Hello");
    p.printStrong();
    p.printWeak();
    }
    }
    示例3与示例2 的Main类和Banner类 都没有改动,只是将Print接口变成了抽象类,那么PrintBanner不能同时继承两个类,因此将Banner对象组合到适配器之中,因此叫做对象适配器.

适配器模式 实现方式

  1. 确保至少有两个类的接口不兼容:

    • 一个无法修改 (通常是第三方、 遗留系统或者存在众多已有依赖的类) 的功能性服务类。
    • 一个或多个将受益于使用服务类的客户端类。
  2. 声明客户端接口, 描述客户端如何与服务交互。

  3. 创建遵循客户端接口的适配器类。 所有方法暂时都为空。

  4. 在适配器类中添加一个成员变量用于保存对于服务对象的引用。 通常情况下会通过构造函数对该成员变量进行初始化, 但有时在调用其方法时将该变量传递给适配器会更方便。

  5. 依次实现适配器类客户端接口的所有方法。 适配器会将实际工作委派给服务对象, 自身只负责接口或数据格式的转换。

  6. 客户端必须通过客户端接口使用适配器。 这样一来, 你就可以在不影响客户端代码的情况下修改或扩展适配器。

Adapter模式中的登场角色

  • Target (对象)
    该角色负责定义所需的方法.
    由Print接口/类
  • Client(请求者)
    该角色负责使用Target角色定义的方法进行具体处理
    由Main类扮演
  • Adaptee(被适配)
    Adaptee是一个持有既定方法的角色
    Banner类扮演
  • Adapter(适配)
    使用Adaptee角色的方法来满足Target角色的需求. 这是Adapter模式的目的.也是Adapter角色的作用

功能完全不同的类

当然,当Adaptee角色和Target角色的功能完全不同时,Adapter模式是无法使用的.就如同我们无法用交流100伏特电压让自来水管出水一样.

关于委托

委托是一种重要的编程方式。与继承相对,是可复用编程的重要方法。

委托指的是在A类中以各种方式利用B类,完成类的功能。

  • 委托的类型:

    • A use B
      B类对象在A类中出现,但是是以局部变量或是方法参数的形式出现的。A类中并没有B类的对象作为域。

      一般称这种delegation为临时性的delegation。

    • A has B

      B类对象在A类中出现,B类的对象是A类的域之一。B类对象通过A类对象的constructor方法或其它方法从外部输入A类对象。

      A has B有两种情况。

      • Association。A类对象和B类对象之间并没有从属关系。
      • Aggregation。A类对象由B类聚合而成,但是B类可以脱离A类单独存在。

      一般称这种delegation为永久性的delegation。

    • A ispartof B
      B类对象在A类中出现,B类的对象是A类的域之一。B类对象在A类对象内创建。
      B类对象不能脱离A类对象独立存在。

      一般称这种delegation为永久性的delegation。

Thank you for your support