七、适配器模式(Adapter Pattern)和外观模式(Facade Pattern)《HeadFirst设计模式》读书笔记
1.适配器模式(Adapter Pattern)
在生活中,如果我们想给笔记本电脑充电,那么直接将电脑和插座连接肯定是不行的,因为插座的电压是220V的交流电,而笔记本电脑需要的是较低电压的直流电,因此我们需要通过电源适配器将交流高电压转换成我们想要的直流低电压。适配器模式也是同样的道理,客户想要一个类型的接口,但是提供的是不匹配的接口,这时就需要适配器模式将不匹配的接口包装成客户想要的接口。这个模式很好理解,书中的一组图可以更形象的表示上述关系:
厂商设计了一个新的接口,不是现有的系统想要的,那么就可以通过适配器将新的厂商接口转换成现有系统所希望的接口,这样厂商和现有系统都不用修改代码。
实现起来也很简单,首先用适配器实现现有系统希望的接口,然后在适配器内组合厂商接口,在内部的方法中调用厂商的方法就可以了。
//系统要用的接口 public interface SystemWanted { public void work(); } //系统要用接口的具体实现 public class SystemWantedImpl implements SystemWanted { @Override public void work(){ System.out.println("I can work 3 hours"); } } //厂商提供了新的接口 public interface Manufacturer { void work(); } //厂商接口的具体实现 public class ManufacturerImpl implements Manufacturer { @Override public void work() { System.out.println("I can work 1 hours"); } } //适配器实现系统想要的接口,并组合厂商的接口来委托使用 public class ManufacturerAdapter implements SystemWanted{ private Manufacturer manufacturer; public ManufacturerAdapter(Manufacturer manufacturer) { this.manufacturer = manufacturer; } @Override public void work() { //因为厂商接口的实现工作时长是系统想要的1/3,所以工作三次 for (int i = 0; i < 3; i++) { manufacturer.work(); } } } //测试 public class SystemNow { public static void main(String[] args) { //系统需要的是SystemWanted接口并调用它的work()方法,通过适配器模式,委托给了Manufacturer接口的子类去做了,实现了接口的转换 Manufacturer manufacturer = new ManufacturerImpl(); SystemWanted manufacturerAdapter = new ManufacturerAdapter(manufacturer); manufacturerAdapter.work(); } }
输出结果:
类图如下:
上面这种通过组合被适配者的方式,叫做“对象适配器”,还有另外一种方式是继承被适配者,叫做“类适配器”。它们的思想相同只是具体实现是组合和继承的差别。
通过上面的例子,可以发现适配器模式和装饰者模式有点像,他们的区别在哪里呢?
1)意图不一样,适配器模式是想要一个接口“转换”成另一个接口,而装饰者模式是想要附加一些功能;
2)适配器实现目标接口,组合被转换的接口,这两个接口存在一个“转换”关系,是不同的,而装饰者要实现的接口是和它组合的类相同的接口,因为装饰者也可以被装饰。
2.外观模式(Facade (/fəˈsɑːd/ ) Pattern)
外观模式也叫门面模式,它和适配器模式实现的原理差不多,它组合很多的小类,在它内部的方法中调用很多小类的方法。外观模式和适配器模式的应用场景不同,适配器模式是为了转换接口,而外观模式则是为了简化接口,它强调将很多个接口组合起来而形成一个“外观”,让外面只看到这个“外观”而看不到具体实现。
比如说原本遥控器类要想打开电视要经过一堆接口的调用,现在把它们组合在一个外观接口中,就只需要调用外观接口的方法就可以了,而不用管它内部的实现,实现了解耦,也通过提供一个简单的接口让子系统更容易被使用。它也符合Least Knowlege(最少知悉)原则:意思是类之间的依赖关系更少,比如客户类只依赖于外观类,如果想要外观类也尽量符合这个原则,可以适当分层,用多个外观类组合子系统。
总结:
1.当客户类需要的接口和我们能提供的接口不同的时候,可以考虑使用适配器模式做一个转换;比如在Tomcat的Connector中Porcessor将字节流解析成Request对象,而Container需要的是ServletRequest对象,就是是通过一个Adapter来转换接口的。
2.当一个接口中需要调用许多接口的方法,可以通过外观模式只调用外观接口,起到简化接口和解耦的作用;