设计模式之适配器模式与外观模式
适配器模式
适配器模式就是将一个接口转化成另一个接口。
一个常见的例子是插头转换器
我们知道英式插头长这样:
而国内的插头一般是这样的
这时候,你就需要一个转换插头了
而适配器的工作就像是一个转换插头。你不需要从新买一个电器,也不用换一个插孔,只需要一个转换器即可。
假设已有一个软件系统,你希望它能和新的厂商类库搭配使用,但是这个新厂商设计出来的接口,不同于旧厂商的接口。
你不想改变现有的代码,也不能改变厂商的代码,于是你可以写一个类,将新厂商接口转成你所希望的接口。
情景:
首先你有一个鸭子接口(这书有毒啊啊啊这都什么破例子)
public interface Duck { public void quack(); public void fly(); }
有一个鸭子的子类
// 绿头鸭 public class MallardDuck implements Duck { @Override public void quack() { System.out.println("Quack"); } @Override public void fly() { System.out.println("I'm flying"); } }
然后现在有一只火鸡
public interface Turkey { public void gobble(); public void fly(); }
public class WildTurkey implements Turkey { @Override public void gobble() { System.out.println("Gobble gobble"); } @Override public void fly() { System.out.println("I'm flying a short diatance"); } }
因为你想要鸭子,鸭子不够用了,但是我们有火鸡,于是你决定为火鸡写一个配适器,偷偷地假装它们是鸭子。。。。
当需要鸭子叫的时候,就让火鸡叫,当需要鸭子飞的时候,就让火鸡飞。火鸡飞的距离短,所以还要多飞几次。
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 DuckTestDrive { public static void main(String[] args) { MallardDuck duck = new MallardDuck(); WildTurkey turkey = new WildTurkey(); Duck turkeyAdapter = new TurkeyAdapter(turkey); System.out.println("The turkey says..."); turkey.gobble(); turkey.fly(); System.out.println("\nThe Duck says..."); testDuck(duck); System.out.println("\nThe TurkeyAdapter says..."); testDuck(turkeyAdapter); } private static void testDuck(Duck duck) { duck.quack(); duck.fly(); } }
输出:
The turkey says... Gobble gobble I'm flying a short diatance The Duck says... Quack I'm flying The TurkeyAdapter says... Gobble gobble I'm flying a short diatance I'm flying a short diatance I'm flying a short diatance I'm flying a short diatance I'm flying a short diatance
我们就可以把火鸡当成鸭子了~(真·有毒。。。。
适配器模式:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
类图:
上面的类图表现的是“对象”适配器,还有一种适配器,被称作“类”适配器,需要多重继承才能实现,所以在Java中是不可能的。
现实世界一个简单的适配器。
在早期的集合实现如Vector,Stack,HashTable都实现了一个elements()的方法,该方法会返回一个Enumeration,这个接口可以逐一走过集合内的每一个元素。
而新的集合类,使用Iterator来遍历集合。
现在,将枚举适配到迭代器。
import java.util.Enumeration; import java.util.Iterator; public class EnumerationIterator implements Iterator { Enumeration enu; public EnumerationIterator(Enumeration enu) { this.enu = enu; } @Override public boolean hasNext() { return enu.hasMoreElements(); } @Override public Object next() { return enu.nextElement(); } public void remove() { throw new UnsupportedOperationException(); } }
反过来也可以将迭代器适配到枚举
import java.util.Enumeration; import java.util.Iterator; public class IteratorEnumeration implements Enumeration { Iterator iterator; public IteratorEnumeration(Iterator iterator) { this.iterator = iterator; } @Override public boolean hasMoreElements() { return iterator.hasNext(); } @Override public Object nextElement() { return iterator.next(); } }
测试一下:
import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; public class ArrayListEnumTest { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add("适"); list.add("配"); list.add("器"); list.add("模"); list.add("式"); Iterator iterator = list.iterator(); Enumeration ei = new IteratorEnumeration(iterator); showListByEnum(ei); } // 一些遗留的“客户代码”仍然使用依赖于枚举接口 public static void showListByEnum(Enumeration enumeration) { while (enumeration.hasMoreElements()) { System.out.print(enumeration.nextElement()); } } }
外观模式
情景:当你需要看一场电影的时候,你可能需要调灯光放屏幕打开投影机选择模式……每一次都是一样的步骤,等结束的时候,又是一系列繁琐的动作。
如果提供一个简单的类,能够一下就完成这一系列工作就好了。
这时就需要用外观模式,将一系列子类包装起来,提供一个简单的接口,来替代访问一系列子系统的接口。
- 外观模式不只简化了接口,也将客户从组件的子系统中解耦。
- 外观模式和适配器模式可以包装许多类,但外观的意图是简化接口,而适配器的意图是将接口转换成不同接口。
原谅自己只能写一个拙劣的例子,折叠掉。。。
package com.wenr.chapter7; public class FacadeTest { public static void main(String[] args) { A a = new A(); B b = new B(); C c = new C(); Facade facade = new Facade(a, b, c); facade.ABC1(); System.out.println(); facade.ABC2(); } } class A { public void a1() { System.out.println("a1"); } public void a2() { System.out.println("a2"); } } class B { public void b1() { System.out.println("b1"); } public void b2() { System.out.println("b2"); } } class C { public void c1() { System.out.println("c1"); } public void c2() { System.out.println("c2"); } } class Facade { A a; B b; C c; public Facade(A a, B b, C c) { this.a = a; this.b = b; this.c = c; } public void ABC1() { a.a1(); b.b1(); c.c1(); } public void ABC2() { a.a2(); b.b2(); c.c2(); } }
外观模式:提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
类图:
设计原则:最少知识原则(又称作墨忒耳法则,Law of Demeter)
只和你的密友交谈。
这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中的一部分,会影响到其他部分。
怎么才能避免这样?这个原则提供了一些方针:就任何对象而言,在该对象的方法内,我们只应调用属于以下范围的方法:
- 该对象本身
- 被当做方法的参数而传递进来的对象
- 此方法所创建或实例化的任何对象
- 对象的任何组件
举个栗子:
不使用这个原则:
public float getTemp() { Thermometer thermometer = station.getThermometer(); return thermometer.getTemperature(); }
使用这个原则:
public float getTemp() { return station.getTemperature(); }
应用此原则时,在气象站中加一个方法,用来向温度计请求温度。这可以减少我们所依赖的类的数目。
综上:
当需要使用一个现有的类而其接口并不符合你的需求时,就使用适配器。
当需要简化并统一一个很大的接口或者一群复杂的接口时,使用外观。