设计模式-装饰者模式(三)

概述

一般情况下,当我们想给一个类或对象添加功能的时候,有两种常用的方式:

  1. 继承:通过使用继承,我们可以使子类既能拥有父类的功能,也能实现本身的功能。
  2. 组合:而组合的方式,是在某一个类中包装另一个类的对象,然后通过这个对象引用来决定是否拥有该类的某些功能,我们把这个包装的对象可以称为装饰器 (Decorator);

由于继承是一种静态的行为,而组合则可以实现动态的往一个类中添加的新的行为。并且就功能而言,组合相比继承更加灵活,这样可以给某个对象而不是整个类添加一些功能。而装饰者模式就是基于组合来实现的:

装饰者模式是一种动态地往一个类中添加新的行为的设计模式。

装饰者模式简介

  也就是说,通过使用装饰者模式,可以在运行时扩充一个类的功能。大概原理是:增加一个装饰类包装原来的类,包装的方式一般是通过在将原来的对象作为装饰类的构造函数的参数。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。修饰类必须和原来的类有相同的接口。
  修饰模式是类继承的另外一种选择。类继承在编译时候增加行为,而装饰模式是在运行时增加行为。
  当有几个相互独立的功能需要扩充时,这个区别就变得很重要。在有些面向对象的编程语言中,类不能在运行时被创建,通常在设计的时候也不能预测到有哪几种功能组合。这就意味着要为每一种组合创建一个新类。相反,装饰者模式是面向运行时候的对象实例的,这样就可以在运行时根据需要进行组合。

结构

我们先通过一张图来看下装饰者模式的结构:

Decorator.jpg

图片来源:图说设计模式 - 装饰模式 - 结构图解
通过以上结构,我们可以大概了解到装饰者模式的几个角色:

  1. Component,最基础的底层接口,包含基础的功能方法;
  2. ConcreteComponent ,底层接口Component原始的实现,我们要装饰的对象就是这部分,这部分也就是被装饰者;
  3. Decorator,装饰角色,一般情况下该对象是一个抽象类,实现自Component,在该类中一般会有一个指向Component接口的对象,指向被装饰的对象,通过组合的形式对其进行包装;
  4. ConcreteDecorator,具体的装饰角色,继承自Decorator,对具体的被装饰者进行包装,并且可以添加新的功能;

我们先通过简单的代码来看一下:

  1. Component接口:
public interface Component {
    /**
     * 底层基础接口
     */
    void operation();
}
  1. ConcreteComponent实现类:
public class ConcreteComponent implements Component {
    /**
     * 基础接口的实现
     */
    @Override
    public void operation() {
        System.out.println("concrete component");
    }
}
  1. Decorator 装饰角色,抽象类,实现Component:
public abstract class Decorator implements Component {
    /** 维护一个被装饰的对象的引用*/
    protected Component component;

    /**
     * 通过构造方法传入被装饰的对象
     * @param component
     */
    public Decorator(Component component) {
        this.component = component;
    }

    /**
     * 调用被装饰的对象的方法
     */
    @Override
    public void operation() {
        component.operation();
    }
}
  1. ConcreteDecorator 具体装饰角色,继承自Decorator抽象类:
public class ConcreteDecorator extends Decorator {
    /**
     * 自定义自己的属性
     */
    private String addState = "test";

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

    @Override
    public void operation() {
        component.operation();
        System.out.println("addState:" + addState);
    }

    /**
     * 自定义新的方法,实现新的功能
     */
    public void addedBehavior() {
        System.out.println("addState:" + addState);
    }
    // 省略掉get,set方法
}
  1. Main,用于测试:
public class Main {
    public static void main(String[] args) {
        Component component = new ConcreteDecorator(new ConcreteComponent());
        component.operation();
    }
}
优缺点
  1. 我们先来看下优点:

a. 装饰者模式可以用来代替继承关系,可以动态的扩展一个实现类的功能,且不影响到其他对象;
b. 装饰者模式不管最终装饰了多少层,最终返回的对象还是Component;
c. 遵循设计模式的原则:对扩展开放,对修改关闭。

  1. 有优点就免不了会有缺点,我们再来看下缺点,缺点的话其实就比较明显了:

装饰者模式采用组合的形式比继承灵活,但也比继承相对复杂,如果装饰的层数过多,出现问题排查的时候也会相对困难些;因此,尽量减少装饰类的数量,以便降低系统的复杂度;

适用场景

装饰者模式的适用场景可以根据它的优点来进行选择:

  1. 当需要在不影响其他对象的情况下,动态的为一个类扩展功能的时候;
  2. 当不能使用继承或者不适合采用继承的方式进行类的扩展和维护时(比如类定义为final类型),可以采用装饰者模式;
在JAVA I/O流中的应用

  而装饰者模式在Java中应用最广泛最出名的恐怕就是Java中的I/O的API了。如果我们在学习I/O体系前,没有了解过装饰者模式的话,那么由于I/O体系本身复杂的结构,学习的时候或许会有一种很头疼的感觉。我们还是先来看下I/O体系大致结构:


I/O体系结构.jpg

注:没有完全展示所有I/O相关的接口。 图片来源:google 图片
我们可以大致把上图分为几层:

  1. Reader、Writer、InputStream、OutputStream,对应装饰者角色中最基础的接口:Component,这里面包含了基础的操作;
  2. FileInputStream,FileOutputStream等第二层没有其他实现类的类,对应于装饰者中被装饰的角色:ConcreteComponent;
  3. FilterReader,FilterOutputStream等,对应于装饰者中的抽象装饰角色:Decorator;
  4. BufferedOutputStream,DataOutputStream等,对应于装饰者中的具体装饰角色:ConcreteDecorator;

我们接下来可以通过一个代码简单看下实现:

public class Main {
    public static void main(String[] args) throws Exception {
        // 1. 定义输入流
        InputStream inputStream = null;
        try {
            // 2. 实例 被装饰者 : 文件读取
            inputStream = new FileInputStream("E://test.txt");
            // 3. 具体装饰角色:缓冲流
            inputStream = new BufferedInputStream(inputStream);
            // 4. 进行操作:开始读文件
            byte[] text = new byte[inputStream.available()];
            inputStream.read(text);
            System.out.println(new String(text));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            inputStream.close();
        }
    }
}

当然,装饰者模式在Java中应用的地方还有许多地方,比如最近学习的Mybatis中Executor体系结构:Executor, BaseExecutor, CachingExecutor等。

总结

学习了装饰者模式之后,我们来简单回顾总结下:

  1. 装饰者模式可以动态的扩展类的功能,就这点来说,装饰者模式要比使用继承更为灵活,按照GoF设计模式的划分,装饰者模式属于结构型模式的一种。
  2. 装饰者模式遵循了对扩展开放,对修改关闭的设计原则,在不修改原有代码的基础上,通过组合的形式扩展新的功能。
  3. 装饰者模式虽然比继承的形式要灵活,但由于其本身的繁琐性,所以也会相对继承复杂些,如果装饰的层数过多,一旦出现问题,将会提高我们排查问题的难度,所以要尽量减少装饰的层数,以便降低系统的复杂度。

本文参考自:
装饰器模式--继承的另一个选择
维基百科-装饰模式

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

推荐阅读更多精彩内容