装饰模式和适配器模式在通用类图上没有太多的相似点,差别比较大,但是他们的功能有相似的地方:都是包装作用,都是通过委托方式实现其功能。不同点是:装饰模式包装的是自己的兄弟类,隶属于同一个家族(相同接口或父类),适配器模式则修饰非血缘关系类,把一个非本家族的对象伪装成本家族的对象,注意是伪装,因此他的本质还是非相同接口的对象。
大家都应该听过丑小鸭的故事吧,我们今天就用这两种模式分别讲述丑小鸭的故事。话说鸭妈妈有5个孩子,其中4个孩子都是黄白相间的颜色,而最小的那只也就是叫做丑小鸭的那只,是纯白色的,与兄弟姐妹不相同,在遭受了诸多的嘲讽和讥笑后,最终丑小鸭变成了一个美丽的天鹅。那我们如何用两种不同模式来描述这一故事呢?
用装饰模式描述丑小鸭
首先就要肯定丑小鸭是一只天鹅,只是因为他小或者是鸭妈妈的无知才没有被认出是天鹅,经过一段时间后,他逐步变成一个漂亮、自信、优美的白天鹅。根据分析我们可以这样设计,先设计一个丑小鸭,然后根据时间现后来进行不同的美化处理,怎么美化呢?先长出漂亮的羽毛,然后逐步展现出异于鸭子的不同行为,如飞行,最终在具备了所有的行为后,他就成为一只纯粹的白天鹅了。
我们按照故事的情节发展一步一步的实现程序。初期的时候,丑小鸭表现得很另类,叫声不同,外形不同,致使周围的亲戚、朋友都对他鄙视,那我们来建立这个过程,由于丑小鸭的本质就是一个天鹅,我们就先生成一个天鹅的接口,如下所示。
public interface Swan {
/**
* 天鹅会飞
*/
void fly();
/**
* 天鹅会叫
*/
void cry();
/**
* 天鹅都有漂亮的外表
*/
void desAppaearance();
}
我们定义了天鹅的行为,都会飞行、会叫,并且可以描述他们漂亮的外表。丑小鸭是一只白天鹅,是“is-a”的关系,也就是需要实现这个接口了,其实现如下所示。
public class UglyDuckling implements Swan {
@Override
public void fly() {
System.out.println("不能飞行");
}
@Override
public void cry() {
System.out.println("叫声是克噜——克噜——克噜");
}
@Override
public void desAppaearance() {
System.out.println("外形是脏兮兮的白色,毛茸茸的大脑袋");
}
}
丑小鸭具备了天鹅的所有行为和属性,因为他本身就是一只白天鹅,只是因为他太小了还不能飞行,也不能照顾自己,所以丑丑的,在经过长时间的流浪生活后,丑小鸭长大了。终于有一天,他发现自己竟然变成了一只美丽的白天鹅,有着漂亮、洁白的羽毛,而且还可以飞行,这完全是一种升华行为。我们来看看他的行为(飞行)和属性(外形)是如何加强的,先看抽象的装饰类,如下所示。
public class Decorator implements Swan {
private Swan swan;
/**
* 修饰的是谁
*
* @param swan
*/
public Decorator(Swan swan) {
this.swan = swan;
}
@Override
public void fly() {
this.swan.fly();
}
@Override
public void cry() {
this.swan.cry();
}
@Override
public void desAppaearance() {
this.swan.desAppaearance();
}
}
这是一个非常简单的代理模式。我们再来看丑小鸭是如何开始变得美丽的,变化是由外及里的,有了漂亮的外表才有内心的实质变化,如下所示。
public class BeautifyAppearance extends Decorator {
public BeautifyAppearance(Swan swan) {
super(swan);
}
@Override
public void desAppaearance() {
System.out.println("外表是纯白色的,非常惹人喜爱!");
}
}
丑小鸭最后发现自己还能飞行,这是一个行为突破,是对原有行为“不会飞行”的一种强化,如下所示。
public class StrongBehavior extends Decorator {
public StrongBehavior(Swan swan) {
super(swan);
}
@Override
public void fly() {
System.out.println("会飞行了!");
}
}
所有的故事元素我们都具备了,就等有人来讲故事了,场景类如下所示。
public class Client {
public static void main(String[] args) {
// 很久很久以前,这里有一个丑陋的小鸭子
System.out.println("===很久很久以前,这里有一个丑陋的小鸭子===");
Swan ducking = new UglyDuckling();
// 展示一下小鸭子
ducking.desAppaearance(); // 小鸭子的外形
ducking.cry(); // 小鸭子的叫声
ducking.fly(); // 小鸭子的行为
System.out.println("\n===小鸭子终于发现自己是一只天鹅===");
// 首先外形变化了
ducking = new BeautifyAppearance(ducking);
// 其次行为也发生了改变
ducking = new StrongBehavior(ducking);
// 虽然还是叫丑小鸭,但是已经发生了很大变化
ducking.desAppaearance(); // 小鸭子的新外形
ducking.cry(); // 小鸭子的新叫声
ducking.fly(); // 小鸭子的新行为
}
}
使用装饰模式描述丑小鸭蜕变的过程是如此简单,他关注了对象功能的强化,是对原始对象的行为和属性的修正和加强,把原本被人歧视、冷落的丑小鸭通过两次强化处理最终转变为受人喜爱、羡慕的白天鹅。
用适配器模式实现丑小鸭
采用适配器模式实现丑小鸭变成白天鹅的过程要从鸭妈妈的角度分析,鸭妈妈有5个孩子,他认为这5个孩子都是她的后代,都是鸭类,但是实际上是有一只(也就是丑小鸭)不是真正的鸭类,他是一只小白天鹅。因为太小,差别太细微,很难分辨,导致鸭妈妈认为他是一只鸭子,从鸭子的审美观来看,丑小鸭是丑陋的。通过分析,我们要做的就是要设计两个对象:鸭和天鹅,然后鸭妈妈把一只天鹅看成了小鸭子,最终时间到来的时候丑小鸭变成了白天鹅。
我们定义了两个接口:鸭类接口和天鹅类接口,然后建立了一个适配器UglyDuckling,把一只白天鹅封装成了小鸭子。我们来看代码,先看鸭类接口,如下所示。
public interface Duck {
/**
* 会叫
*/
void cry();
/**
* 鸭子的外形
*/
void desAppearance();
/**
* 描述鸭子的其他行为
*/
void desBehavior();
}
鸭类有3个行为,一个是鸭会叫,一个是外形描述,还有一个是综合性的其他行为描述,例如会游泳等。我们来看鸭妈妈的4个正宗孩子,如下所示。
public class Duckling implements Duck {
@Override
public void cry() {
System.out.println("叫声是噶——噶——噶");
}
@Override
public void desAppearance() {
System.out.println("外形是黄白相间,嘴长");
}
@Override
public void desBehavior() {
System.out.println("会游泳");
}
}
4只正宗的小鸭子形象已经清晰的定义出来了。鸭妈妈还有一个孩子,就是另类的丑小鸭,他实际上是一只白天鹅。我们先定义出白天鹅,如下所示。
public class WhiteSwan implements Swan {
@Override
public void fly() {
System.out.println("能够飞行");
}
@Override
public void cry() {
System.out.println("叫声是克噜——克噜——克噜");
}
@Override
public void desAppaearance() {
System.out.println("外形是纯白色,惹人喜爱");
}
}
但是,鸭妈妈却不认为自己这个另类的孩子是白天鹅,他从自己的观点触发,认为他很丑陋,有碍自己的脸面,于是驱赶他——鸭妈妈把这只小天鹅误认为一只鸭。我们来看实现,如下所示。
public class UglyDuckling extends WhiteSwan implements Duck {
@Override
public void desAppearance() {
super.desAppaearance();
}
@Override
public void desBehavior() {
// 丑小鸭不仅会游泳
System.out.println("会游泳");
// 还会飞行
super.fly();
}
}
天鹅被看成了鸭子,有点暴殄天物的感觉。我们再来创建一个场景类来描述这一场景,如下所示。
public class Client {
public static void main(String[] args) {
// 鸭妈妈有5个孩子,其中4个都是一个模样
System.out.println("===鸭妈妈有5个孩子,其中4个都是一个模样===");
Duck duck = new Duckling();
duck.cry(); // 小鸭子的叫声
duck.desAppearance(); // 小鸭子的外形
duck.desBehavior(); // 小鸭子的其他行为
System.out.println("\n===一只独特的小鸭子,模样是这样的:===");
Duck uglyDuckling = new UglyDuckling();
uglyDuckling.cry(); // 丑小鸭的叫声
uglyDuckling.desAppearance(); // 丑小鸭的外形
uglyDuckling.desBehavior(); // 丑小鸭的其他行为
}
}
可怜的小天鹅被认为是一只丑陋的小鸭子,造化弄人呀!采用适配器模式讲述丑小鸭的故事,我们首先观察到的是鸭和天鹅的不同点,建立了不同的接口以实现不同的物种,然后在需要的时候(根据故事情节)把一个物种伪装成另外一个物种,实现不同物种的相同处理过程,这就是适配器模式的设计意图。
最佳实践
我们用两个模式实现了丑小鸭的美丽蜕变。我们发现:这两个模式有较多的不同点。
- 意图不同
装饰模式的意图是加强对象的功能,例子中就是把一个怯弱的小天鹅强化成了一个美丽、自信的白天鹅,他不改变类的行为和属性,只是增加(当然了,减弱类的功能也是可能存在的)功能,使美丽更加美丽,强壮更加强壮,安全更加安全;而适配器模式关注的则是转化,他的主要意图是两个不同对象之间的转化,他可以把一个天鹅转化为一个小鸭子看待,也可以把一只小鸭子看成是一只天鹅(那估计要在小鸭子的背上装个螺旋桨了),他关注转换。
- 施与对象不同
装饰模式装饰的对象必须是自己的同宗,也就是相同的接口或父类,只要在具有相同属性和行为的情况下,才能比较行为是增加还是减弱;适配器模式则必须是两个不同的对象,因为他着重于转换,只有两个不同的对象才有转换的必要,如果是相同对象还转换什么!
- 场景不同
装饰模式在任何时候都可以使用,只要是想增强类的功能,而适配器模式则是一个补救模式,一般出现在系统成熟或已经构件完毕的项目中,作为一个紧急处理手段采用。
- 扩展性不同
装饰模式很容易扩展!今天不用这个修饰,好,去掉;明天想再使用,好,加上。这都没有问题。而且装饰类可以继续扩展下去;但是适配器模式就不同了,他在两个不同对象之间架起了一座沟通的桥梁,建立容易,去掉就比较困难了,需要从系统整体考虑是否能够撤销。