重学设计模式之装饰模式

装饰模式

定义

装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。

在软件开发中,往往会有这样一种需求,我们需要在不改变原系统代码的时候,给一个类增加一个新的功能或特性,而Java中单继承的特性往往会限制我们对原代码的拓展。采用装饰模式可以使用非继承的方式且不改变原系统的情况下拓展新的功能/特性。

UML图

装饰者模式是一种比较容易理解的设计模式。它的UML图如下:

装饰者模式有以下几个角色:

  • Component(抽象构建):它是具体构建与抽象装饰类的共同父类,定义一个统一的规范,使客户端以一致的方法处理装饰前后的对象。
  • ConcreteComponent(具体构建):抽象构建的具体实现,需要被装饰的对象。
  • Decorator(抽象装饰类):它也是抽象构建的字类,它用作给具体构建增加新的功能/特性,但是具体增加方法由它的字类实现。它维持一个抽象构建的引用,通过该引用调用未装饰前具体构建的方法。
  • ConcreteDecorator(具体装饰类):抽象装饰类的子类,实现向具体构建新增功能/特性。

我觉得装饰模式的核心就是 Decorator(抽象装饰类) ,它通过一个持有一个抽象构建的引用来实现对具体构建的调用,且让子类可以新增方法特性。

代码

//抽象构建
public abstract class Component {

    public abstract void operation();
}
//具体构建
public class ConcreteComponent extends Component {
    @Override
    public void operation() {
        System.out.println("ConcreteComponent operation");
    }
}
//抽象装饰类  核心
public class Decorator extends Component {
    Component component; //持有一个抽象构建的引用

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        component.operation();
    }
}
//具体的装饰类
public class ConcreteDecoratorA extends Decorator {

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        methodA();
    }

    public void methodA(){
        System.out.println("ConcreteDecoratorA methodA");
    }
}

......

客户端调用

public class Client {

    public static void main(String[] args) {
        Component concreteComponent, concreteDecoratorA, concreteDecoratorB;

        concreteComponent = new ConcreteComponent();
        concreteComponent.operation();
        System.out.println("----------------------------");

        concreteDecoratorA = new ConcreteDecoratorA(concreteComponent);
        concreteDecoratorA.operation();
        System.out.println("----------------------------");

        concreteDecoratorB = new ConcreteDecoratorB(concreteDecoratorA);
        concreteDecoratorB.operation();
        System.out.println("----------------------------");
    }
}

客户端调用结果:

ConcreteComponent operation
----------------------------
ConcreteComponent operation
ConcreteDecoratorA methodA
----------------------------
ConcreteComponent operation
ConcreteDecoratorA methodA
ConcreteDecoratorB methodB
----------------------------

从上面可以看到,我在客户端分别创建了三个Component的子类,第一个是具体构建类,第二个是具体装饰类A,持有具体构建类的引用,第三个是具体装饰类B,持有具体装饰类A的引用,调用同一个方法后,可以从输出结果中看到,每一个装饰类都实现了被装饰类的方法同时可以装饰自己的方法。

实例

老规矩,还是用一个实例来演练一番。

某软件公司欲开发了一个数据加密模块,可以对字符串进行加密。最简单的加密算法通过对字母进行移位来实现,同时还提供了稍复杂的逆向输出加密,还提供了更为高级的求模加密。用户先使用最简单的加密算法对字符串进行加密,如果觉得还不够可以对加密之后的结果使用其他加密算法进行二次加密,当然也可以进行第三次加密。试使用装饰模式设计该多重加密系统。

分析需求,字母位移加密是原始的加密方法,其他算法的二次三次加密都是对原加密算法对装饰。

UML图

根据需求绘制UML图如下

基本上都是套用定义的UML图,根据UML图可以很清楚的看清整个软件架构。

代码

// 抽象构建
public abstract class EncryptComponent {

    abstract String encrypt(String str);
}
//原始加密算法
public class OriginalEncrypt extends EncryptComponent {

    @Override
    String encrypt(String str) {
        System.out.println("对字符串 \'"+str+"\' 使用原始加密   =====>  原始加密结果");
        String encryptStr = "原始加密结果";
        return encryptStr;
    }
}
//抽象装饰类
public class EncryptDecorator extends EncryptComponent {

    EncryptComponent encryptComponent;

    public EncryptDecorator(EncryptComponent encryptComponent) {
        this.encryptComponent = encryptComponent;
    }

    @Override
    String encrypt(String str) {
        return encryptComponent.encrypt(str);
    }
}
//另一种加密算法A
public class OtherAEncrypt extends EncryptDecorator {

