在讲装饰器模式之前,我先讲讲代码实例,在讲具体的原理和结构。
情景:游戏中经常使用枪(英文:Gun),有手枪(英文:Pistol),狙击枪(英文:SniperRifle)等等。
然后枪有个基本功能,肯定是fire,也许shot更恰当,如果用代码实现,大致如下:
- 抽象类:枪(Gun)
public abstract class Gun {
//开火
public abstract Fire fire();
}
- 具体类:手枪(Pistol),继承于 Gun
public class Pistol extends Gun{
@Override
public Fire fire() {
Fire fire = new Fire();
fire.setRange(50); //射程50
fire.setVoice(false); //开火有声音
return fire;
}
}
- 具体类:狙击枪(SniperRifle),继承于 Gun
public class SniperRifle extends Gun{
@Override
public Fire fire() {
Fire fire = new Fire();
fire.setRange(400);//射程400
fire.setVoice(false);//开火有声音
return fire;
}
}
枪可能有很多种,这里,就是举两个。
- 开火属性类,表示开火的属性。
public class Fire {
//射程
private int range;
//开枪是否有声音
private boolean voice;
@Override
public String toString() {
return "[射程:" + range + ","+((voice) ? "消音":"没有消音")+"]";
}
/***************************getter & setter*******************************/
public int getRange() {return range;}
public void setRange(int range) {this.range = range;}
public boolean isVoice() {return voice;}
public void setVoice(boolean voice) {this.voice = voice;}
/***************************getter & setter*******************************/
}
在没有给枪装任何配件的情况下,这就是裸枪了。开火始终有声音,射程总是不变。
现在,我们用装饰器模式给枪装配件,我们设定如下:
- 如果装上消音器(英文:Silencer),可以消音
- 如果装上瞄准镜(英文:Collimation Mirror),可以增加射程。
我们代码实现如 :
- 我们定义一个配件的父类,这个类可以不是抽象类,但是很明显,定义为abstract 更符合语义
/**
* 配件父类
*/
public abstract class DecorateParts extends Gun{
protected Gun gun;
/**核心方法,这个方法很关键,是装饰器模式的核心 */
@Override
public Fire fire() {
return (gun != null) ? gun.fire() : null;
}
/**安装配件*/
public void install(Gun gun) {
this.gun = gun;
}
}
- 具体配件类:消音器
/**
* 消音器
*/
public class Silencer extends DecorateParts{
@Override
public Fire fire() {
Fire fireResult = super.fire();
fireResult.setVoice(true);//装上消音
return fireResult;
}
}
- 具体配件类:瞄准镜
/**
* 瞄准镜
*/
public class CollimationMirror extends DecorateParts{
//增加的射程,这里设定增加100射程
private int addRange = 100;
@Override
public Fire fire() {
Fire fireResult = super.fire();
//增加射程
fireResult.setRange(fireResult.getRange() + addRange);
return fireResult;
}
}
至此,配件相关的类也写完了,接下来,测试一下:
public class DecorateMain {
public static void main(String[] args) {
//创建一把手枪
Pistol gun = new Pistol();
System.out.println("手枪-[裸枪]-" + gun.fire());
//创建一个瞄准镜,裸枪上,安装瞄准镜
CollimationMirror mirrorGun = new CollimationMirror();
mirrorGun.install(gun);
System.out.println("手枪-[瞄准镜]-" + mirrorGun.fire());
//创建一个消音器,在安装了瞄准镜的基础上,再安装消音器
Silencer silencerMirrorGun = new Silencer();
silencerMirrorGun.install(mirrorGun);
System.out.println("手枪-[瞄准镜,消音器]-" + silencerMirrorGun.fire());
//创建一个消音器,裸枪上,安装消音器
Silencer silencerGun = new Silencer();
silencerGun.install(gun);
System.out.println("手枪-[消音器]-" + silencerGun.fire());
}
}
具体的运行结果如下:
在不影响枪的情况下,对枪进行装饰,主要还是看配件的3个类。
DecorateParts 本质上他不是配件,他继承了Gun,然后还有一个Gun的引用(protected Gun gun),
可以说这是装饰器模式关键。
我们从代码中可以知道,手枪,狙击枪是在Gun的基础上进行扩展,而消音器,瞄准器是在DecorateParts 上扩展。
/**
* 消音器
*/
public class Silencer extends DecorateParts{
@Override
public Fire fire() {
Fire fireResult = super.fire();
fireResult.setVoice(true);//装上消音
return fireResult;
}
}
我们主要分析下消音器,使用装饰器模式,基本上第一个的方法的就是super.fire(),也就是调用父类的同名方法,
这也代理模式有些相似,但是并不相同。看如下代码,也是就是配件的装配过程:
Pistol gun = new Pistol();
mirrorGun.install(gun);
silencerMirrorGun.install(mirrorGun);
对于silencerMirrorGun;super.fire()其实就是mirrorGun.fire();
对于mirrorGun,super.fire()其实是gun.fire()
由此可见:silencerMirrorGun.fire()中第一步调用了mirrorGun.fire(),mirrorGun.fire()第一步调用了gun.fire()。
所以mirrorGun.fire()是在gun.fire()上装饰,而silencerMirrorGun.fire()在mirrorGun.fire()的基础上装饰。
这就是装饰模式的真正意义。而代理模式也是类似,不过代理一般不会改变方法的返回结果,装饰器模式一遍都是多层的,也就是说装配了很多东西,而代理模式一般也就只有一层而已。
最后来看看装饰器模式的类图结构,图片来自大话设计模式,如下:
与我上面的例子其实很容易对应,如下:
(Component – Gun)
(ConcreteComponent – Pistol)
(Decorator – DecorateParts)
(ConreteDecoratorA – Silencer)
(ConreteDecoratorB – CollimationMirror)
结语:当系统需要新的功能的时候,而且是像旧的类中添加新的代码。这些新的逻辑通常修饰了原有类的核心设计和职责。这时候可以考虑用装饰器模式