简说设计模式——装饰模式

时间:2024-01-22 13:39:38

一、什么是装饰模式

  还记得我的一个长辈曾经买了一部手机,买的时候还好好的新新的,刚拿到家就坏了,怎么回事呢?其实就是一个假手机,把一个已经报废的旧机子改了改,外面加了个新壳子罢了,这就是一个装饰模式,在原有的基础上加了些东西。

  装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。UML结构图如下:

  其中,Component是抽象构件,定义一个对象接口,可以给这些对象动态地添加职责;ConreteComponent定义一个具体对象,也可以给这个对象添加一些职责;Decorator是装饰抽象类,实现接口或抽象方法;ConreteDecorator是具体装饰对象,起到给Component添加职责的功能。。

  下面我们通过代码实现上面的UML图。

  1. Component抽象类

  Component是一个接口或是抽象类,就是定义我们最核心的对象,也就是最原始的对象。

1 public abstract class Component {
2 
3     public abstract void operation();
4     
5 }

   2. ConretetComponent类

  具体构件,通过继承实现Component抽象类中的抽象方法。是最核心、最原始、最基本的接口或抽象类的实现,我们要装饰的就是它。

1 public class ConcreteComponent extends Component {
2 
3     @Override
4     public void operation() {
5         System.out.println("具体对象的操作");
6     }
7 
8 }

   3. Decorator装饰类

  一般是一个抽象类,在其属性里必然有一个private变量指向Component抽象构件。

 1 public abstract class Decorator extends Component {
 2 
 3     private Component component = null;
 4     
 5     //通过构造函数传递给被修饰者
 6     public Decorator(Component component) {
 7         this.component = component;
 8     }
 9     
10     //委托给被修饰者执行
11     @Override
12     public void operation() {
13         if(component != null) {
14             this.component.operation();
15         }
16     }
17     
18 }

   4. ConcreteDecorator类

  我们可以写多个具体实现类,把最核心的、最原始的、最基本的东西装饰成其它东西。

  这里就写两个类,稍改一下二者的实现顺序,看看结果。

  A类,它的operation()方法先执行了method1()方法,再执行了Decorator的operation()方法。

 1 public class ConcreteDecoratorA extends Decorator {
 2 
 3     //定义被修饰者
 4     public ConcreteDecoratorA(Component component) {
 5         super(component);
 6     }
 7     
 8     //定义自己的修饰方法
 9     private void method1() {
10         System.out.println("method1 修饰");
11     }
12     
13     @Override
14     public void operation() {
15         this.method1();
16         super.operation();
17     }
18 
19 }

   B类,它的operation()方法先执行了Decorator的operation()方法,再执行了method2()方法。

 1 public class ConcreteDecoratorB extends Decorator {
 2 
 3     //定义被修饰者
 4     public ConcreteDecoratorB(Component component) {
 5         super(component);
 6     }
 7     
 8     //定义自己的修饰方法
 9     private void method2() {
10         System.out.println("method2 修饰");
11     }
12     
13     @Override
14     public void operation() {
15         super.operation();
16         this.method2();
17     }
18 
19 }

   5. Client客户端

 1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         Component component = new ConcreteComponent();
 5         //第一次修饰
 6         component = new ConcreteDecoratorA(component);
 7         //第二次修饰
 8         component = new ConcreteDecoratorB(component);
 9         //修饰后运行
