07.适配器模式设计思想
目录介绍
- 01.适配器模式基础介绍
- 1.1 适配器模式由来
- 1.2 适配器模式定义
- 1.3 适配器模式场景
- 1.4 适配器模式思考
- 02.适配器模式原理与实现
- 2.1 罗列一个场景
- 2.2 用例子理解适配器
- 2.3 适配器模式基本实现
- 2.4 如何选择适配器
- 03.适配器模式分析
- 3.1 类适配器案例
- 3.2 对象适配器案例
- 3.3 适配器模式结构图
- 3.4 适配器模式时序图
- 04.适配器应用解析
- 4.1 读卡器适配案例【类】
- 4.2 播放器适配器案例【对象】
- 05.实际场景演变和分析
- 5.1
01.适配器模式基础介绍
1.1 适配器模式由来
- 适配器模式的英文翻译是 Adapter Design Pattern。
- 顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。
- 对于这个模式,有一个经常被拿来解释它的例子,就是 USB 转接头充当适配器,把两种不兼容的接口,通过转接变得可以一起工作。
- 简单来说
- 项目代码后期,想让不想关的类,变成可以一起工作。在计算机编程中,当我们有两个已有的功能,但由于某种原因它们不能直接协同工作时,我们使用适配器模式。
- 举个例子1:类似USB转接头,既可以链接Android手机,也可以链接IOS手机。
- 举个例子2:欧洲制造的电器,但你住在中国。你不能直接将这部电器插入墙上,因为插头和插座不兼容。此时,你需要一个适配器,将欧洲插头转换为中国插座。
1.2 适配器模式定义
- 适配器模式(Adapter Pattern)的定义 :
- 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。
- 适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
1.3 适配器模式场景
1.4 适配器模式思考
- 适配器模式应该在真正需要时使用,而不是为了强行适配而使用。
- 在设计阶段,应该尽量考虑接口的一致性和兼容性,以减少对适配器模式的依赖。
02.适配器模式原理与实现
2.1 罗列一个场景
2.2 用例子理解适配器
- 两种方式
- 类的适配器。通过继承来实现
- 对象的适配器。通过组合来实现
- 具体的代码实现如下所示。
- 其中,ITarget 表示要转化成的接口定义。Adaptee 是一组不兼容 ITarget 接口定义的接口,Adaptor 将 Adaptee 转化成一组符合 ITarget 接口定义的接口。
2.3 适配器模式基本实现
- 类的适配器。通过继承来实现
- ●目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
- ●源(Adapee)角色:现在需要适配的接口。
- ●适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
// 类适配器: 基于继承 public interface ITarget { void f1(); void f2(); void fc(); } public class Adaptee { public void fa() { //... } public void fb() { //... } public void fc() { //... } } public class Adaptor extends Adaptee implements ITarget { public void f1() { super.fa(); } public void f2() { //...重新实现f2()... } // 这里fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点 }
- 对象的适配器。通过组合来实现
// 对象适配器:基于组合 public interface ITarget { void f1(); void f2(); void fc(); } public class Adaptee { public void fa() { //... } public void fb() { //... } public void fc() { //... } } public class Adaptor implements ITarget { private Adaptee adaptee; public Adaptor(Adaptee adaptee) { this.adaptee = adaptee; } public void f1() { adaptee.fa(); //委托给Adaptee } public void f2() { //...重新实现f2()... } public void fc() { adaptee.fc(); } }
2.4 如何选择适配器
- 针对这两种实现方式,在实际的开发中,到底该如何选择使用哪一种呢?
- 判断的标准主要有两个,一个是 Adaptee 接口的个数,另一个是 Adaptee 和 ITarget 的契合程度。
- 如下所示
- 如果 Adaptee 接口并不多,那两种实现方式都可以。
- 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都相同,那我们推荐使用类适配器,因为 Adaptor 复用父类 Adaptee 的接口,比起对象适配器的实现方式,Adaptor 的代码量要少一些。
- 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都不相同,那我们推荐使用对象适配器,因为组合结构相对于继承更加灵活。
03.适配器模式分析
3.1 类适配器案例
- 特点
- 使用继承:类适配器通过继承源类来实现适配功能。
- 单一适配:由于 Java 中不支持多重继承,类适配器只能适配一个源类。
- 优点
- 简单实现:由于类适配器通过继承实现,它可以直接访问被适配类(Adaptee)的所有方法。这使得实现适配器时相对简单,不需要额外的委托逻辑。
- 提高代码的可复用性:适配器模式通过继承实现,使得子类能够继承父类的所有功能,从而提高了代码的复用性。
- 可以重定义被适配类的一些行为:通过继承,可以在适配器类中重定义被适配类的一些方法,实现更加灵活的适配。
- 缺点
- 受限于单继承:类适配器模式在Java等单继承语言中有一个显著的缺点,即一个类只能继承一个父类。这意味着如果适配器类已经有一个父类,就不能再使用类适配器模式来继承另一个类。
- 高耦合:适配器类和被适配类之间的耦合度较高,因为适配器类直接继承了被适配类的实现。如果被适配类发生变化,适配器类可能也需要进行相应的修改。
- 不符合“组合优于继承”原则:面向对象设计中,组合优于继承的原则提倡使用组合来代替继承,以降低类之间的耦合度。类适配器模式违背了这一原则,因为它是通过继承来实现适配的。
3.2 对象适配器案例
3.3 适配器模式结构图
- 适配器模式包含如下角色:
- Target:目标抽象类
- Adapter:适配器类
- Adaptee:适配者类
- Client:客户类
- 适配器模式有对象适配器和类适配器两种实现
- 对象适配器:
image
- 类适配器:
image
3.4 适配器模式时序图
- 适配器模式时序图如下所示:
image
04.适配器应用解析
4.1 读卡器适配案例
- 读卡器需求分析:
- 现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。
- 首先我们先开发电脑读取SD卡的业务:
- Computer只能使用SD卡,所以方法名为readSD(),需要SDCard类型的对象。具体做法是:主要是SDCard(包含两个方法:读数据,写数据),子实现类为SDCardImpl。
public interface SDCard { //从SD卡中读取数据 String readSD(); //往SD卡中写数据 void writeSD(String msg); } //具体的SD卡 public class SDCardImpl implements SDCard { @Override public String readSD() { String msg = "SDCard read msg : SD"; System.out.println(msg); return msg; } @Override public void writeSD(String msg) { System.out.println("SDCard write msg : " + msg); } } //计算机类 public class Computer { //从SD卡中读取数据 public String readSD(SDCard sdCard) { if (sdCard == null) { throw new NullPointerException("sd card is null"); } return sdCard.readSD(); } } private void test1() { //计算机读SD卡 Computer computer = new Computer(); SDCardImpl sdCard = new SDCardImpl(); computer.readSD(sdCard); } //输出结果是: //SDCard read msg : SD
- 然后,思想一下如何给TF做适配。
- 第一步:定义了一个接口 TFCard 接口,有两个方法(读取数据,写数据)。子实现类TFCardImpl重写了接口的两个方法
- 第二步:当我们想用Computer去读取TF卡中的内容,不能直接读取,需要定义适配器类。要让这个适配器类实现目标接口,就要重写SDCard中的两个方法,同时我们要让它去继承TFCardImpl。
- 第三步:最后,实现之后这两个方法看似是从SD卡中读数据写数据,但是实际上用的是TF卡中的功能
//适配者类的接口 public interface TFCard { //从TF卡中读取数据 String readTF(); //往TF卡中写数据 void writeTF(String msg); } //适配者类 public class TFCardImpl implements TFCard { @Override public String readTF() { String msg = "TFCard read msg : TF"; System.out.println(msg); return msg; } @Override public void writeTF(String msg) { System.out.println("TFCard write msg : " + msg); } } //适配器类 public class SDAdapterTF extends TFCardImpl implements SDCard { @Override public String readSD() { System.out.println("adapter read tf card"); return readTF(); } @Override public void writeSD(String msg) { System.out.println("adapter wrete tf card"); writeTF(msg); } } private void test2() { //计算器适配,读卡器,去读TF卡中的内容 Computer computer = new Computer(); //创建适配器对象,完全没有影响之前计算机读SD卡的逻辑 SDAdapterTF sdAdapterTF = new SDAdapterTF(); computer.readSD(sdAdapterTF); }
- 通过这个案例可以知道
- 适配器模式是一种强大的设计模式,能够有效解决接口不兼容的问题,使得不同接口的类能够协同工作。通过合理使用适配器模式,可以提高系统的灵活性和复用性,但也需要注意其可能带来的复杂性和性能影响。
4.2 播放器适配器案例
播放器案例需求分析
- 想象一下,我们有一个MediaPlayer接口,它可以播放mp3格式的文件。现在,我们想扩展这个功能,使其也可以播放其他格式的文件,比如vlc和mp4。但是,我们不希望修改原始的MediaPlayer接口。
https://blog.csdn.net/m0_57781768/article/details/133410344
https://cloud.tencent.com/developer/article/1907365
05.应用场景分析
- 大概有哪些场景
- 封装有缺陷的接口设计
- 统一多个类的接口设计
- 为何用这个
- 一般来说,适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷。应用这种模式算是“无奈之举”。如果在设计初期,我们就能协调规避接口不兼容的问题,那这种模式就没有应用的机会了。
5.1 封装有缺陷的接口设计
- 假设我们依赖的外部系统在接口设计方面有缺陷(比如包含大量静态方法),引入之后会影响到我们自身代码的可测试性。
- 为了隔离设计上的缺陷,我们希望对外部系统提供的接口进行二次封装,抽象出更好的接口设计,这个时候就可以使用适配器模式了。
- 具体我还是举个例子来解释一下,你直接看代码应该会更清晰。具体代码如下所示:
public class CD { //这个类来自外部sdk,我们无权修改它的代码 //... public static void staticFunction1() { //... } public void uglyNamingFunction2() { //... } public void tooManyParamsFunction3(int paramA, int paramB, ...) { //... } public void lowPerformanceFunction4() { //... } } // 使用适配器模式进行重构 public class ITarget { void function1(); void function2(); void fucntion3(ParamsWrapperDefinition paramsWrapper); void function4(); //... } // 注意:适配器类的命名不一定非得末尾带Adaptor public class CDAdaptor extends CD implements ITarget { //... public void function1() { super.staticFunction1(); } public void function2() { super.uglyNamingFucntion2(); } public void function3(ParamsWrapperDefinition paramsWrapper) { super.tooManyParamsFunction3(paramsWrapper.getParamA(), ...); } public void function4() { //...reimplement it... } }
5.2 封装有缺陷的接口设计
- 某个功能的实现依赖多个外部系统(或者说类)。通过适配器模式,将它们的接口适配为统一的接口定义,然后我们就可以使用多态的特性来复用代码逻辑。具体我还是举个例子来解释一下。
- 假设我们的系统要对用户输入的文本内容做敏感词过滤,为了提高过滤的召回率,我们引入了多款第三方敏感词过滤系统,依次对用户输入的内容进行过滤,过滤掉尽可能多的敏感词。
- 但是,每个系统提供的过滤接口都是不同的。这就意味着我们没法复用一套逻辑来调用各个系统。这个时候,我们就可以使用适配器模式,将所有系统的接口适配为统一的接口定义,这样我们可以复用调用敏感词过滤的代码。
- 你可以配合着下面的代码示例,来理解我刚才举的这个例子。
public class ASensitiveWordsFilter { // A敏感词过滤系统提供的接口 //text是原始文本,函数输出用***替换敏感词之后的文本 public String filterSexyWords(String text) { // ... } public String filterPoliticalWords(String text) { // ... } } public class BSensitiveWordsFilter { // B敏感词过滤系统提供的接口 public String filter(String text) { //... } } public class CSensitiveWordsFilter { // C敏感词过滤系统提供的接口 public String filter(String text, String mask) { //... } } // 未使用适配器模式之前的代码:代码的可测试性、扩展性不好 public class RiskManagement { private ASensitiveWordsFilter aFilter = new ASensitiveWordsFilter(); private BSensitiveWordsFilter bFilter = new BSensitiveWordsFilter(); private CSensitiveWordsFilter cFilter = new CSensitiveWordsFilter(); public String filterSensitiveWords(String text) { String maskedText = aFilter.filterSexyWords(text); maskedText = aFilter.filterPoliticalWords(maskedText); maskedText = bFilter.filter(maskedText); maskedText = cFilter.filter(maskedText, "***"); return maskedText; } } // 使用适配器模式进行改造 public interface ISensitiveWordsFilter { // 统一接口定义 String filter(String text); } public class ASensitiveWordsFilterAdaptor implements ISensitiveWordsFilter { private ASensitiveWordsFilter aFilter; public String filter(String text) { String maskedText = aFilter.filterSexyWords(text); maskedText = aFilter.filterPoliticalWords(maskedText); return maskedText; } } //...省略BSensitiveWordsFilterAdaptor、CSensitiveWordsFilterAdaptor... // 扩展性更好,更加符合开闭原则,如果添加一个新的敏感词过滤系统, // 这个类完全不需要改动;而且基于接口而非实现编程,代码的可测试性更好。 public class RiskManagement { private List<ISensitiveWordsFilter> filters = new ArrayList<>(); public void addSensitiveWordsFilter(ISensitiveWordsFilter filter) { filters.add(filter); } public String filterSensitiveWords(String text) { String maskedText = text; for (ISensitiveWordsFilter filter : filters) { maskedText = filter.filter(maskedText); } return maskedText; } }
5.3 替换依赖的外部系统
- 当我们把项目中依赖的一个外部系统替换为另一个外部系统的时候,利用适配器模式,可以减少对代码的改动。具体的代码示例如下所示:
// 外部系统A public interface IA { //... void fa(); } public class A implements IA { //... public void fa() { //... } } // 在我们的项目中,外部系统A的使用示例 public class Demo { private IA a; public Demo(IA a) { this.a = a; } //... } Demo d = new Demo(new A()); // 将外部系统A替换成外部系统B public class BAdaptor implemnts IA { private B b; public BAdaptor(B b) { this.b= b; } public void fa() { //... b.fb(); } } // 借助BAdaptor,Demo的代码中,调用IA接口的地方都无需改动, // 只需要将BAdaptor如下注入到Demo即可。 Demo d = new Demo(new BAdaptor(new B()));
5.4 兼容老版本接口
- 在做版本升级的时候,对于一些要废弃的接口,我们不直接将其删除,而是暂时保留,并且标注为 deprecated,并将内部实现逻辑委托为新的接口实现。这样做的好处是,让使用它的项目有个过渡期,而不是强制进行代码修改。这也可以粗略地看作适配器模式的一个应用场景。同样,我还是通过一个例子,来进一步解释一下。
- JDK1.0 中包含一个遍历集合容器的类 Enumeration。JDK2.0 对这个类进行了重构,将它改名为 Iterator 类,并且对它的代码实现做了优化。但是考虑到如果将 Enumeration 直接从 JDK2.0 中删除,那使用 JDK1.0 的项目如果切换到 JDK2.0,代码就会编译不通过。为了避免这种情况的发生,我们必须把项目中所有使用到 Enumeration 的地方,都修改为使用 Iterator 才行。
- 单独一个项目做 Enumeration 到 Iterator 的替换,勉强还能接受。但是,使用 Java 开发的项目太多了,一次 JDK 的升级,导致所有的项目不做代码修改就会编译报错,这显然是不合理的。这就是我们经常所说的不兼容升级。为了做到兼容使用低版本 JDK 的老代码,我们可以暂时保留 Enumeration 类,并将其实现替换为直接调用 Itertor。代码示例如下所示:
public class Collections { public static Emueration emumeration(final Collection c) { return new Enumeration() { Iterator i = c.iterator(); public boolean hasMoreElments() { return i.hashNext(); } public Object nextElement() { return i.next(): } } } }
5.5 适配不同格式的数据
- 前面我们讲到,适配器模式主要用于接口的适配,实际上,它还可以用在不同格式的数据之间的适配。
- 比如,把从不同征信系统拉取的不同格式的征信数据,统一为相同的格式,以方便存储和使用。
- 再比如,Java 中的 Arrays.asList() 也可以看作一种数据适配器,将数组类型的数据转化为集合容器类型。
List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
06.源码中适配器模式
6.1 Java中适配器模式的使用
- JDK1.1 之前提供的容器有 Arrays,Vector,Stack,Hashtable,Properties,BitSet,其中定义了一种访问群集内各元素的标准方式,称为 Enumeration(列举器)接口。
Vector v=new Vector(); for (Enumeration enum =v.elements(); enum.hasMoreElements();) { Object o = enum.nextElement(); processObject(o); }
- JDK1.2 版本中引入了 Iterator 接口,新版本的集合对(HashSet,HashMap,WeakHashMap,ArrayList,TreeSet,TreeMap, LinkedList)是通过 Iterator 接口访问集合元素。
List list=new ArrayList(); for(Iterator it=list.iterator();it.hasNext();){ System.out.println(it.next()); }
- 这样,如果将老版本的程序运行在新的 Java 编译器上就会出错。因为 List 接口中已经没有 elements(),而只有 iterator() 了。
- 那么如何将老版本的程序运行在新的 Java 编译器上呢? 如果不加修改,是肯定不行的,但是修改要遵循“开-闭”原则。我们可以用 Java 设计模式中的适配器模式解决这个问题。
public class NewEnumeration implements Enumeration { Iterator it; public NewEnumeration(Iterator it) { this.it = it; } public boolean hasMoreElements() { return it.hasNext(); } public Object nextElement() { return it.next(); } public static void main(String[] args) { List list = new ArrayList(); list.add("a"); list.add("b"); list.add("C"); for (Enumeration e = new NewEnumeration(list.iterator()); e.hasMoreElements(); ) { System.out.println(e.nextElement()); } } }
- 那么如何将老版本的程序运行在新的 Java 编译器上呢? 如果不加修改,是肯定不行的,但是修改要遵循“开-闭”原则。我们可以用 Java 设计模式中的适配器模式解决这个问题。
- NewEnumeration 是一个适配器类,通过它实现了从 Iterator 接口到 Enumeration 接口的适配,这样我们就可以使用老版本的代码来使用新的集合对象了。
6.2 Android中适配器模式的使用
- 在开发过程中,ListView的Adapter是我们最为常见的类型之一。一般的用法大致如下:
// 代码省略 ListView myListView = (ListView) findViewById(listview_id); // 设置适配器 myListView.setAdapter(new MyAdapter(context,myDatas)); // 适配器 public class MyAdapter extends BaseAdapter { private LayoutInflater mInflater; List<String> mDatas; public MyAdapter(Context context, List<String> datas) { this.mInflater = LayoutInflater.from(context); mDatas = datas; } @Override public int getCount() { return mDatas.size(); } @Override public String getItem(int pos) { return mDatas.get(pos); } @Override public long getItemId(int pos) { return pos; } // 解析、设置、缓存convertView以及相关内容 @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; // Item View的复用 if (convertView == null) { holder = new ViewHolder(); convertView = mInflater.inflate(R.layout.my_listview_item, null); // 获取title holder.title = (TextView) convertView.findViewById(R.id.title); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.title.setText(mDatas.get(position)); return convertView; } }
- 我们知道,作为最重要的View,ListView需要能够显示各式各样的视图,每个人需要的显示效果各不相同,显示的数据类型、数量等也千变万化。那么如何隔离这种变化尤为重要。
- Android的做法是增加一个Adapter层来应对变化,将ListView需要的接口抽象到Adapter对象中,这样只要用户实现了Adapter的接口,ListView就可以按照用户设定的显示效果、数量、数据来显示特定的Item View。
- 通过代理数据集来告知ListView数据的个数( getCount函数 )以及每个数据的类型( getItem函数 ),最重要的是要解决Item View的输出。Item View千变万化,但终究它都是View类型,Adapter统一将Item View输出为View ( getView函数 ),这样就很好的应对了Item View的可变性。
- 那么ListView是如何通过Adapter模式 ( 不止Adapter模式 )来运作的呢 ?我们一起来看一看。
- ListView继承自AbsListView,Adapter定义在AbsListView中,我们看一看这个类。
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher, ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, ViewTreeObserver.OnTouchModeChangeListener, RemoteViewsAdapter.RemoteAdapterConnectionCallback { ListAdapter mAdapter; // 关联到Window时调用的函数 @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); // 代码省略 // 给适配器注册一个观察者。 if (mAdapter != null&& mDataSetObserver == null){ mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); // Data may have changed while we were detached. Refresh. mDataChanged = true; mOldItemCount = mItemCount; // 获取Item的数量,调用的是mAdapter的getCount方法 mItemCount = mAdapter.getCount(); } mIsAttached = true; } /** * 子类需要覆写layoutChildren()函数来布局child view,也就是Item View */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mInLayout = true; if (changed) { int childCount = getChildCount(); for (int i = 0; i<childCount; i++) { getChildAt(i).forceLayout(); } mRecycler.markChildrenDirty(); } if (mFastScroller != null&& mItemCount != mOldItemCount){ mFastScroller.onItemCountChanged(mOldItemCount, mItemCount); } // 布局Child View layoutChildren(); mInLayout = false; mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; } // 获取一个Item View View obtainView(int position, boolean[] isScrap) { isScrap[0] = false; View scrapView; // 从缓存的Item View中获取,ListView的复用机制就在这里 scrapView = mRecycler.getScrapView(position); View child; if (scrapView != null) { // 代码省略 child = mAdapter.getView(position, scrapView, this); // 代码省略 } else { child = mAdapter.getView(position, null, this); // 代码省略 } return child; } }
- 通过增加Adapter一层来将Item View的操作抽象起来,ListView等集合视图通过Adapter对象获得Item的个数、数据元素、Item View等,从而达到适配各种数据、各种Item视图的效果。
- 因为Item View和数据类型千变万化,Android的架构师们将这些变化的部分交给用户来处理,通过getCount、getItem、getView等几个方法抽象出来,也就是将Item View的构造过程交给用户来处理,灵活地运用了适配器模式,达到了无限适配、拥抱变化的目的。
07.和其他模式对比
- 代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。
- 尽管代码结构相似,但这4种设计模式的用意完全不同,也就是说要解决的问题、应用场景不同,这也是它们的主要区别。这里我就简单说一下它们之间的区别。
- 代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
- 桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
- 装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
- 适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。
08.重要知识回顾
- 适配器模式是用来做适配,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。
- 适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
- 一般来说,适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷。
- 应用这种模式算是“无奈之举”,如果在设计初期,我们就能协调规避接口不兼容的问题,那这种模式就没有应用的机会了。
- 那在实际的开发中,什么情况下才会出现接口不兼容呢?我总结下了下面这样 5 种场景:
- 封装有缺陷的接口
- 设计统一多个类的接口
- 设计替换依赖的外部系统
- 兼容老版本接口
- 适配不同格式的数据