设计模式之装饰器模式(decorator pattern)

装饰器模式主要对现有的类对象进行包裹和封装,以指望在不改变类对象及其类定义的状况下,为对象添加额外功能。是一种对象结构型模式。须要注意的是,该过程是经过调用被包裹以后的对象完成功能添加的,而不是直接修改现有对象的行为,至关于增长了中间层。相似于python中的@装饰器。html

下面仍是按照老规矩,先来了解一下该模式相关的概念和原理,而后经过两个具体的实例体会一下如何在实际开发中应用该模式。python

1. 目的

能够动态的为同一类的不一样对象加以修饰以添加新的功能。网络

2. 动机

灵活的对类对象功能进行扩展。app

3. 优缺点

优势:tcp

  1. 相比较于类的继承来扩展功能,对对象进行包裹更加的灵活;
  2. 装饰类和被装饰类相互独立,耦合度较低;

缺点:ide

  1. 没有继承结构清晰;
  2. 包裹层数较多时,难以理解和管理;

4. 应用场景

  • 动态的增长对象的功能;
  • 不能以派生子类的方式来扩展功能;
  • 限制对象的执行条件;
  • 参数控制和检查等;

5.  原理

下面是GoF介绍的典型的装饰器模式的UML类图:函数

Component:this

 对象的接口类,定义装饰对象和被装饰对象的共同接口;spa

ConcreteComponent:code

 被装饰对象的类定义;

Decorator:

 装饰对象的抽象类,持有一个具体的被修饰对象,并实现接口类继承的公共接口;

ConcreteDecorator:

 具体的装饰器,负责往被装饰对象添加额外的功能;

说明:

 因为这个模式从实际的例子来理解更加的直观方便,所以这里再也不单独的实现上面的UML结构代码。

6.实例——画图

先来经过一个简单的画图的实例来直观感觉一下。

前提:

系统中存在一个画圆的类,该类只是用来画圆,以及其余一些大小和位置等参数的控制。

新加需求:

  • 能够对圆的边进行着色
  • 能够对圆填充颜色;
  • 能够同时对边和内部着色;

这个需求的常规方法实现可能以下:

  1. 对画圆类进行迭代,以支持边和内部颜色填充 ;
  2. 画圆类做为父类,分别定义三个子类,继承父类的画圆方法,子类分别实现对应的做色需求;

上面的两个方法都是可行的,也是比较直观的,这里咱们尝试使用装饰器模式来实现,做为以上两种方法的对比。

下面来看一下装饰器模式实现该需求的UML类图:

接口类:shape

public interface Shape {
    void draw();
}

 画圆类:Circle

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.print("a circle!");
    }
}

抽象装饰器类:Decorator

public abstract class Decorator implements Shape {
    
    protected Shape circle;
    
    public Decorator(Shape shape) {
        circle = shape;
    } 
    
    public void draw() {
        circle.draw();
    }
}

为圆边着色装饰器类:CircleEdge

public class CircleEdge extends Decorator {
    
    public CircleEdge(Shape circle) {
        super(circle);
    }
    
    private void setEdgeColor() {
        System.out.print(", edge with color");
    }
    
    public void draw() {
        circle.draw();
        setEdgeColor();
    }
}

为圆填充颜色装饰器类:CircleEdge

public class CircleFill extends Decorator {
    
    public CircleFill(Shape circle) {
        super(circle);
    }
    
    private void setEdgeFill() {
        System.out.print(", content with color");
    }
    
    public void draw() {
        circle.draw();
        setEdgeFill();
    }
}

演示:

public class Demo {
    public static void main(String[] args) {
        Shape circle = new Circle();
        circle.draw();
        System.out.println("");
        Decorator circleEdge = new CircleEdge(circle);
        circleEdge.draw();
        System.out.println("");
        Decorator circleFill = new CircleFill(circle);
        circleFill.draw();
        System.out.println("");
        Decorator circleEdgeFill = new CircleFill(circleEdge);
        circleEdgeFill.draw();
    }
}

结果:

a circle!
a circle!, edge with color
a circle!, content with color
a circle!, edge with color, content with color

 上面咱们经过实现两个装饰器分别完成对边着色和填充的需求,经过对装饰器的进一步装饰,咱们完成了同时着色的需求。

7.实例——网络数据报封装

接下来咱们在使用网络数据传输的例子来体会一下装饰器模式,下图表示的是应用层的文件传输协议FTP经过TCP来传输数据:

虽然应用层能够越过传输层直接使用网络层进行数据发送(如,ICMP),但多数都会使用传输层的TCP或者UDP进行数据传输的。

下面咱们用装饰器模式来表示一下应用层数据经过传输层来发送数据,UML类图以下:

上述图中表示了,应用层的数据经过添加TCP头或者UDP头,而后经过下面的网络层send数据。

 数据报接口类:Datagram

public interface Datagram {
    void send();    // 经过网络层发送IP数据报
}

应用层数据类:AppDatagram

public class AppDatagram implements Datagram {
    @Override
    public void send() {
        System.out.print("send IP datagram!");
    }
}

传输层类(抽象装饰器):TransportLayer

public abstract class TransportLayer implements Datagram {
    
    protected Datagram appData;
    
    public TransportLayer(Datagram appData) {
        this.appData = appData;
    } 
    
    public void send() {
        appData.send();
    }
}

添加TCP头部类:UseTCP

public class UseTCP extends TransportLayer {
    
    public UseTCP(Datagram appData) {
        super(appData);
    }
    
    private void addHeader() {
        System.out.print("Appdata add TCP header, ");
    }
    
    public void send() {
        addHeader();
        appData.send();
    }
}

添加TCP头部类:UseUDP

public class UseUDP extends TransportLayer {
    
    public UseUDP(Datagram appData) {
        super(appData);
    }
    
    private void addHeader() {
        System.out.print("Appdata add UDP header, ");
    }
    
    public void send() {
        addHeader();
        appData.send();
    }
}

演示:

public class Demo {
    public static void main(String[] args) {
        Datagram appData = new AppDatagram();
        appData.send();
        System.out.println("");
        TransportLayer tcpData = new UseTCP(appData);
        tcpData.send();
        System.out.println("");
        TransportLayer udpData = new UseUDP(appData);
        udpData.send();
        System.out.println("");
    }
}

结果:

send IP datagram!
Appdata add TCP header, send IP datagram!
Appdata add UDP header, send IP datagram!

固然这里例子中已经添加过TCP头部的数据报不能再使用UDP传输了,无心义,也被必要。

8. 总结

其实所谓装饰器,本质上是对现有类对象的包裹,获得一个增强版的对象。

和python中@装饰器不一样的是:

  1. python中的装饰器是做用于函数或者类定义的,并直接覆盖掉了原来函数或者类的定义;
  2. 装饰器模式仅仅是修改了了已经产生的对象的行为,和类定义没有半点关系;

经过上面的两个例子,应该对装饰器模式有了一个简单的认识。

另外,要体会到何时用继承何时用装饰器。

参考:

GoF《Design Patterns: Elements of Reusable Object-Oriented Software》

https://www.runoob.com/design-pattern/decorator-pattern.html

相关文章
相关标签/搜索