#设计模式 - 适配器模式 [toc]
简介
adapter class/object 别名:变压器模式 、包装模式(还包括:装饰模式) 定义:将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法一起工作饿两个类能够在一起工作
- 类别
- 类适配器模式 类适配器模式是通过多重继承实现对一个接口与另一个接口的适配,而这在java中不太适用,所以这里不做讨论
- 对象适配器 通过组合的方式,将“被适配者”作为一个对象组合到适配器类中,以修改目标接口包装被适配器
组成角色
- Targer 目标角色:该角色定义把其他的类转换为何种接口,是已经存在的。换句话说就是客户所期待的接口,可以是具体的、抽象的类或是接口
- Adaptee 源角色:你想把谁转换成目标角色,“谁”就是源角色,它是已经存在的,运行良好的类或对象
- Adapter 适配器角色:适配器模式的核心角色,是需要新建的,职责:将源角色 --> 目标角色,怎么转换?通过继承或类关联的方式.
优缺点
-
优点
- 可以让任何两个没有关联的类一起运行
- 提高了类的复用
- 增加了类的透明度
- 灵活性好
-
缺点
- 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构
适用场合
- 你想使用一个已经存在的类,而它的接口不符合你的需求。
- 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口 可能不一定兼容的类)协同工作。
- (仅适用于对象Adapter) 你想使用一些已经 存在的子类 , 但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
注意事项
- 在详细设计阶段不要使用,主要出现在维护阶段
- 与代理模式的区别:
- 代理模式 --> 不改变原接口
- 适配器模式 --> 原接口不符合规范
案例
给我一个电源,我便能伴你走天涯
小鸟:“牛哥,公司要我让我到美国的总部去学习,那边有提供电脑,但是我想带上自己的Mac,但是我怕到时不能充电,听说美国那边是低压国家,电压才110V,我国可是220V呢。”
大牛:“放心带着你心爱的本本去吧,记得买个插头转换器就行。”
小鸟:“真的吗?”
大牛:“你把你的充电器拿过来。”
大牛:“你看,你这里的输入电压的范围是:100-240V,输出:20V。这里就表示你本本要的是20V的直流电,这个电源适配器,可以将100-240V范围内的交流电转换成你本本需要的20V的直流电。”
大牛:“今天再让你涨涨姿势。”
小鸟:“这个我最喜欢了。”
大牛:“你把咱们刚才说的电源适配器用代码写出来看看。”
Target目标角色 --> 充电接口:IVoltageNoteBook
小鸟:“电源适配器与笔记本是通过接口连接的,所以我这里抽象出一个充电的接口类,让笔记本和电源适配器分别去实现它,这样才能愉快的充电”
public interface IVoltageNoteBook { // 充电接口 void voltage20();}
Target目标角色实现类 --> 笔记本类:NoteBook
小鸟:“笔记本使用的是直流电,方法里面我简单打印出来。”
public class NoteBook implements IVoltageNoteBook{ @Override public void voltage20() { // 直流电20V int inputVoltage = 20; String string = String.format("%s\n\t我是MAC\n\t\t我的额定电压是:20V\n\t已经接通电源,开始充电\n\t\t输入电压:%dV", "---------NoteBook----------", inputVoltage); System.out.println(string); }}
Adatpee源角色 --> 国家电网:GBVoltageFence
小鸟:“我这里以国家电网为例,使用的是220V的交流电.”
public class GBVoltageFence { public int voltage220() { System.out.println("\n国家电网,提供220V的电压\n"); return 220; }}
Adapter角色 --> 国标适配器:GBAdapter
小鸟:“我笔记本要求的是20V的直流电,而国家提供的是220V的交流电,国家是不可能为了我改成直流供电的,如果直接使用220V的电源充电,我就再也见不到我的本本了。就像牛哥你说的,电源适配器就是为了解决这个问题而生的,所以我们这里也来一个电源适配器的类.”
public class GBAdapter implements IVoltageNoteBook { private GBVoltageFence gbVoltageFence; public GBAdapter(GBVoltageFence gbVoltageFence) { this.gbVoltageFence = gbVoltageFence; } @Override public void voltage20() { // 调用了不符合要求的交流电:220V int inputVoltage = gbVoltageFence.voltage220(); // 转化算法: 转化为符合要求的20V直流电,将220V --> 20V int out = inputVoltage / 10 - 2; String string = String.format("%s\n\t我是电源适配器" + "\n\t\t输入的电压范围是:100-240V" + "\n\t已经接通电源,开始充电" + "\n\t\t输入电压:%dV" + "\n\t\t转化后输出的电压:%dV", "---------GBAdapter----------", inputVoltage,out); System.out.println(string); }}
客户端调用
小鸟:“开始充电了!”
// 如果国家能提供20V的直流电就好了,我就可以直接充电IVoltageNoteBook iVoltageNoteBook = new NoteBook();iVoltageNoteBook.voltage20();// 无奈的现实啊,只能使用220V的交流电充电了GBVoltageFence volateFenceGB = new GBVoltageFence();IVoltageNoteBook iVoltage20 = new GBAdapter(volateFenceGB);iVoltage20.voltage20();
大牛:“如果,你现在到美国了呢,你要充电咋办?”
小鸟:“我再加一个美国电网类,同时也加一美标适配器类,调用美国电网的供电方法 --> 通过算法转化成需要的20V的直流电,充电的时候,只要将GBVoltageFence --> 美标适配器类就行了。”
大牛:“不错。”
小鸟:“我还知道,我这里用的设计模式是适配器模式
,它的精华就在于我的适配类,本来只能使用20V的直流电才能对本本进行充电的,我这里加上它之后,就通过调用其他的类似的但是不太符合要求的事物(国标电网,美标电网),然后进行重设计,达到了我目标要求
,就跟我这里先调用国家电网的供电方法voltage220() --> 然后通过算法转化为适合我们本本的充电要求算法一样。”
小鸟:“这样模式适用于项目的维护,在前期的开发中不要考虑
,比如说我们这里,在国标适配器中看似调用的是笔记本的充电方法voltage20(),但实际调用的是国家电网提供的voltage220(),这样表里不一,很容易造成混乱,只有在已经运行良好的项目中,系统需要扩展,而原有的接口不符合规范,为了减少代码修改带来风险,我们才考虑使用它
,同时项目要遵循依赖倒置原则和里氏替换原则才行,否则这样的话也会带来很大的修改量
,还不如重构.”
大牛:“说的好!怪不得你出来不久,你们公司能把去美国学习的机会留给你。”