Decorator(装饰器)模式


Decorator模式思维导图

描述

定义

动态地给一个对象添加一些额外的职责,其别名为包装器(Wrapper)。就增加对象功能来说,装饰器模式比生成子类实现更为灵活。

类型

对象结构型模式

UML类图

Decorator模式UML类图

时序图

Decorator模式时序图

特点

  • 装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
  • 大多数的装饰模式实际上是半透明的装饰模式,即允许增加新的方法。半透明的装饰模式是介于装饰器模式和适配器模式之间的。

实现

主要角色

  • Component:抽象接口,以规范准备接收附加责任的类;
  • ConcreteComponent:目标实现类,定义一个将要接收附加责任的实现类;
  • Decorator:装饰器抽象类,持有一个构件(Component)对象的实例,将请求转发给它的Component对象(可能在转发请求前后执行一些附加的动作),实现一个与抽象构件接口一致的接口。
  • ConcreteDecorator:装饰器实现类,负责给构件对象“贴上”附加的责任;

注意事项

  • 接口的一致性:装饰对象的接口必须与它所装饰的Component的接口是一致的;
  • 省略抽象的Decorator类:当你仅需要添加一个职责时,没有必要定义抽象Decorator类,这时可以把Decorator合并到ConcreteDecorator中;
  • 保持Component类的简单性:为了保证接口的一致性,组件和装饰必须有一个公共的Component父类,保持公共父类的简单性是很重要的;
  • 改变对象外壳与改变对象内核:将Decorator看作一个对象的外壳,它可以改变这个对象的行为。另外一种方法是改变对象的内核,如Strategy模式。

示例

  • Component:抽象接口

      interface Component {
          void operation();
      }
    
  • ConcreteComponent:目标实现类

      public class ConcreteComponent implements Component {
          @Override
          public void operation() {
              System.out.println("目标实现类!");
          }
      }
    
  • Decorator:装饰器抽象类

      public abstract class Decorator implements Component{
          private Component component;
          public Decorator(Component component){
              this.component = component;
          }
      
          @Override
          public void operation() {
              component.operation();
          } 
      }
    
  • ConcreteDecorator:装饰器实现类

      public class ConcreteDecoratorA extends Decorator {
          public ConcreteDecoratorA(Component component) {
              super(component);
          }
          
          @Override
          public void operation() {
      		 // preOperation
           super.operation();
               // postOperation
          }
      }
    
      public class ConcreteDecoratorB extends Decorator {
          public ConcreteDecoratorB(Component component) {
              super(component);
          }
          
          @Override
          public void operation() {
            // preOperation
           super.operation();
               // postOperation
          }
      }
    
  • Client:客户类

      public class Client {
          public static void main(String[] args) {
              Component component = new ConcreteComponent();
      		Component decorator = new ConcreteDecoratorB(new ConcreteDecoratorA(component));
              decorator.operation();
          }
      }
    

适用场景

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 需要动态地给一个对象增加功能,并且这些功能也可以动态地被撤销。
  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类)。

优点

  • 比静态继承更灵活:与对象的静态继承(多重继承)相比,Decorator模式提供了更加灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。相比之下,继承机制要求为每个添加的职责创建一个新的子类,这会产生许多新的类,并且会增加系统的复杂度。
  • 可以通过一种动态的方式来扩展一个对象的功能,如通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,从而得到功能更为强大的对象。
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。

缺点

  • Decorator与它的ConcreteComponent不一样:Decorator是一个透明的包装。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此,使用装饰时不应该依赖对象标识。
  • 有许多小对象:采用Decorator模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

相关模式

  • Adapter模式:适配器模式将给对象一个全新的接口,而装饰仅改变对象的职责而不改变它的接口。
  • Composite模式:可以将装饰视为一个退化的、仅有一个组件的组合。然而,装饰仅给对象添加一些额外的职责——它的目的不在于对象聚焦。
  • Strategy模式:改变对象的途径不同,装饰可以改变对象的外壳,而Strategy模式可以改变对象的内核。
  • Bridge模式:桥接中的行为是横向的行为,行为彼此之间无关联,就比如异常和异常处理之间是没有行为关联的一样;而装饰者模式中的行为具有可叠加性,其表现出来的结果是一个整体,各个行为组合后的一个结果。

实际应用

装饰模式在Java语言中的最著名的应用莫过于Java I/O标准库的设计了。由于Java I/O库需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量性能重复的类出现。而如果采用装饰模式,那么类的数目就会大大减少,性能的重复也可以减至最少。

InputStream UML类图

根据上图可以看出:

  • 抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。
  • 具体构件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。
  • 抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口。
  • 具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStream、DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream。

下面是使用I/O流读取文件内容的简单操作示例。

public class Client {
    public static void main(String[] args) throws IOException {
        DataInputStream dis = null;
        try {
            dis = new DataInputStream(
                    new BufferedInputStream(
                            new FileInputStream("test.txt")
                    )
            );
            //读取文件内容
            byte[] bs = new byte[dis.available()];
            dis.read(bs);
            String content = new String(bs);
            System.out.println(content);
        } finally{
            dis.close();
        }
    }

}

观察上面的代码,会发现最里层是一个FileInputStream对象,然后把它传递给一个BufferedInputStream对象,经过BufferedInputStream处理,再把处理后的对象传递给了DataInputStream对象进行处理,这个过程其实就是装饰器的组装过程,FileInputStream对象相当于原始的被装饰的对象,而BufferedInputStream对象和DataInputStream对象则相当于装饰器。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章