🚏 导论

装饰模式(Decorator Pattern):动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。当现有目标功能不足的时候,需要增强时,可以考虑使用装饰模式。

装饰模式把每个要装饰的代码放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了。

装饰器常在面向切面编程(AOP)中使用,比如日志、事务、权限等功能。


🚦 结构

UML类图

classDiagram
  class Component{
    <<interface>>
    +operation()
  }
  class ConcreteComponent{
    +operation()
  }
  class Decorator{
    <<abstract>>
    -component: Component
    +operation()
  }
  class ConcreteDecoratorA{
    -addedState
    +operation()
  }
  class ConcreteDecoratorB{
    +operation()
    -AddedBehavior()
  }
  Component <|-- ConcreteComponent
  Component <|-- Decorator
  Decorator o-- Component
  Decorator <|-- ConcreteDecoratorA
  Decorator <|-- ConcreteDecoratorB

Component是定义一个对象接口,可以给这些对象动态地添加职责。ConcreteComponent是定义了一个具体的对象,也可以给这个对象添加一些职责。Decorator,装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对于Component来说,是无需知道Decorator的存在的。至于ConcreteDecorator就是具体的装饰对象,起到给Component添加职责的功能。

装饰模式是利用setComponent来对对象进行包装的。这样每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中。

基本代码

Component类

public interface Component {
    void operation();
}

ConcreteComponent类

public class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("具体对象的操作");
    }
}

Decorator类

public abstract class Decorator implements Component {
    protected Component component;

    public void setComponent(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        if (component != null) {
            component.operation();
        }
    }
}

ConcreteDecoratorA类

public class ConcreteDecoratorA extends Decorator {
    private String addedState;

    @Override
    public void operation() {
        super.operation();
        addedState = "New State";
        System.out.println("具体装饰对象A的操作");
    }
}

ConcreteDecoratorB类

public class ConcreteDecoratorB extends Decorator {
    @Override
    public void operation() {
        super.operation();
        addedBehavior();
        System.out.println("具体装饰对象B的操作");
    }

    private void addedBehavior() {
        System.out.println("为具体装饰对象B增加额外的行为addedBehavior()");
    }
}

客户端代码

public class Client {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        Decorator decoratorA = new ConcreteDecoratorA();
        Decorator decoratorB = new ConcreteDecoratorB();

        decoratorA.setComponent(component);
        decoratorB.setComponent(decoratorA);

        decoratorA.operation();
        decoratorB.operation();
    }
}

🎭 优缺点分析


😊 优点

  • 有效地将类的核心职责和装饰功能区分开,而且可以去除相关类中重复的装饰逻辑。

🙁 缺点

  • 会产生很多小对象,增加了系统的复杂性。

🎬 场景

现有一需求,要求写一个可以给人搭配不同服饰的系统,比如类似QQ、网络游戏或论坛都有的Avatar系统。


🛠 解决

第一版代码

classDiagram
  class 人 {
    +穿大T恤()
    +穿垮裤()
    +穿破球鞋()
    +穿西装()
    +打领带()
    +穿皮鞋()
    +形象展示()
  }

Person类的代码如下:

public class Person {

    /**
     * 姓名
     */
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void wearTShirts() {
        System.out.print("大T恤 ");
    }

    public void wearBigTrouser() {
        System.out.print("垮裤 ");
    }

    public void wearSneakers() {
        System.out.print("破球鞋 ");
    }

    public void wearSuit() {
        System.out.print("西装 ");
    }

    public void wearTie() {
        System.out.print("领带 ");
    }

    public void wearLeatherShoes() {
        System.out.print("皮鞋 ");
    }

    public void show() {
        System.out.print("装扮的" + name);
    }
}

客户端代码:

public class Client {
    public static void main(String[] args) {
        Person rex = new Person("Rex");

        System.out.print("\n第一种装扮:");

        rex.wearTShirts();
        rex.wearBigTrouser();
        rex.wearSneakers();
        rex.show();

        System.out.print("\n第二种装扮:");
        rex.wearSuit();
        rex.wearTie();
        rex.wearLeatherShoes();

        rex.show();
    }
}

但是这样就违背了开闭原则,如果要增加一种装扮,就需要修改Person类,这样就不符合开闭原则。可以考虑将这些服饰都写成子类。

第二版代码(用面向对象的思路构造)