    public OtherAEncrypt(EncryptComponent encryptComponent) {
        super(encryptComponent);
    }

    @Override
    String encrypt(String str) {
        return otherAEncrypt(super.encrypt(str));
    }

    public String otherAEncrypt(String str){
        System.out.println("对字符串 \'"+str+"\' 使用OtherA加密   =====>  OtherA加密结果");
        return "OtherA 加密结果";
    }
}

//另一种加密算法B 代码类似  可以到我的git上去clone
...

客户端测试

public class Client {

    public static void main(String[] args) {
        EncryptComponent originalEncrypt, otherAEncrypt, otherBEncrypt;
        String result;

        originalEncrypt = new OriginalEncrypt();
        result = originalEncrypt.encrypt("初始数据");
        System.out.println("-----------------------------------------------");

        otherAEncrypt = new OtherAEncrypt(originalEncrypt);
        result = otherAEncrypt.encrypt("初始数据");
        System.out.println("-----------------------------------------------");

        otherBEncrypt = new OtherBEncrypt(originalEncrypt);
        result = otherBEncrypt.encrypt("初始数据");
        System.out.println("-----------------------------------------------");

        otherBEncrypt = new OtherBEncrypt(otherAEncrypt);
        result = otherBEncrypt.encrypt("初始数据");
        System.out.println("-----------------------------------------------");

    }
}

结果

对字符串 '初始数据' 使用原始加密   =====>  原始加密结果
-----------------------------------------------
对字符串 '初始数据' 使用原始加密   =====>  原始加密结果
对字符串 '原始加密结果' 使用OtherA加密   =====>  OtherA加密结果
-----------------------------------------------
对字符串 '初始数据' 使用原始加密   =====>  原始加密结果
对字符串 '原始加密结果' 使用OtherB加密   =====>  OtherB加密结果
-----------------------------------------------
对字符串 '初始数据' 使用原始加密   =====>  原始加密结果
对字符串 '原始加密结果' 使用OtherA加密   =====>  OtherA加密结果
对字符串 'OtherA 加密结果' 使用OtherB加密   =====>  OtherB加密结果
-----------------------------------------------

相信大部分同学都可以猜到结果及逻辑处理过程。

简化

其实当系统中具体构建只有一个的时候,我们可以省略抽象构建,让具体构建同时担任这两个角色,如下:

如图,可以简化系统的复杂度,去处冗余的代码。具体代码就不贴了,相信对于大家应该不难理解。

半透明装饰模式

通过上面的代码,相信大部分同学都会感觉有点熟悉,这种一个类包裹另一个类的套路像不像JAVA 数据IO 操作。

FileInputStream fileInputStream = new FileInputStream("DecoratorPattern/test.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);

的确,JAVA的IO操作就是装饰模式的一个具体实现,在JAVA中装饰模式是使用十分频繁的一种设计模式。

但是,大家有没有发现这里的装饰模式与我们前面的例子有什么不同地方呢?

在上面的例子中,我们在客户端声明实例时

 EncryptComponent originalEncrypt, otherAEncrypt, otherBEncrypt;

所有的具体构建或者具体装饰都是以抽象构建来定义,因为通过UML图我们知道它们都是抽象构建的子类。这种对于客户端是而言是完全针对抽象编程,也就是透明装饰模式

但是所有的具体构建或者具体装饰都以抽象构建来定义会导致一个问题,它们都只能调用抽象构建中定义的方法,而在实际开发中,我们往往需要单独使用具体装饰类中的方法,这个时候使用抽象构建来定义具体装饰类就不合适了。

当我们需要单独使用具体装饰类中的方法时,我们就需要单独以具体装饰类来定义声明,这种就是半透明的装饰模式。

小结

装饰模式降低了系统的耦合度,可以动态增加或删除对象的职责,并使得需要装饰的具体构件类和具体装饰类可以独立变化,以便增加新的具体构件类和具体装饰类。对于拓展一个对象的功能,装饰模式比继承更加灵活。通过动态的方式来拓展一个对象的功能,且可以进行多次不同的装饰。

在实际开发中,透明装饰模式的设计难度较大,而且使用不够灵活。而半透明装饰模式可以给系统带来更大的灵活性,且设计相对简单。


源码:https://github.com/lichenming0516/DesignPattern

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,894评论 1 15
  • 设计模式基本原则 开放-封闭原则(OCP),是说软件实体(类、模块、函数等等)应该可以拓展,但是不可修改。开-闭原...
    西山薄凉阅读 3,748评论 3 13
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • every time you kiss me we are one eversleeping 忧伤还是快乐 风之甬...
    栖惶阅读 51评论 0 0