10         component.operation();
11     }
12     
13 }

  运行结果如下:

  

  如果我们将B的运算顺序改为与A相同的,即先this再super,运行结果如下:

  

  所以我们可以知道,原始方法和装饰方法的执行顺序在具体的装饰类是固定的,可以通过方法重载实现多种执行顺序。

  至于上面的具体对象操作为什么只输出了一次,因为在装饰者类中,我们有一个“component != null“的判断条件,控制了对象的引用,更多类似的内容可参考单例模式

 二、装饰模式的应用

  1. 何时使用

  • 在不想增加很多子类的情况下扩展类时

   2. 方法

  • 将具体功能职责划分,同时继承装饰者模式

   3. 优点

  • 装饰类和被装饰类可以独立发展,而不会相互耦合。它有效地把类的核心职责和装饰功能分开了
  • 装饰模式是继承关系的一个替代方案
  • 装饰模式可以动态地扩展一个实现类的功能

  4. 缺点

  • 多层装饰比较复杂。比如我们现在有很多层装饰,出了问题,一层一层检查,最后发现是最里层的装饰出问题了,想想工作量都害怕

  5. 使用场景

  • 需要扩展一个类的功能时
  • 需要动态地给一个对象增加功能,并可以动态地撤销时
  • 需要为一批的兄弟类进行改装或加装功能时

  6. 应用实例

  • 旧机包装成新机,手机/电脑内部配件不变,只是换个外壳
  • 换衣小游戏,人还是那个人,不断给她换衣服,还可以一层套一层的
  • 孙悟空有72变,变成什么后就有了它的功能,但本质还是一只猴子

三、装饰模式的实现

  下面我们看一个例子,我们就以上面说的换装为例。我们先分析一下,换装需要有一个人类用于指定是谁换装、一个服装类为具体服装类的父类、以及服装类下的各种具体服装的类。UML图如下:

  1. 人类(Person类)

  通过构造方法获取人,再通过show()方法传递出去。

 1 public class Person {
 2 
 3     private String name;
 4     
 5     public Person() {}
 6     
 7     public Person(String name) {
 8         this.name = name;
 9     }
10     
11     public void show() {
12         System.out.println(name + "的装扮:");
13     }
14 }

   2. 服装类(Finery类)

  通过构造方法传递参数给show()方法,show()方法为重写父类Person类的方法。

 1 public class Finery extends Person {
 2 
 3     protected Person component;
 4     
 5     public void Decorate(Person component) {
 6         this.component = component;
 7     }
 8     
 9     @Override
10     public void show() {
11         if(component != null) {
12             component.show();
13         }
14     }
15     
16 }

  3. 具体服装类

  上述UML图中我给了6种服装,这个可以自行设计,内部实现都是相同的,这里就放一个TShirt类,过多的就不赘余了。

1 public class TShirts extends Finery {
2 
3     @Override
4     public void show() {
5         super.show();
6         System.out.print("T恤 ");
7     }
8      
9 }

  4. Client客户端

  接下来我们编写一个客户端测试一下装饰模式。

  首先先给adam换装,给他穿上西装、领带、皮鞋,然后展示出来;然后再给bill换装,给他穿上T恤、垮裤、球鞋,然后展示出来。我们可以看到,代码中的服装是一层套一层的,比如adam的,先给adam穿上Suits,再给Suits套上Tie,再给Tie套上LeatherShoes,然后对最后一层LeatherShoes展示。

 1 public class Client {
 2     
 3     public static void main(String[] args) {
 4         //adam的换装
 5         Person adam = new Person("adam");
 6         
 7         Suits a = new Suits();
 8         Tie b = new Tie();
 9         LeatherShoes c = new LeatherShoes();
10         
11         a.Decorate(adam);
12         b.Decorate(a);
13         c.Decorate(b);
14         c.show();
15         
16         System.out.println("\n--------------");
17         
18         //bill的换装
19         Person bill = new Person("bill");
20         
21         TShirts x = new TShirts();
22         Trouser y = new Trouser();
23         Sneakers z = new Sneakers();
24         
25         x.Decorate(bill);
26         y.Decorate(x);
27         z.Decorate(y);
28         z.show();
29     }
30 
31 }

   运行结果如下:

  

  平常当系统需要新功能时,是向旧的类中添加新的代码,这些新加的代码通常装饰了原有类的核心职责或主要行为,这种做法的问题在于,它们再主类中加入了新的字段、新的方法和新的逻辑,从而增加了主类的复杂度,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的个特殊行为的需要。

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

 

   源码地址:https://gitee.com/adamjiangwh/GoF