设计模式——结构型模式之借助装饰器模式(Decorator Pattern)动态透明扩展对象的功能(三)

引言

前一篇文章设计模式——结构型模式之通过适配器模式也可以让“强扭的瓜变得很甜”俗话说人靠衣妆,佛靠金装。有些时候本质需要十分重要,但是有些额外的修饰会更妙,无论是在现实生活还是编程世界中,除了本质还需要一些“外饰”才能达到最好的效果。尤其是在项目开发过程中,很多业务都充满了很多的不确定性,来自业务本身、产品、项目特性的,如果一味的使用继承来扩展的话,可能会相当不灵活,今天介绍的装饰器模式就是多层继承的替代方案。

一、装饰器模式概述

装饰模式(Decorator Pattern)是一种常见的结构型模式,又叫包装模式(Wrapper),又叫包装器模式(Wrapper),它可以动态地给一个对象添加一些额外的职责功能。从增加功能方面来说,装饰模式相比生成子类更为灵活。(Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.)装饰模式是对客户端以透明的方式扩展对象的功能,是继承关系的一个替代方案。即对于客户端来说并不会觉得对象在装饰前后有何不同,而且装饰模式可以在不用创造更多子类的情况下将对象的功能加以扩展,更关键在于这种扩展是完全透明的。通常装饰器模式中会涉及到两大类角色——被装饰对象装饰者,又可以细分为四种具体角色:

  • Component抽象构件——定义我们最核心的对象的一个接口或者是抽象类,即最原始的对象(必然有一个最基本、最核心、最原始的接口或抽象类充当Component抽象构件)

  • ConcreteComponent 具体构件——定义一个要被装饰器装饰的对象,Component 的具体实现,即被装饰者。

  • 抽象Decorator装饰角色 ——一般是一个抽象类(便于根据不同的装饰逻辑去实现其子类)且一定会持有一个private变量指向Component抽象构件的引用(它里面不一定只有抽象的方法呀)维护对组件对象和其子类组件的引用,但是要注意的是所谓装饰者仅仅是发挥锦上添花的作用,核心的本质功能还是应该由构件提供,相当于是把在构件的基础上进行升级,所需要继承构件

  • 具体装饰器角色(ConcreteDecorator)——通常是一群子装饰器组合共同为组件添加新的功能

    设计模式——结构型模式之借助装饰器模式(Decorator Pattern)动态透明扩展对象的功能(三)_第1张图片

二、装饰器模式的优点和缺点及可用场景

1、装饰器模式的优点

  • 虽然装饰模式与继承关系的都是为了要在基类的基础上扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性,装饰模式允许系统动态决定“贴上”或者除掉一个“装饰”而不需要改变代码的层次;而继承关系是静态的,如果想要增加或者减少一个功能只能通过增加继承层次或降低层次。

  • 使用者可以随时根据具体的业务逻辑灵活组织所需要的功能,通过使用不同的具体装饰类以及这些装饰类的组合即可

  • 装饰者类可以在被装饰者的行为前面或后面加上自己的行为,甚至取代被装饰者的行为

  • 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型

  • 通过使用装饰器模式,我们可以实现不改变原有代码,开放现有代码的方式来实现更多的功能。

  • 装饰类和被装饰类是可以独立发展且不会相互耦合。即Component类无须知
    道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具
    体的构件。

2、装饰器模式的缺点

使用装饰模式会产生比使用继承关系更多的对象和复杂的逻辑,降低了可读性,因此,合理使用装饰类,控制其数量,以便降低系统的复杂度

3、装饰器模式的适用场景及注意事项

  • 在不影响其他对象的情况下,需要以动态、透明的方式给随时给被装饰对象添加或者移除某些功能时而不影响原有逻辑,想通过灵活组合对象的方式动态地改变被装饰对象的行为

  • 需要扩展一个类的功能或给一个类增加或随时移除附加功能。

  • 当需要以多层次的继承关系才能实现扩充时,可以考虑装饰器模式。

  • 需要为某一类型的兄弟类进行改装或加装功能,首选装饰模式。

  • 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。

  • 装饰对象包含一个真实对象的引用(reference)

  • 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。

  • 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。

