模式动机
在我们的系统开发中,我们可能经常会对现有功能进行扩展,会给现有的类增加一些行为。一般有两种机制可供选择:
继承机制:通过继承一个现有的类使得子类在拥有父类方法的同时还可以拥有自身独有的方法。但使用继承实现功能拓展,我们必须可预见这些拓展功能,这些功能是编译时就确定了,是静态的,用户不能控制增加行为的方式和时机。
关联机制:将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)。这种方式即是我们的装饰模式。
继承是一种耦合度较大的静态关系,无法在程序运行时动态扩展。使用装饰模式不会破坏类的封装性,使系统具有较好的松耦合性。
模式定义
动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活
模式结构
抽象构件:Component,抽象出一个接口,以规范准备接收附加功能的对象
具体构件:ConcreteComponent,定义一个要添加功能的类
抽象装饰类:Decorator,持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口
具体装饰类:ConcreteDecorator,负责给构件对象添加新功能
代码示例
看过变形金刚的都知道,变形金刚在变形之前是一辆汽车,它最基本的功能是在陆地上移动。但当它变成机器人之后还具有说话的能力;同时它还可以变成像飞机一样的飞行器,具有在天空飞翔的本领。我们以变形金刚为例,使用装饰器模式来实现这样的功能。
//抽象构件-变形金刚,相当于类图中的Component
public interface Transform {
public void move();
}
//具体构件-汽车,相当于类图中的ConcreteComponent
public class Car implements Transform{
@Override
public void move() {
System.out.println("移动功能");
}
}
//抽象装饰类
public class Decorator implements Transform{
private Transform transform;
public Decorator(Transform transform) {
this.transform = transform;
}
@Override
public void move() {
transform.move();
}
}
//具体装饰类-机器人
public class Robot extends Decorator{
public Robot(Transform transform) {
super(transform);
System.out.println("我变成机器人啦!");
}
public void say(){
System.out.println("我现在是机器人,我具有了说话功能");
}
}
//具体装饰类-飞机
public class Airplane extends Decorator{
public Airplane(Transform transform) {
super(transform);
System.out.println("我变成飞机啦!");
}
public void fly(){
System.out.println("我现在是飞机,我具有了飞翔功能");
}
}
//客户端测试类
public class Client {
public static void main(String[] args) {
Transform transform = new Car();
transform.move();
Robot robot = new Robot(transform);
robot.say();
Airplane airplane = new Airplane(transform);
airplane.fly();
}
}
控制台输出
移动功能
我变成机器人啦!
我现在是机器人,我具有了说话功能
我变成飞机啦!
我现在是飞机,我具有了飞翔功能
透明、半透明装饰模式
透明装饰模式中,要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该声明具体构件类型和具体装饰类型,而应该全部声明为抽象构件类型。比如我们的例子中,Robot robot = new Robot(transform)就应该修改为Transform robot = new Robot(transform)。但这样我们就没法调用我们新增的say()方法了。
所以大多数情况下我们使用装饰模式时都是使用的半透明装饰模式,即允许用户在客户端声明具体装饰者类型的对象,调用在具体装饰者中新增的方法,我们的代码示例也是使用的半透明装饰模式。
适用场景
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:一是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;二是因为类定义不能继承(如final类)。
模式简化
如果只有一个具体构件类而没有抽象构件类,那么抽象装饰类可以作为具体构件类的直接子类。如下图:
注意事项
- 一个装饰类的接口必须与被装饰类的接口保持相同,对于客户端来说无论是装饰之前的对象还是装饰之后的对象都可以一致对待。
- 尽量保持具体构件类Component作为一个“轻”类,也就是说不要把太多的逻辑和状态放在具体构件类中,可以通过装饰类对其进行扩展。