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

article/2025/10/12 17:39:57

一、什么是Adapter模式

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

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

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

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

Adapter模式有以下两种:

  • 类适配器模式(使用继承的适配器)

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

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

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

2.1 各个类之间的关系

先看一下类图:

这里的示例程序是一段会将输入的字符串显示为括号包围或者星号包围的简单程序。例如,输入字符串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);}@Overridepublic void printWeak() {showWithParen();}@Overridepublic 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 运行结果

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

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

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

3.1 各个类之间的关系

先看一下类图:

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);}@Overridepublic void printWeak() {banner.showWithParen();}@Overridepublic 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 运行结果

四、拓展思路的要点

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接口中的方法”。


http://chatgpt.dhexx.cn/article/TmbQMTgk.shtml

相关文章

3.设计模式--适配器模式(adapter模式)

1.场景 适配器模式可能是开发人员用的最多的一种设计模式,做后台开发你可能每天都在使用。只是不知道他的名字;现实中的适配器你应该不会陌生,新款的IQOO 8 pro手机充电器已经达到了120w,实际上充电器就是一个适配器,他…

设计模式【7】——适配器模式(Adapter 模式)

文章目录 前言一、适配器模式(Adapter 模式)二、具体源码1.Adapter.h2.Adapter.cpp3.main.cpp 三、运行结果总结 前言 实际上在软件系统设计和开发中,经常会遇到:我们为了完成某项工作购买了一个第三方的库来加快开发。这就带来了…

【Adapter模式】C++设计模式——适配器

适配器 一、设计流程探讨二、模式介绍三、代码实现 C设计模式大全,23种设计模式合集详解—👉(点我跳转) 一、设计流程探讨 首先放一张图,让大家大致了解什么叫适配器。适配器属于接口隔离的一种,它能使接口…

设计模式-Adapter模式(适配器模式)

适配器模式是什么?为什么要有适配器模式?用一个例子来看看代码的实现 适配器模式是什么? 我的理解: 比如现实世界,电脑充电需要的电压是12V,而家用电压是220V,肯定不能直接用呀,那不…

adapter 模式

一、adapter是什么 属于结构模式(持有或继承被适配的类)。 对功能类进行包装,转换成客户端希望的样子,所以也叫包装模式。 实现比较直观,比较简单,就是加了一层封装。 二、adapter的使用场景 系统改造&a…

Adapter 模式(适配器模式)

适配器 将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 动机 由于应用环境的变化,常常需要将「一些现存的对象」放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足…

Adapter(适配器)模式

Adapter 模式是一个常用的模式,它可以与其他很多模式共同使用 Adapter模式的意图是 将一个类的接口转换成希望的另外一种接口,Adapter模式使原本由接口不兼容而不能一起工作的那一些类可以起工作,就是说 需要一种方法,为一个内容合…

oracle decode嵌套,Oracle 中 Decode函数用法 | YuXi

