设计模式学习(三):Adapter适配器模式

简介: 我们先举个例子:如果想让额定工作电压是直流12V的笔记本电脑在交流220V的电源下工作,应该怎么做呢?通常,我们会使用适配器,将家庭用的交流220V电压转换成我们所需要的直流12V电压。这就是适配器的工作,它位于实际情况与需求之间,填补两者之间的差异。

一、什么是Adapter模式



我们先举个例子:如果想让额定工作电压是直流12V的笔记本电脑在交流220V的电源下工作,应该怎么做呢?通常,我们会使用适配器,将家庭用的交流220V电压转换成我们所需要的直流12V电压。这就是适配器的工作,它位于实际情况与需求之间,填补两者之间的差异。


在程序世界中,经常会存在现有的程序无法直接使用,需要做适当的变换之后才能使用的情况。这种用于填补“现有的程序”和“所需的程序”之间差异的设计模式就是Adapter模式。


Adapter模式也被称为Wrapper模式。Wrapper有“包装器”的意思,就像用精美的包装纸将普通商品包装成礼物那样,替我们把某样东西包起来,使其能够用于其他用途的东西就被称为“包装器”或是“适配器”。


用一句话来概括:Adapter模式就是为程序加一个“适配器”以便于复用。


b2dc6cb2699534a79283489c86dba7b5.png


Adapter模式有以下两种:

  • 适配器模式(使用继承的适配器)
  • 对象适配器模式(使用委托的适配器)

本文将依次介绍这两种Adapter模式。


二、使用继承的Adapter模式示例代码



2.1 各个类之间的关系


先看一下类图

fa580e8a4e1191dca8043b0e9c1154e1.png


这里的示例程序是一段会将输入的字符串显示为括号包围或者星号包围的简单程序。例如,输入字符串Hello,显示为(Hello)或是*Hello*。


目前在Banner类( Banner有广告横幅的意思)中,有将字符串用括号括起来的showWithParen方法,和将字符串用*号括起来的showWithAster方法。我们假设这个Banner类是类似前文中的“交流220伏特电压”的“实际情况”。


假设Print接口中声明了两种方法,即弱化字符串显示(加括号)的printweak方法,和强调字符串显示(加*号)的printstrong方法。我们假设这个接口是类似于前文中的“直流12伏特电压”的“需求”。


现在要做的事情是使用Banner类编写一个实现了Print接口的类,也就是说要做一个将“交流220伏特电压”转换成“直流12伏特电压”的适配器。


扮演适配器角色的是 PrintBanner类。该类继承了Banner类并实现了“需求”——Print接口。PrintBanner类使用showWithParen方法实现了printWeak,使用showwithAster方法实现了printstrong。这样,PrintBanner类就具有适配器的功能了。


2.2 Banner类


Banner类是我们现有的功能。

public class Banner {
    private String string;
    public Banner(String string) {
        this.string = string;
    }
    public void showWithParen() {
        System.out.println("(" + string + ")");
    }
    public void showWithAster() {
        System.out.println("*" + string + "*");
    }
}


2.3 Print接口


Print接口就是我们新的“需求”。

public interface Print {
    public abstract void printWeak();
    public abstract void printStrong();
}


2.4 PrintBanner类


PrintBanner类扮演适配器的角色。

public class PrintBanner extends Banner implements Print{
    public PrintBanner(String string) {
        super(string);
    }
    @Override
    public void printWeak() {
        showWithParen();
    }
    @Override
    public void printStrong() {
        showWithAster();
    }
}


2.5 用于测试的Main类

public class Main {
    public static void main(String[] args) {
        Print p = new PrintBanner("Hello");
        p.printStrong();
        p.printWeak();
    }
}


2.6 运行结果

fff27d55e0da67563d8d16f1315b774e.png

需要注意的是,我们是使用Print接口(即调用printweak方法和printstrong方法)来进行编程的。对Main类的代码而言,Banner类中的showWithParen'方法和showWithAster方法被完全隐藏起来了。这就好像需要12V的笔记本电脑插在220V的插座上能正常工作,但它并不知道这12伏特的电压是由适配器将220伏特交流电压转换而成的。


Main类并不知道PrintBanner类是如何实现的,这样就可以在不用对Main类进行修改的情况下改变 PrintBanner类的具体实现。


三、使用委托的Adapter模式示例代码



3.1 各个类之间的关系


先看一下类图:

8d3f141ade42a29de1db98e481f13cae.png

Main类和 Banner类与示例程序中的内容完全相同,不过这里我们假设Print不是接口而是类。


也就是说,我们打算利用Banner类实现一个类,该类的方法和Print类的方法相同。由于在Java中无法同时继承两个类(只能是单一继承),因此我们无法将PrintBanner类分别定义为Print类和 Banner类的子类。


3.2 Banner类


同上面的Banner

public class Banner {
    private String string;
    public Banner(String string) {
        this.string = string;
    }
    public void showWithParen() {
        System.out.println("(" + string + ")");
    }
    public void showWithAster() {
        System.out.println("*" + string + "*");
    }
}


3.3 Print类

public abstract class Print {
    public abstract void printWeak();
    public abstract void printStrong();
}


3.4 PrintBanner类


