JAVA设计模式(4) 之装饰设计模式

在现实生活中我们的汽车都具备跑的功能,我们可以不改变汽车原有功能的前提下,把它放入一个装修厂,开进去让里面给咱们的车子做一些装饰,开出来之后呢,就具备了上天的功能了(技术可达是可以的哈),这就给原来的汽车对象,增加了额外的功能。
再举一个例子:假设我们非常爱惜一张照片,我们可以不改变照片本身前提下,给它增加一个相框,使得它具有防潮的功能,而且用户可以根据需要给它增加不同类型的相框,甚至可以在一个小相框的外面再套一个大相框。这就是针对照片这个对象的装饰,在软件工程中,同样存在类似的功能,使用装饰模式可以透明的增加指定对象的功能。


1、装饰模式的引入

首先咱们来看一段代码:
假设我要设计一个汽车类,然后在里面定义了汽车可能存在的功能(后面我还要扩展汽车的功能):

public class Car {

    public void run(){
        System.out.println("能跑");
    }

    public void fly(){
        System.out.println("能飞");
    }

    public void sweep(){
        System.out.println("能游");
    }

    public void show(){
        System.out.println("该汽车拥有的功能:");
        this.run();
        this.fly();
        this.sweep();
    }

}

public class Main {
    public static void main(String[] args) {
        Car bus = new Car();
        bus.show();
    }
}

这段代码并没有什么设计可言的。运行结果就不贴出来了,可见,我们在客户端造了一个巴士,调用了巴士的 show 方法后,发现有一些功能并不是巴士的(飞、游泳),这样显然是存在问题的。那么我们可能会作如下修改:
使用继承,每个继承体系归类。

//接口可以改为抽象类
public interface Car {
    //只要是有汽车,都具备跑的功能
    void run();

    //调用展示该汽车存在的功能
    void show();

}

public class RunCar implements Car{
//普通汽车只具备跑的功能

    public void run() {
        System.out.println("可以跑");
    }

    public void show() {
        this.run();
    }

}

public class FlyCar implements Car {//扩展的汽车具备飞的功能

    @Override
    public void run() {
        System.out.println("可以跑");
    }

    // 定义自己的功能
    public void fly() {
        System.out.println("可以飞");
    }

    @Override
    public void show() {
        this.run();
        this.fly();
    }

}

public class SwimCar implements Car{//扩展的汽车具备游泳的功能

    @Override
    public void run() {
        System.out.println("可以跑");
    }

    public void swim(){
        System.out.println("可以游泳");
    }

    @Override
    public void show() {
        this.run();
        this.swim();
    }

}

然后在客户端调用:

public class Main {
    public static void main(String[] args) {
        Car bus = new RunCar();
        bus.show();

        Car flyCar = new FlyCar();
        flyCar.show();
    }
}

运行结果如下:

我们这里使用继承的方式来扩展系统(Car)的功能,这样拥有了设计可言,但是还是存在问题的。问题在于如果增加子类,他拥有“遁地”的功能的话(当然技术先进可以做到哈,不要在意这些细节),仍然要在遁地这个子类里面定义额外的“遁地”方法。这个时候,为了解决这种继承扩展功能问题,就引入了本节的内容——装饰模式。装饰模式是扩展系统功能的最佳选择。装饰模式是对已有对象的功能进行扩展(装修、装饰),以获得更加符合用户需求的对象,使得对象具有更加强大的功能。


2、装饰模式概述

装饰模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为,在现实生活中,这种情况也到处存在,例如装修窗户,我们可以不改变窗户本身,给它增加一些额外的装饰(比如窗花),增加他的可观赏性,而且用户可以根据需要给它增加不同类型的窗花,甚至可以装饰多层。
装饰模式是一种用于替代继承的技术,它通过一种无须定义子类的方式来给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。在装饰模式中引入了装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩充原有类的功能。
装饰模式定义如下:

动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。

在装饰模式中,为了让系统具有更好的灵活性和可扩展性,我们通常会定义一个抽象装饰类,而将具体的装饰类作为它的子类,装饰模式结构如图所示:

在装饰模式结构图中包含如下几个角色:

  • Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。【上述 Car 就是这个角色】
  • ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。【上述 RunCar 就是这个角色】
  • Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。比如针对开头的案例,它可以对 Car 增加除了跑额外的功能 “可以游泳”、“下水”、“遁地”等。
  • ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。【上述 FlyCar 和 SwinCar 可以作为整个角色对 Car 动能进行扩展】

由于具体构件类和装饰类都实现了相同的抽象构件接口,因此装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。

装饰模式的核心在于抽象装饰类的设计,其典型代码如下所示:

class Decorator implements Component{
       //持有抽象构件的引用
       private Component component;  
       //注入一个抽象构件类型的对象(依赖倒置)
       public Decorator(Component component) {
              this.component=component;
       }

       public void operation(){
              //调用原有业务方法
              component.operation();  
       }
}

在抽象装饰类 Decorator 中定义了一个 Component 类型的对象 component,维持一个对抽象构件对象的引用,并可以通过构造方法或 Setter 方法将一个 Component 类型的对象注入进来,同时由于 Decorator 类实现了抽象构件 Component 接口,因此需要实现在其中声明的业务方法 operation(),需要注意的是在 Decorator 中并未真正实现 operation() 方法,而只是调用原有 component 对象的 operation() 方法,它没有真正实施装饰,而是提供一个统一的接口,将具体装饰过程交给子类完成。
在 Decorator 的子类即具体装饰类中将继承 operation() 方法并根据需要进行扩展,典型的具体装饰类代码如下:

class ConcreteDecorator extends Decorator{
       public ConcreteDecorator(Component  component){
              super(component);
       }

       public void operation(){
              super.operation();  //调用原有业务方法
              addedBehavior();  //调用新增业务方法
       }

     //新增业务方法
       public  void addedBehavior(){    
         ……
      }
}

在具体装饰类中可以调用到抽象装饰类的 operation() 方法,同时可以定义新的业务方法,如 addedBehavior()。
由于在抽象装饰类 Decorator 中注入的是 Component 类型的对象,因此我们可以将一个具体构件对象注入其中,再通过具体装饰类来进行装饰;此外,我们还可以将一个已经装饰过的 Decorator 子类的对象再注入其中进行多次装饰,从而对原有功能的多次扩展。


3、装饰模式实战

针对文章开头处的案例,使用装饰设计模式进行修改。
对于装饰模式,可以难于理解的地方在于 Decorator 抽象装饰类为何会继承或者实现 Component 抽象构建类。如果我们不继承 Component 构建类使用装饰模式的时候,代码如下:
UML图:

代码:

//Component抽象构件角色
public interface Car {
    //只要是有汽车,都具备跑的功能
top Created with Sketch.