在讲装饰者模式之前建议先看另一篇文章:JAVA23种设计模式——代理模式,因为这两种模式在类图结构上甚至说代码实现上有太多相似,以至于刚接触的时候会搞不清为什么要分成两种模式来说,但是反过来讲,以代理模式为切入点,装饰者模式就更容易理解了。
接下来会讲两个内容,一个是介绍装饰者模式,第二个就是讲装饰者模式和代理模式之间的区别以及各自的适用场景。
一、装饰者模式
其实就是在保持原有功能的情况下,增加新的功能,这时候你可能觉得熟悉,子类继承父类,也可以实现这种扩展,但是当你一次又一次增加新的扩展的时候,结构就会变成这样:
只能垂直扩展,不然你将在扩展基类喷火能力的时候,失去跑和飞行的能力。然而扩展的子类数量将随着一个又一个功能的增加而变得庞大,纵深越来越深,越来越难以调试。这时候装饰者模式就出现了,装饰者模式可以实现水平扩展,符合开闭原则,也符合迪米特法则,增加新的功能可以水平扩展实现。
基本步骤是:
- 定义一个接口
- 定义一个被装饰的类并实现接口
- 定义一个装饰的抽象类,并持有被装饰类的引用
- 定义一个装饰抽象类的实体类
以飞机举例。
1.新建一个飞机的接口,实现最简单的飞的功能
public interface IAirplane{
public void fly();
}
2.定义一个民用飞机
public class CivilAirplane implements IAirplane{
@Override
public void fly() {
System.out.println("民用飞机,载客5人");
}
}
3.定义一个装饰抽象类,并且同样实现飞机接口,并持有对飞机类的引用
public abstract class AbstractAirplane implements IAirplane {
private IAirplane airplane;//持有对飞机类(被装饰类)的引用
public AbstractAirplane(IAirplane airplane) {//通过构造函数注入被装饰类的引用
this.airplane = airplane;
}
@Override
public void fly() {//重写飞行方法,调用被装饰类的同名方法,这一步可以在后面扩展新方法的同时保留原有功能
airplane.fly();
}
}
4.继承装饰抽象类,扩展自己的新功能,可以投放炸弹
public class Fighter extends AbstractAirplane{
public Fighter(IAirplane airplane) {
super(airplane);
}
public void TransportAmmunition(){
System.out.println("战争来临,飞机经过改造可以投放弹药");
}
@Override
public void fly() {
super.fly();//调用父类方法
this.TransportAmmunition();//扩展自己的方法
}
}
5.继续扩展新功能,还可以运输伤员
public class Transport extends AbstractAirplane {
public Transport(IAirplane airplane) {
super(airplane);
}
public void TransportWounded(){
System.out.println("战争来临,经过改造,还可以运输伤员");
}
@Override
public void fly() {
super.fly();//调用父类方法
TransportWounded();//扩展自己的方法
}
}
6.调用
public class Test {
public static void main(String[] args){
IAirplane airplane = new CivilAirplane();//创建被装饰的类的实例
AbstractAirplane fighter = new Fighter(airplane);//把被装饰的类通过构造方法注入到第一个装饰类里,实际上在构造方法里还是调用父类的构造,把参数注入到父类对被装饰类的引用里
AbstractAirplane Transport = new Transport(fighter);//把第一个装饰类注入到第二个装饰类里,过程同上
Transport.fly();//调用扩展的装饰类fly()方法
}
}
输出结果:
民用飞机,载客5人
战争来临,飞机经过改造可以投放弹药
战争来临,经过改造,还可以运输伤员
到此可能就看出来了,装饰者模式是把原来子类对基类之间的继承关系,转换成代码对目标的引用,即对象之间的联系,解决了子类层层继承造成的逻辑复杂难以调试。
如果想扩大新功能,只需要继承AbstractAirplane装饰抽象类,重写fly()方法,即可实现对原有功能的扩展,而且在测试类调用时,只需要把原有功能传到新的实现类里,即可实现对功能的累加:飞行(new CivilAirplane())——>飞行+投掷炸弹(new Fighter(airplane)),飞行+投掷炸弹(fighter)——>飞行+投掷炸弹+运输伤员(new Transport(fighter))。
如果看过代理模式,可能会产生疑惑,两种模式看起来着实差不多,而且用起来总感觉也一样,到底什么时候用装饰者模式,什么时候用代理模式呢?
二、装饰者模式和代理模式之间的区别以及各自的适用场景
装饰者模式用来在原有功能的基础上,扩展新功能,强调扩展;
代理模式用来控制原有的被代理对象,使其发生的前后加点其他内容,控制流程走向。
一个强调不修改原有功能,扩展,一个强调控制原有功能。
还是举个例子,一个唱歌的歌手,有一位经纪人,这位经纪人就是他的代理,这位歌手自身实际上只需要会唱歌,唱好歌就可以了,但是还有一些商演合同,粉丝签名会等等其他的业务,就需要经纪人帮他打理,比如经纪人接了商演合同,确定地点,时间,歌手只需要到时候出来唱歌就可以了,这是代理模式,更强调控制。 歌手本身会唱歌,后来想提高自己,去学新的舞蹈,学表演,这就适合用装饰者模式。
其实他们之间的边界区分比较模糊,但是你用什么模式有助于别人理解你的代码的意图。
用代码来实现一下:
代理模式:
//接口
public interface IUserService {
public void addUser();
}
//需要被代理的目标类
public class UserService implements IUserService {
@Override
public void addUser() {
System.out.println("-----添加一条user信息-----");
}
}
//代理类,更强调对流程的控制,在之前做什么,在之后做什么。
public class UserServiceProxy implements IUserService {
private IUserService target;
public UserServiceProxy(IUserService target){
this.target = target;
}
@Override
public void addUser() {
System.out.println("------添加user信息之前先校验数据是否安全------");//这里更强调对整个流程的控制,在添加user信息之前或者之后做一些别的操作
target.addUser();
System.out.println("------添加user信息成功,将信息反馈回客户端------");
}
}
装饰者模式:
//接口
public interface IAirplane{
public void fly();
}
//需要被装饰的目标类
public class CivilAirplane implements IAirplane{
@Override
public void fly() {
System.out.println("民用飞机,载客5人");
}
}
//提取出对目标类装饰的抽象类,并持有对目标的引用,实际上代理类也持有对目标的引用
public abstract class AbstractAirplane implements IAirplane {
private IAirplane airplane;//持有对飞机类(被装饰类)的引用
public AbstractAirplane(IAirplane airplane) {//通过构造函数注入被装饰类的引用
this.airplane = airplane;
}
@Override
public void fly() {//重写飞行方法,调用被装饰类的同名方法,这一步可以在后面扩展新方法的同时保留原有功能
airplane.fly();
}
}
//扩展第一个新功能,这里更强调了扩展,当实现了多个功能沿着这条链挨个调用之后,顺序(流程控制)不那么重要, 重要的是功能的增加
public class Fighter extends AbstractAirplane{
public Fighter(IAirplane airplane) {
super(airplane);
}
public void TransportAmmunition(){
System.out.println("战争来临,飞机经过改造可以投放弹药");
}
@Override
public void fly() {
super.fly();//调用父类方法
this.TransportAmmunition();//扩展自己的方法
}
}
//扩展第二个新功能......
实际上不要为了用什么模式而用什么模式,这两种是为了解决不同的问题而总结出来的,可以在代理的基础上使用装饰者,也可以在装饰者里面加上代理,所以理解清楚各自的适用场景,灵活运行更为重要。