PrintBanner类的banner字段中保存了Banner类的实例。该实例是在PrintBanner类的构造函数中生成的。然后,printWeak方法和printStrong方法会通过banner字段调用Banner类的showWithParen和 showWithAster方法。


与之前的示例代码中调用了从父类中继承的showWwithParen方法和showwithAster方法不同,这次我们通过字段来调用这两个方法。


这样就形成了一种委托关系。当PrintBanner类的printWeak被调用的时候,并不是PrintBanner类自己进行处理,而是将处理交给了其他实例(Banner类的实例)的showWithParen方法。


public class PrintBanner extends Print{
    private Banner banner;
    public PrintBanner(String string) {
        this.banner = new Banner(string);
    }
    @Override
    public void printWeak() {
        banner.showWithParen();
    }
    @Override
    public void printStrong() {
        banner.showWithAster();
    }
}


3.5 用于测试的Main类


同上面的Main

public class Main {
    public static void main(String[] args) {
        Print p = new PrintBanner("Hello");
        p.printStrong();
        p.printWeak();
    }
}


3.6 运行结果

812a6794369f667c2fc009ea63708038.png


四、拓展思路的要点



4.1 什么时候使用Adapter模式


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


很多时候,我们并非从零开始编程,经常会用到现有的类。特别是当现有的类已经被充分测试过了,Bug很少,而且已经被用于其他软件之中时,我们更愿意将这些类作为组件重复利用。


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


4.2 如果没有现成的代码


让现有的类适配新的接口(API)时,使用Adapter模式似乎是理所当然的。不过实际上,我们在让现有的类适配新的接口时,常常会有“只要将这里稍微修改下就可以了”的想法,一不留神就会修改现有的代码。但是需要注意的是,如果要对已经测试完毕的现有代码进行修改,就必须在修改后重新进行测试。


使用Adapter模式可以在完全不改变现有代码的前提下使现有代码适配于新的接口(API)。此外,在Adapter模式中,并非一定需要现成的代码。只要知道现有类的功能,就可以编写出新的类。


4.3 版本升级与兼容性


软件的生命周期总是伴随着版本的升级,而在版本升级的时候经常会出现“与旧版本的兼容性”问题。如果能够完全抛弃旧版本,那么软件的维护工作将会轻松得多,但是现实中往往无法这样做。这时,可以使用Adapter模式使新旧版本兼容,帮助我们轻松地同时维护新版本和旧版本。


例如,假设我们今后只想维护新版本。这时可以让新版本扮演Adaptee角色,旧版本扮演Target角色。接着编写一个扮演Adapter角色的类,让它使用新版本的类来实现旧版本的类中的方法。


4.4 功能完全不同的类


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


五、相关的设计模式



5.1 Bridge桥接模式


Adapter模式用于连接接口(API )不同的类,而 Bridge模式则用于连接类的功能层次结构与实现层次结构。

设计模式学习(一):Bridge桥接模式


5.2 Decorator装饰器模式


Adapter模式用于填补不同接口(API)之间的缝隙,而Decorator模式则是在不改变接口(API)的前提下增加功能。


设计模式学习(十二):Decorator装饰器模式


六、思考题



6.1

题目:

在示例程序中生成PrintBanner类的实例时,我们采用了如下方法,即使用Print类型的变量来保存PrintBanner实例。


Print p = new PrintBanner ( "Hello");

请问我们为什么不像下面这样使用PrintBanner类型的变量来保存PrintBanner的实例呢?

PrintBanner p = new PrintBanner ( "Hello");


答案:

明确地表明程序的意图,即“并不是使用PrintBanner类中的方法,而是使用Print接口中的方法”。


相关文章
|
2月前
|
设计模式 Java 程序员
Java设计模式-适配器模式(8)
Java设计模式-适配器模式(8)
|
1月前
|
设计模式 Java
Java设计模式之适配器模式
这篇文章详细讲解了Java设计模式中的适配器模式,包括其应用场景、实现方式及代码示例。
41 0
|
2月前
|
设计模式 Java
设计模式--适配器模式 Adapter Pattern
这篇文章介绍了适配器模式,包括其基本介绍、工作原理以及类适配器模式、对象适配器模式和接口适配器模式三种实现方式。
|
3月前
|
设计模式 XML 存储
【六】设计模式~~~结构型模式~~~适配器模式(Java)
文章详细介绍了适配器模式(Adapter Pattern),这是一种结构型设计模式,用于将一个类的接口转换成客户期望的另一个接口,使原本不兼容的接口能够一起工作,提高了类的复用性和系统的灵活性。通过对象适配器和类适配器两种实现方式,展示了适配器模式的代码应用,并讨论了其优点、缺点以及适用场景。
|
4月前
|
设计模式 Go 数据处理
iLogtail设计模式问题之在iLogtail中,为何需要使用适配器模式
iLogtail设计模式问题之在iLogtail中,为何需要使用适配器模式
|
4月前
|
设计模式 JavaScript 前端开发
js设计模式【详解】—— 适配器模式
js设计模式【详解】—— 适配器模式
31 0
|
5月前
|
设计模式 Java Android开发
Java设计模式:适配器模式的三种形式(五)
Java设计模式:适配器模式的三种形式(五)
|
15天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
2月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
17天前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###