classDiagram
  class 人{
    +形象展示()
  }
  class 服饰{
    <<abstract>>
    +形象展示
  }
  class 大T恤{
    +形象展示()
  }
  class 垮裤{
    +形象展示()
  }
  class 破球鞋{
    +形象展示()
  }
  class 西装{
    +形象展示()
  }
  class 领带{
    +形象展示()
  }
  class 皮鞋{
    +形象展示()
  }
  服饰 <|-- 大T恤
  服饰 <|-- 垮裤
  服饰 <|-- 破球鞋
  服饰 <|-- 西装
  服饰 <|-- 领带
  服饰 <|-- 皮鞋

Person类

public class Person {

    /**
     * 姓名
     */
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void show() {
        System.out.print("装扮的" + name);
    }
}

服饰抽象类

public abstract class Finery {

    /**
     * 服饰抽象方法
     */
    public abstract void show();
}

各种服饰子类

public class BigTrouser extends Finery {
    /**
     * 垮裤展示
     */
    @Override
    public void show() {
        System.out.print("垮裤 ");
    }
}

public class LeatherShoes extends Finery {
    /**
     * 皮鞋展示
     */
    @Override
    public void show() {
        System.out.print("皮鞋 ");
    }
}


// ...

客户端类

public class Client {
    public static void main(String[] args) {
        Person rex = new Person("Rex");

        System.out.print("\n第一种装扮:");
        Finery tShirts = new TShirts();
        Finery bigTrouser = new BigTrouser();
        Finery sneakers = new Sneakers();
        tShirts.show();
        bigTrouser.show();
        sneakers.show();
        rex.show();

        System.out.print("\n第二种装扮:");
        Finery suit = new Suit();
        Finery tie = new Tie();
        Finery leatherShoes = new LeatherShoes();

        suit.show();
        tie.show();
        leatherShoes.show();
        rex.show();

    }
}

虽然改写成了面向对象写法,实现了“服饰”与“人”类的分离,但是分离的有点过度了,这就好像一个人当众光着身子将衣服穿起来,应该是要在内部组装好再展示出来。

第三版(用装饰模式改造)

classDiagram
  class 人{
    +形象展示()
  }
  class 服饰{
    <<abstract>>
    +形象展示()
  }
  class 大T恤{
    +形象展示()
  }
  class 垮裤{
    +形象展示()
  }
  class 破球鞋{
    +形象展示()
  }
  class 西装{
    +形象展示()
  }
  class 领带{
    +形象展示()
  }
  class 皮鞋{
    +形象展示()
  }
  人 <|-- 服饰
  服饰 <|-- 大T恤
  服饰 <|-- 垮裤
  服饰 <|-- 破球鞋
  服饰 <|-- 西装
  服饰 <|-- 领带
  服饰 <|-- 皮鞋

“Person"类(ConcreteComponent)

public class Person {

    /**
     * 姓名
     */
    private String name;


    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public void show() {
        System.out.print("装扮的" + name);
    }
}

服饰类(Decorator)

public class Finery extends Person {
    /**
     * 被装饰者
     */
    protected Person component;

    /**
     * 装饰
     *
     * @param component 被装饰者
     */
    public void decorate(Person component) {
        this.component = component;
    }

    /**
     * 展示
     */
    @Override
    public void show() {
        if (component != null) {
            component.show();
        }
    }
}

具体服饰类(ConcreteDecorator)

public class BigTrouser extends Finery {
    /**
     * 垮裤展示
     */
    @Override
    public void show() {
        System.out.print("垮裤 ");
        super.show();
    }
}
public class LeatherShoes extends Finery {
    /**
     * 皮鞋展示
     */
    @Override
    public void show() {
        System.out.print("皮鞋 ");
        super.show();
    }
}
//...

客户端类

public class Client {
    public static void main(String[] args) {
        Person rex = new Person("Rex");

        System.out.print("\n第一种装扮:");
        TShirts tShirts = new TShirts();
        BigTrouser bigTrouser = new BigTrouser();
        Sneakers sneakers = new Sneakers();
        sneakers.decorate(rex);
        bigTrouser.decorate(sneakers);
        tShirts.decorate(bigTrouser);
        tShirts.show();

        System.out.print("\n第二种装扮:");
        Finery suit = new Suit();
        Finery tie = new Tie();
        Finery leatherShoes = new LeatherShoes();

        leatherShoes.decorate(rex);
        tie.decorate(leatherShoes);
        suit.decorate(tie);
        rex.show();
    }
}