三、装饰器模式的实现

前不久我在项目中刚刚做了块聊天日志模块,当时产品说先实现基本的统计功能可能后期还会有其他数据需要展示,而且这些功能可能会随着项目的不同会有不同程度删减组合,当时我就想到了装饰器模式。

1、首先定义抽象构件

//定义原始的被装饰者,即抽象构件
public abstract class ChatLogReport {
    public abstract void showLog();
}

2、实现具体的被装饰者角色

实现具体的被装饰者角色,相当于是实现需要的核心功能,其他为附加的锦上添花的功能

public class RobotChatLogReprot extends RobotChatLogReport{

    @Override
    public void showLog() {
        //统计交互次数略...
        System.out.println("今天智能机器人交互次数5000次");
    }
}

3、定义抽象装饰器角色

//定义抽象装饰器角色
public abstract class Decorator extends ChatLogReport {
    // 此处我的设计是在具体的被装饰者的基础上进行装饰,而不是取代原有功能,所以最终还需要调用原具体被装饰者去实现核心功能
    private ChatLogReport report;

    public Decorator(ChatLogReport r) {
        this.report = r;
    }
    public void showLog() {
        System.out.println(this.getClass().getSimpleName()+"调用ChatLogReport的showLog");
        this.report.showLog();
    }
}

4、根据具体业务实现具体装饰器

一个附加功能对应一个具体装饰器

public class SexTypeDecorator extends Decorator {

    public SexTypeDecorator(ChatLogReport r) {
        super(r);
    }

    public void showType(){
        System.out.println("通过"+this.getClass().getSimpleName()+"增加的装饰功能统计交互人员性别:男 1200次  女3800次");
    }

    @Override
    public void showLog() {
        showType();
        System.out.println("****"+this.getClass().getSimpleName()+"调用Decorator的showLog"+"****");
        super.showLog();
    }

}
public class TypeDecorator extends Decorator {

    public TypeDecorator(ChatLogReport r) {
        super(r);
    }

    public void showType(){
        System.out.println("通过"+this.getClass().getSimpleName()+"增加的装饰功能统计交互类别:自然聊天 1200次  政务咨询3000次 其他800次");
    }

    @Override
    public void showLog() {
        showType();
        System.out.println("****"+this.getClass().getSimpleName()+"调用Decorator的showLog"+"****");
        super.showLog();
    }
}

虽然最终调用的还是被装饰者的核心方法,但是已被子装饰器修饰了

public class ProductManager {

    public static void main(String[] args) {
        RobotChatLogReport report=new RobotChatLogReport();
        report=new SexTypeDecorator(report);//如果需要统计性别功能就附加上
        report=new TypeDecorator(report);//需要类别统计问题就附加上
        report.showLog();
    }
}

设计模式——结构型模式之借助装饰器模式(Decorator Pattern)动态透明扩展对象的功能(三)_第2张图片

四、小结

如果说前一种适配器模式是一种补偿模式的话,那么装饰器模式更像是一种“预防”模式,使用装饰器模式可以避免后期频繁修改功能带来的困扰,可以随意灵活组合,高度的扩展性和较低的耦合度。装饰器模式与适配器、代理模式都有点类似,和适配器模式一样都拥有一个目标对象,而装饰器通过包装一个装饰对象来扩展其功能而又不改变其接口,这实际上是基于对象的适配器模式的一种变种。不同之处在于,适配器模式需要实现另外一个接口,而装饰器模式必须实现该对象的接口。适配器模式核心是为了接口的转换,而装饰者模式关注的是通过组合来动态的为被装饰者注入新的功能或行为。与代理模式相比,装饰器模式是以透明的方式扩展功能,而代理模式则是通过一个对象代理另一个对象,并利用代理对象来持有被被代理对象的引用,是被被代理对象的控制而非本身功能的加强。最后在源码中,装饰器模式也使用得很多,比如说Context本身就是一个抽象组件,定义了大量的抽象方法,而ContextImpl则是相当于具体的组件,而ContextWrapper则是充当装饰器角色,我们熟悉的Activity就相当于是子装饰器

你可能感兴趣的:(设计模式,设计模式与架构)