含义解释: decode(条件,值1,返回值1,值2,返回值2,...值n,返回值n,缺省值) 该函数的含义如下: IF 条件值1 THEN RETURN(翻译值1) ELSIF 条件值2 THEN RETURN(翻译值2) ...... ELSIF 条件值n THEN RETURN(翻译值n) ELSE RETURN(缺省值) END IF decode(字段…

DECODE函数和SIGN函数详解

SIGN函数 一、基本语法 sign是符号函数&#xff0c;基本语法如下&#xff1a; sign(n)如果n>0&#xff0c;则返回1&#xff1b;如果n0&#xff0c;则返回0&#xff1b;如果n<0&#xff0c;则返回-1。 二、案例演示 【案例1】 select sign( 100 ),sign(- 100 ),sign…

mysql中中decode用法_MySQL 中的 DECODE 函数的实现

在 Oracle 中的 decode() 函数 语法如下: DECODE (expr, search1, result1[, search2, result2…][, default]) 它用于比较参数 expr 的值,如果匹配到哪一个 search 条件,就返回对应的 result 结果,可以有多组 search 和 result 的对应关系,如果任何一个 search 条件都没有…

Oracle中decode函数详解

【函数格式】&#xff1a; decode ( expression, condition_01, result_01, condition_02, result_02, ......, condition_n, result_n, result_default) 【函数说明】&#xff1a; 若表达式expression值与condition_01值匹配&#xff0c;则返回result_01&#xff0c;若不…

oracle中decode函数的使用

一、DECODE函数相当于if条件语句&#xff0c;它将输入的值与函数中的参数列比较&#xff0c;根据输入值返回一个对应值 1、语法&#xff1a;decode(条件&#xff0c;值1&#xff0c;返回值1&#xff0c;值2&#xff0c;返回值2&#xff0c;...值n,返回值n&#xff0c;缺省值) …

Oracle函数之DECODE函数

1.语法 2.用途 DECODE 函数将 expr 与 search 的值逐个比较。如果 expr 与 search 值相等&#xff0c;Oracle 返回 search 相应的 result。如果 expr 与 search 值都不匹配&#xff0c;Oracle 返回 default&#xff0c;如果没有函数中没有赋值 default&#xff0c;Oracle 返回…

java decode函数用法_decode函数的几种用法

1:使用decode判断字符串是否一样 DECODE(value,if1,then1,if2,then2,if3,then3,...,else) 含义为 IF 条件=值1 THEN RETURN(value 1) ELSIF 条件=值2 THEN RETURN(value 2) ...... ELSIF 条件=值n THEN RETURN(value 3) ELSE RETURN(default) END IF select empno, decode(empn…

Oracle decode函数

一 两种语法格式 1 decode(expression,value,result1,result2) 如果expressionvalue&#xff0c;则输出result1&#xff0c;否则输出result2 例子&#xff1a; &#xff08;123&#xff0c;输出a&#xff09; &#xff08;12≠4&#xff0c;输出b&#xff09; 2 decode(expre…

decode()函数简介

2019独角兽企业重金招聘Python工程师标准>>> decode()函数简介&#xff1a;主要作用&#xff1a;将查询结果翻译成其他值&#xff08;即以其他形式表现出来&#xff0c;以下举例说明&#xff09;&#xff1b;使用方法&#xff1a;Select decode&#xff08;columnna…

冯诺依曼元胞计算机,冯诺依曼元胞自动机

冯诺依曼元胞自动机(John V on Neumann’s Cellular Automaton) 冯诺依曼元胞自动机是由计算机科学家约翰冯诺依曼发明的一种图灵完备的元胞自动机。目前它还有三种不同的规则&#xff0c;分别名叫&#xff1a;JvN29&#xff0c;Nobili32,Hutton32.可以模拟许多“机器”&#x…

元胞自动机学习笔记

元胞自动机学习笔记 2021.7.19 一、简介&#xff1a; 元胞自动机&#xff08;cellular automata,CA):是一种时间&#xff0c;空间&#xff0c;状态都离散&#xff0c;空间相互作用和时间因果关系为局部的网格动力学模型&#xff0c;具有模拟复杂系统时空演化过程的能力。 二…

python实现元胞自动机

这是刚学习写代码时完成的&#xff0c;因此很多地方编写的可能不太美观&#xff0c;但运行起来没有问题&#xff0c;先发出来&#xff0c;之后有精力了在修改美化。 该元胞自动机的功能&#xff1a; 1.初始化按一定概率在各个位置生成元胞。 2.迭代&#xff0c;每次迭代元胞…

元胞自动机基本概念

目录 前言 一、元胞自动机的概述 二、元胞自动机的构成 1.元胞 2.元胞空间 3.元胞邻居 4.元胞规则 总结 前言 元胞自动机的寒假学习笔记&#xff0c;这里只是一些基本的概念&#xff0c;后面一篇文章会详细、具体地去介绍基于MATLAB元胞自动机的代码实现。&#xff08…