装饰者模式

规律坚持

写在设计模式之前

1. 概念

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IDSsa9MF-1573649103911)(D:\Images\DesignPattern\decorator.PNG)]

  • 指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式
  • 装饰者模式又名包装(Wrapper)模式*(看源码的小伙伴以后看到Wrapper这个后缀词一定要往装饰者模式上想呀)*。装饰者模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案
  • 装饰者模式动态地将责任附加到对象身上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案
  • 装饰者模式完全遵循开闭原则

装饰者模式和继承的比较:

  1. 继承是编译时静态决定的,所有子类会继承到相同的行为。用户不能控制增加行为的方式和时机
  2. 装饰者是运行时扩展的,将一个类的对象嵌入到另外个类中,是动态灵活的,有利于扩展。尤其是在软件开发阶段利于维护
  3. 装饰者模式并不是不需要继承,装饰者利用继承获得类型匹配,而不是利用继承获得了行为,行为是通过组合而不是继承得到的
  4. 装饰者模式和继承的作用都是去扩展类的功能

2. 结构

装饰模式主要包含以下角色:

  1. 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
  2. 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
  3. 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  4. 具体装饰(Concrete Decorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任

装饰者模式的UML图如下所示:

装饰模式的结构图

3. 例子

具体的程序,我同步更新到Github上了,一直都有更新,喜欢可以fork同步

这次的例子是根据老友记中的人物来的,还是有点趣味性的

我们希望最终可以输出涂口红和眼影的Rachel,穿衣服的Joey,涂口红穿衣服的Monica,,,等等

继承

如果我们用继承来实现的话,很容易想到,用一个父类Friends,其里面有口红,眼影,衣服等布尔类型值,然后让Rachel,Joey等六个人去继承Friends就可以。我们可以画出如下类图

  • 对于Friends类来说

    public class Friends {
        private boolean eyeShadow;
        private boolean clothes;
        private boolean lipstick;
    
        void doSomething() {
            if (isEyeShadow()) {
                System.out.print("画了眼影");
            }
            if (isClothes()) {
                System.out.print("穿了衣服");
            }
            if (isLipstick()) {
                System.out.print("涂了口红");
            }
        }
    
        private boolean isEyeShadow() {
            return eyeShadow;
        }
    
        void setEyeShadow(boolean eyeShadow) {
            this.eyeShadow = eyeShadow;
        }
    
        private boolean isClothes() {
            return clothes;
        }
    
        public void setClothes(boolean clothes) {
            this.clothes = clothes;
        }
    
        private boolean isLipstick() {
            return lipstick;
        }
    
        void setLipstick(boolean lipstick) {
            this.lipstick = lipstick;
        }
    }
    
  • Joey, Rose, Rachel, Monica等只需要继承Friends就好

    public class Joey extends Friends {
        @Override
        void doSomething() {
            System.out.print("Joey");
            super.doSomething();
        }
    }
    public class Rachel extends Friends {
        @Override
        void doSomething() {
            System.out.print("Rachel");
            super.doSomething();
        }
    }
    
  • 当我们调用时:

    public static void run() {
        Friends rachel = new Rachel();
        rachel.setEyeShadow(true);
        rachel.setLipstick(true);
        rachel.doSomething();
    }
    
    Rachel画了眼影涂了口红
    

但是用继承的话,就像我们之前说的,继承是一种耦合度极高的方式,我们试想这么几种情况:

  1. Friends类中我们定义了几个私有布尔变量包括clothes,eysShadow等,事实上,根据实际情况,我们会添加更多,如shoes等等,当我们要改变时,我们就必须修改已经闭合的代码,这违背了开闭原则
  2. 同时,当出现新变量的时候,我们还要修改FriendsdoSomething(),这显得更加糟糕
  3. 其实我们已经发现,Joey不用画eyeShadow,但是eyeShadow已经存在于Friends中了,并且因为Joey继承了FriendsJoey类中也会有eyeShadow,这显然不是我们希望看到的,这其实说明了继承的另外一个缺点,太过臃肿,灵活度不高
  4. 还有,我们发挥脑洞,如果Rachel心血来潮要涂两次口红怎么办?我们的Friends中只有一次口红!

继承的种种缺点就引出了下一步我解决方法:

装饰者模式

我们让Friends作为Component,让JoeyRachel等作为ConcreteComponent,让MakeUp作为Decorator,让EyeShadowLipStick等作为ConcreteDecorator,这就实现了装饰者模式

类图如下(PS,比较下上面装饰者模式的结构):

在这里插入图片描述

  • 此时的Friends已经成为了一个接口

    public interface Friends {
        void doSomething();
    }
    
  • Rachel, Joey等是Friends的具体组件

    public class Joey implements Friends {
        @Override
        public void  doSomething() {
            System.out.print("Joey");
        }
    }
    public class Rachel implements Friends {
        @Override
        public void doSomething() {
            System.out.print("Rachel");
        }
    }
    
  • MakeUp是Friends的装饰者

    public class MakeUp implements Friends {
        Friends friends;
        MakeUp(Friends friends) {
            this.friends = friends;
        }
        @Override
        public void doSomething() {
            friends.doSomething();
        }
    }
    
  • Clothes, EyeShadow等是装饰者的具体实现

    public class Clothes extends MakeUp {
        public Clothes(Friends friends) {
            super(friends);
        }
        @Override
        public void doSomething() {
            friends.doSomething();
            System.out.print("穿了衣服");
        }
    }
    public class EyeShadow extends MakeUp {
        public EyeShadow(Friends friends) {
            super(friends);
        }
        @Override
        public void doSomething() {
            friends.doSomething();
            System.out.print("画了眼影");
        }
    }
    public class Lipstick extends MakeUp {
        public Lipstick(Friends friends) {
            super(friends);
        }
        @Override
        public void doSomething() {
            super.doSomething();
            System.out.print("涂了口红");
        }
    }
    
  • 客户端调用为:

    public static void runDecorator() {
        Friends rachel = new Clothes(new Lipstick(new EyeShadow(new Rachel())));
        rachel.doSomething();
    }
    
    Rachel画了眼影涂了口红穿了衣服
    

相信大家都看出来了,所谓的装饰者模式,其实是一种递归

4. 应用

java.io

第一个首推的就是java.io,这里面的API多的要死,刚开始学习的时候把我折磨的不轻,要是早知道用的装饰者模式我也不会那么♂了555

输入流的UML(输出流/Reader/Writer类似)
在这里插入图片描述

我们可以看到:

  • Component: InputStream
  • ConcreteComponent:FileInputStream, StringBufferInputStream,StringBufferInputStream
  • Decorator: FilterInputStream
  • ConcreteDecorator: DataInputStream, BufferedInputStream, PushbcakInputStream, LineNumberInputStream

我们可以基于java的I/O自己写一个装饰器(作用是把文件中的大写变为小写):

public class LowerCaseInputStream extends FilterInputStream {
    LowerCaseInputStream(InputStream in) {
        super(in);
    }

    @Override
    public int read() throws IOException {
        int i = super.read();
        return i == -1 ? i : Character.toLowerCase((char)i);
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int i = super.read(b, off, len);
        for (int j = off; j < off + i; j++) {
            b[i] = (byte)Character.toLowerCase((char)b[i]);
        }
        return i;
    }
}

启动

public class InputTest {
    public static void main(String[] args) {
        int c ;
        try(InputStream is = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("test.txt")))){
            while ((c = is.read()) >= 0) {
                System.out.print((char) c);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
javax.swing

我们可以通过装饰模式动态给一些构件增加新的行为或改善其外观显示

如:JList本身不支持直接滚动,没有滚动条,要创建可以滚动的List,我们可以用如下代码

JScrolPane jsp = new JScrollPane(new JList());

5. 扩展

装饰者模式和工厂模式的组合

因为装饰者模式意味着我们要管理更多的类,所以通常情况下,我们一般会将装饰者模式和工厂模式组合起来用。通过工厂模式的控制翻转来几种管理装饰者模式中的装饰者

对装饰者模式的扩展

装饰模式所包含的 4 个角色不是任何时候都要存在的,在有些应用环境下模式是可以简化的,如以下两种情况:

  1. 如果只有一个具体构件而没有抽象构件时,可以让抽象装饰继承具体构件

img

  1. 如果只有一个具体装饰时,可以将抽象装饰和具体装饰合并

img

6. 优劣

装饰者模式的优点
  • 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式以提供比继承更多的灵活性
  • 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得 到功能更为强大的对象。
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的 具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
装饰者模式的缺点
  • 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度
  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继 承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐

7. 面试

面试官:谈一谈你对装饰者模式的理解

  1. 说出装饰者模式的结构
  2. 装饰者模式的优化,即和工厂模式结合起来
  3. 装饰者模式和适配器模式的区别
  4. 装饰者模式和继承的比较
  5. 装饰者模式的应用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值