- 适配器模式与外观模式
定义
适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
形象的说,如果你在国内使用一台美版的笔记本,那么你必然需要一个交流电适配器,适配器的作用就是将一种接口转换成另一种接口,以符合客户的期望。
这个模式可以通过创建适配器进行接口转换,让不兼容的接口变成兼容。这可以让客户从实现的接口解耦。
外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
优缺点
优点:被适配者的任何子类,都可以搭配着适配器使用
应用场景
例子
假设以有偿一个软件系统,你希望它能和一个新的厂商类库搭配使用,但是这个新的厂商所设计出来的接口,不同于旧厂商的接口;因此我们需要一个适配器类将新厂商的接口转换成你所期望的接口。
鸭子适配器:将火鸡适配为鸭子
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 WindTurkey implements Turkey {
@Override
public void gobble() {
System.out.println("Gooble");
}
@Override
public void fly() {
System.out.println("I'm flying a short distance");
}
}
鸭子适配器(适配器)
public class TurkeyAdapter implements Duck {
private Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
@Override
public void quack() {
turkey.gobble();
}
@Override
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();
WindTurkey turkey = new WindTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);
System.out.println("The Turkey says ...");
turkey.gobble();
turkey.fly();
System.out.println("The Duck says ...");
testDuck(duck);
System.out.println("The TurkeyAdapter says ...");
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}
测试结果
实际上有两种适配器:“对象”适配器和“类”适配器,前面是“对象”适配器;“类”适配器在Java中是不可能的,因为他需要使用多重继承才能实现。
真实案例:
Java早期的集合(collection)类型(如:Vector、Stack、Hashtable)都实现了一个名为elements()的方法。该方法会返回一个Enumeration(枚举)。这个Enumeration接口可以逐一走过此集合内的每一个元素,而无需知道他们在集合内是如何被管理的。
序号 | 方法描述 |
0 | boolean hasMoreElements( ) 测试此枚举是否包含更多的元素。 |
1 | Object nextElement( ) 如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。 |
当Sun推出更新后的集合类时,开始使用了Iterator(迭代器)接口,这个接口和枚举接口很像,都可以让你遍历此集合类型内的每个元素,但不同的是,迭代器还提供了删除元素的能力。
序号 | 方法描述 |
0 | boolean hasNext( ) 测试此迭代集合是否包含下一个元素。 |
1 | Object next( ) 返回集合中当前游标右侧的元素 |
2 | void remove( ) 删除集合中当前游标左侧的元素 |
如今我们希望在新的代码中只使用迭代器,所以我们需要塑造一个适配器。
适配器的hasNext( )与next( )方法很容易实现,对于remove( )方法,因为枚举是一个“只读”接口,所以我们需要把remove( )方法定义成会抛出UnsupportedOperationExeception。
public class EnumerationIterator<T> implements Iterator<T> {
Enumeration<T> enumeration;
public EnumerationIterator(Enumeration<T> enumeration) {
this.enumeration = enumeration;
}
@Override
public boolean hasNext() {
return enumeration.hasMoreElements();
}
@Override
public T next() {
return enumeration.nextElement();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
外观模式就是在创建一个接口简化而统一的类,用来包装系统中一个或多个复杂的类。外观模式相当直接,很容易理解,此处就不再赘述。
对比
序号 | 模式 | 意图 |
0 | 装饰者 | 不改变接口,但加入责任 |
1 | 适配器 | 将一个接口转成另一个接口 |
2 | 外观 | 让接口更简单 |
设计原则
封装变化
多用组合,少用继承
针对接口编程,不针对实现编程
为交互对象之间的松耦合设计而努力
类应该对扩展开放,对修改关闭
依赖抽象,不依赖具体类
最少知识原则:只和朋友交谈
总结
当需要使用一个现有类而其接口并不符合你的需要时,就使用适配器;适配器改变接口以符合客户的期望。
当需要简化并统一一个很大的接口或者一群复杂的接口时,使用外观;外观将客户从一个复杂的子系统中解耦。
实现一个外观,需要将子系统组合进外观中,然后将工作委托给子系统执行。
小知识
最少知识原则(墨忒尔法则Law of Demeter):只和你的密友谈话。
这个原则不希望过多的类耦合在一起,否则会造成系统太复杂而不容易被维护。
在一个对象的方法内,我们只应该调用属于以下范围的方法:
该对象本身;
被当做方法的参数而传递进来的对象;
此方法所创建或实例化的任何对象;(前三个方针告诉我们,如果某对象是调用其他的方法的返回结果,不需要调用该对象的方法)
对象的任何组件。(把“组件”想象成是被实例变量所引用的任何对象,换句话说,把这想象成是“有一个”(HAS-A)关系)
比方说:
不建议使用这个原则
public float getTemp() {
Thermometer thermometer = station.getThermometer();
return thermometer.getTemperature();
}
采用这个原则
public float getTemp() {
return station.getTemperature();
}
优点:减少对象依赖,减少维护成本。
缺点:会导致更多的“包装”类被制造出来,以处理和其他组件的沟通,可能会导致复杂度和开发时间的增加,并降低运行时的性能。