模式7 适配器模式与外观模式"/>
设计模式7 适配器模式与外观模式
适配器模式
定义
适配器模式(Adapter Pattern)将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。 ——《HEAD First设计模式》
适配器模式分为类结构型模式和对象结构型模式两种。
主要角色
目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。客户想使用的那个类或接口。可以理解为出国旅行使用的那个充电子插头。
适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。是实际上工作的那个类。可以理解为出国旅行,酒店提供的那个实际上的插口。
适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。可以理解为出国旅行携带的电源转换器。
下图是对象适配器模式:
下图是类适配器模式:
对象适配器模式
例子来源于《HEAD First设计模式》,实现火鸡和鸭子的适配器。
客户想使用鸭子对象,拿一些火鸡来冒充鸭子,需要实现一个鸭子到火鸡的适配器。鸭子对象和第一篇[设计模式1 策略模式]是一致的()
首先是鸭子接口:
public interface Duck{public void quack();public void fly();
}
具体的鸭子类:
public MallardDuck implements Duck{public void quack(){System.out.println("Quack");}public void fly(){System.out.println("I'm flying");};
}
然后是火鸡类:
public interface Turkey{public void gobble();public void fly();
}
火鸡飞行是一次飞行一小段距离:
public class WildTurkey implements Turkey{public void gobble(){System.out.println("Gobble");}public void fly(){System.out.println("Fly short distance");}
}
那么,适配器就可以是:
public class TurkeyAdapter implements Duck{Turkey turkey;public TurkeyAdapter(Turkey turkey){this.turkey = turkey;}public void quack(){turkey.gobble();}public void fly(){for(int i = 0;i < 5;i++)turkey.fly();}
}
这样在使用火鸡伪装的鸭子时,可以这样:
public class Test
{public static void main(String[] args){WildTurkey turkey = new WildTurkey();Duck turkeyAdapter = new TurkeyAdapter(turkey);testDuck(turkeyAdapter);}static void testDuck(Duck duck){duck.quack();duck.fly();}
}
因此可以看出,客户通过目标接口(Duck),调用适配器(TurkeyAdapter)的方法(quack()、fly())对适配器发出请求,适配器把请求转换为被适配者(Turkey)的一个或多个调用接口。这个过程中对用户是透明的,用户是不知道适配器在起作用的。
这种适配器不光可以适配某个类,还可以适配任何这个类的子类。
类适配器
类适配器模式可采用多重继承方式实现。Java中不允许多重继承,可以继承Target接口和Adaptee类,来实现类适配器。
Target接口,即目标接口(Duck):
public interface Duck{public void quack();public void fly();
}
再来实现一个Adaptee,即上个例子中的Turkey:
public interface Turkey{public void gobble();public void fly();
}
public class WildTurkey implements Turkey{public void gobble(){System.out.println("Gobble");}public void fly(){System.out.println("Fly short distance");}
}
实现一个类的适配器TurkeyAdapter可以这样:
public class TurkeyAdapter extends WildTurkey implements Duck{public TurkeyAdapter(){}public void quack(){super.gobble();}public void fly(){for(int i = 0;i < 5;i++)super.fly();}
}
类的适配器和对象适配器对比
可以看出,对象适配器是持有Adaptee对象的,是组合关系,是动态的方式;类的适配器是继承adaptee,并且实现target方法,是静态的方式。
这样做可以使得对象适配器可以重定义实现行为,可以override掉父类的功能函数;但是对象适配器重定义适配的行为比较困难,但是添加行为较方便。
优缺点
优点
- 可以让任何两个没有关联的类一起运行。
- 提高了类的复用。 不需要修改原有代码而重用现有的适配者类。
- 增加了类的透明度。 客户端通过适配器可以透明地调用目标接口。
- 在很多业务场景中符合开闭原则。
缺点
增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。例如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现。
已有使用场景
- JAVA 中的 jdbc
- AVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口
适配器模式和装饰者模式对比
装饰者模式和适配器模式都是起到包装一个类的作用,都有一个别名叫做包装模式(Wrapper Pattern)。但是使用它们的目的很不一一样。
适配器模式的是要将一个接口转变成另一个接口,它的目的是通过改变接口调用方式来达到重复使用的目的。
装饰器模式是不改变原有的接口,但是增强原有对象的功能,当涉及到装饰者就意味着新的行为或责任被加入了设计中。
外观模式
定义
外观模式(Facade Pattern)提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,让子系统更容易使用。 ——《HEAD First设计模式》
主要角色
- 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
- 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
- 客户(Client)角色:通过一个外观角色访问各个子系统的功能。
图源:外观模式(Facade模式)详解
简单来说,可以理解为外观模式可以实现把很多接口进行打包,用户点一下玩一年,一个接口调用其他接口干很多事情。外观模式的特点主要是简化接口,以及减少客户端对外观组件的耦合。
例子
例子来源于《HEAD First设计模式》,实现家庭影院的外观模式。
家庭影院中,有个爆米花机,CD播放器,屏幕,用户使用家庭影院需要一个个的去打开关闭。这时候采用外观模式,一个函数让用户点一下玩一年。
首先是爆米花机:
class PopcornPopper{public void on(){System.out.println("正在打开爆米花机。。");}public void off(){System.out.println("正在关闭爆米花机。。");}public void pop(){System.out.println("正在蹦爆米花。。");}
}
然后是屏幕:
class Screen{public void up(){System.out.println("正在生起屏幕。。");}public void down(){System.out.println("正在放下屏幕。。");}
}
然后是CD播放器:
class CDPlayer{public void on(){System.out.println("正在打开CD");}public void off(){System.out.println("正在关闭CD");}public void eject(){System.out.println("弹出CD播放器!");}public void pause(){ }public void play(){}public String toString(){return "hello panda";}
}
外观模式类:
class HomeTheaterFacade{CDPlayer cd;Screen screen;PopcornPopper pop;//构造的时候拿到这些对象public HomeTheaterFacade(CDPlayer cd ,Screen screen,PopcornPopper pop){this.cd = cd;this.screen = screen;this.pop = pop;}//看电影 放一个方法里来执行一系列动作public void watchMovie(String movie){System.out.println("get ready to watch a movie..");pop.on();//首先打开爆米花机pop.pop();//然后蹦爆米花screen.down();//投影仪放下来cd.on();cd.play();}//电影结束public void endMovie(String movie){System.out.println("shutting movie theater down..");pop.off();screen.up();cd.off();}
}
对于客户而言,只需要watchMovie和endMovie:
public class FacadePattern {public static void main(String args[]){CDPlayer cd = new CDPlayer();Screen screen = new Screen();PopcornPopper pop = new PopcornPopper(); HomeTheaterFacade facade = new HomeTheaterFacade(cd,screen,pop);facade.watchMovie("movie");facade.endMovie("movie");}}
最少知识原则
每个类对象都要执行一些方法,如果直接new这些类创建对象去调方法会与这些类产生耦合。单独再写一个外观类,构造初始化时拿到这些类对象,在一个方法里去调这些类对象的方法,这样对客户来说只和一个类打交道,与子系统的一堆类解耦了。这个原则被称为:最少知识原则(Least Knowledge)。
最少知识原则的指导思想:
就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:
- 该对象本身
- 被当做方法的参数而传过来的对象
- 该方法所创建或实例化的任何对象
- 对象的任何组件
如果调用从另一个调用中返回的内容,即如下所示想另外一个对象的子部分发送请求,需要增加我们知道的对象。
//不采用这个原则
public float getTemp()
{Thermometer t = station.getThermometer();return t.getTemperture();
}
//采用这个原则
public float getTemp()
{return station.getTemperture();
}
这样可以减少我们所依赖的类的数目,在station里面加一个getTemperture方法。
这个法则也被称为德墨忒尔法则(Law of Demeter)。这个原则虽然减少了对象间的依赖减少维护成本,但是采用这个原则也会导致更多包装类被制造,处理和其他组件的沟通成本上升,效率降低。
参考
外观模式(Facade模式)详解.
Carson带你学设计模式:适配器模式(Adapter Pattern)
适配器模式
适配器模式(Adapter模式)详解
学习编程|Head First设计模式:外观模式
更多推荐
设计模式7 适配器模式与外观模式
发布评论