设计模式(三)_装饰器模式

上篇学习了策略模式,现在回想下,什么是策略模式,好了。本篇主要介绍装饰器模式,just do it!

什么是装饰器模式

装饰器模式指的是动态的将责任附加到对象上。若要扩展功能,装饰器模式提供了比继承更弹性的替代方案。

如何使用装饰器模式

老王来到商场买衣服,需要买衣服,裤子,帽子......

public class Wang {

    public void show(){
        System.out.println("我穿上衣服,累计花费100元");
        System.out.println("我穿上裤子,累计花费250元");
        System.out.println("我穿上帽子,花费300元");
    }
}

如果老王每新买一件衣服,都要修改一下这个show方法,这就不符合开闭原则。我们可以用装饰器模式。针对这个过程,我画了老王买衣服的uml图

观察上图,观察模式主要有4个角色

  • 抽象构件角色(给出一个抽象接口Person,以规范准备接受附加责任的对象)
  • 装饰器父类
  • 具体的装饰器对象
  • 被装饰的对象

老王就是被装饰的对象,衣服帽子 就是装饰器
从图中可以看出,装饰器和被装饰的2个特点,也是装饰器模式的关键

  • 装饰器和被装饰对象实现同一个接口;
  • 装饰器中使用了被装饰的对象

代码实现

(1) 抽象构件角色

public interface Person {

    /**
     * 累积消费
     * @return
     */
    public double cost();

    public void show();
}

(2).老王,被装饰的对象


public class Wang implements Person {
    @Override
    public double cost() {
        return 0;
    }

    @Override
    public void show() {
        System.out.println("我是赤裸裸的老王");
    }
}

(3).装饰器父类,和被装饰对象实现同一个接口Person

public abstract class ClothesDecorator implements Person {
    protected  Person person;

    public ClothesDecorator(Person person){
        this.person = person;
    }
}

(4) 具体的装饰器类:衣服和帽子

public class Jacket extends ClothesDecorator {

    public Jacket(Person person){
        super(person);
    }
    @Override
    public double cost() {
        return person.cost()+100;
    }

    @Override
    public void show() {
        person.show();
        System.out.println("买了夹克,累计花了"+this.cost());
    }
}

public class Hat extends ClothesDecorator{
    public Hat(Person person) {
        super(person);
    }

    @Override
    public double cost() {
        return person.cost()+50;
    }

    @Override
    public void show() {
        person.show();
        System.out.println("买了帽子,累计花了"+this.cost());

    }
}

测试

        Person wang = new Wang();

        wang  = new Jacket(wang);

        wang  = new Hat(wang);

        //  wang  = new Hat(new Jacket(wang));

        wang.show();

        System.out.println("买单:王总共消费"+wang.cost());

输出结果

我是赤裸裸的老王
买了夹克,累计花了100.0
买了帽子,累计花了150.0
买单:王总共消费150.0

如果还要买鞋子,只要动态创建鞋子的装饰类,就可以了,不用修改已经写好的类。也贯彻了开闭原则。

使用装饰器模式的关键点

  • 装饰器和被装饰对象实现同一个接口,实际开发中也可能使用继承。
  • 装饰器中的方法可以调用被装饰对象提供的方法,以此实现功能累加的效果,例如,夹克装饰器和帽子装饰器中调用了 person.cost() + xx 实现累计消费金额的累加。

实际案例

装饰器模式在Java体系中的经典应用是Java I/O,下面讲解字节输入流InputStream
我简单画了UMl图,并用颜色进行了标注

定义中说:“装饰器提供了比继承更有弹性的解决方案”,为甚么这样说?

  • InputStream假设这里写了两个实现类,FileInputStream,ObjectInputStream分别表示文件字节输入流,对象字节输入流
  • 现在我要给这两个输入流加入一点缓冲功能以提高输入流效率,使用继承的方式,那么就写一个BufferedInputStream,继承FileInputStream,ObjectInputStream,给它们加功能
  • 现在我有另外一个需求,需要给这两个输入流加入一点网络功能,那么就写一个SocketInputStream,继承继承FileInputStream,ObjectInputStream,给它们加功能。

这样就导致2个问题
1)、因为我要给哪个类加功能就必须继承它,比如我要给FileInputStream,ObjectInputStream加上缓冲功能、网络功能就得扩展出2*2=4个类,更多的以此类推,这样势必导致类数量不断膨胀
2)、代码无法复用,给FileInputStream,ObjectInputStream加入缓冲功能,本身代码应该是一样的,现在却必须继承完毕后把一样的代码重写一遍,多此一举,代码修改的时候必须修改多个地方,可维护性很差

所以,这个的时候我们就想到了一种解决方案:

  • 在要扩展的类比如BufferedInputStream中持有一个InputStream的引用,在BufferedInputStream调用InputStream中的方法,这样扩展的代码就可以复用起来.
  • 将BufferedInputStream作为InputStream的子类,这样客户端只知道我用的是InputStream而不需要关心具体实现,可以在客户端不知情的情况下,扩展InputStream的功能,加上缓冲功能
    这就是装饰器模式简单的由来,一切都是为了解决实际问题而诞生

代码分析

1、InputStream是一个抽象构件角色

public abstract class InputStream implements Closeable {

    // MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to
    // use when skipping.
    private static final int MAX_SKIP_BUFFER_SIZE = 2048;
    ...
    }

2、被装饰对象ByteArrayInputStream

public
class ByteArrayInputStream extends InputStream {

    /**
     * An array of bytes that was provided
     * by the creator of the stream. Elements <code>buf[0]</code>
     * through <code>buf[count-1]</code> are the
     * only bytes that can ever be read from the
     * stream;  element <code>buf[pos]</code> is
     * the next byte to be read.
     */
    protected byte buf[];

    ...
    }

3.装饰器父类FilterInputStream

public
class FilterInputStream extends InputStream {
    /**
     * The input stream to be filtered.
     */
    protected volatile InputStream in;
    

4、具体装饰类BufferedInputStream

public
class BufferedInputStream extends FilterInputStream {

    private static int DEFAULT_BUFFER_SIZE = 8192;

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;

具体测试

public static void main(String[] args) throws Exception
{
        File file = new File("src/test.txt");
        InputStream in0 = new FileInputStream(file);
        InputStream in1 = new BufferedInputStream(new FileInputStream(file));
        InputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
}

我们这里实例化出了三个InputStream的实现类:

  • in0这个引用指向的是new出来的FileInputStream,这里简单构造出了一个文件字节输入流
  • in1这个引用指向的是new出来的BufferedInputStream,它给FileInputStream增加了缓冲功能,使得FileInputStream读取文件的内容保存在内存中,以提高读取的功能
  • in2这个引用指向的是new出来的DataInputStream,它也给FileInputStream增加了功能,因为它有DataInputStream和BufferedInputStream两个附加的功能

半透明装饰器模式与全透明装饰器模式

  • 对于半透明装饰器模式,装饰后的类未必有和抽象构件角色同样的接口方法,它可以有自己扩展的方法
  • 对于全透明装饰器模式,装饰后的类有着和抽象构件角色同样的接口方法

如下面的代码,就是全透明装饰器模式

        File file = new File("src/test.txt");
        InputStream in0 = new FileInputStream(file);
        InputStream in1 = new BufferedInputStream(new FileInputStream(file));

半透明装饰器模式则是

        FileInputStream in3 = new FileInputStream(file);
        BufferedInputStream in4 = new BufferedInputStream(new FileInputStream(file));
        DataInputStream in5 = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));

全透明装饰器模式是一种比较理想主义的想法,现实中不太可能出现。

比如BufferedInputStream吧,我把FileInputStream装饰为BufferedInputStream,难道BufferedInputStream就完全没有自己的行为?

装饰器模式的作用是动态给对象增加一些功能,而不需要修改对象本身。

练习

根据装饰器模式,我们自定义一个装饰器,将所有的字母转成空格

src/src/test.txt 文本内容

hello java78879

装饰类

public class CharacterInputStream extends FilterInputStream {

    public CharacterInputStream(InputStream in) {
        super(in);
    }

    @Override
    public int read() throws IOException {
        //ASCLL码对照,[97,122] 和 [65,90]是英文字母
        int c = super.read();
        if(c >= 97 && c <= 122 || c >= 65 && c <= 90){
            return 32; //32是空格
        }else{
            return c;
        }
    }

}

测试

        DataInputStream in = new DataInputStream(
                new CharacterInputStream(
                        new FileInputStream("src/test.txt")));

        String str;
        while((str =  in.readLine()) != null){
            System.out.println(str);
        }

测试结果

          78879

总结

优点:
  • 扩展功能的方式比较灵活;

    • 装饰器模式与继承关系的目的都是要扩展对象的功能,但是装饰器模式可以提供比继承更多的灵活性。装饰器模式允许系统动态决定贴上一个需要的装饰,或者除掉一个不需要的装饰。继承关系是不同,继承关系是静态的,它在系统运行前就决定了
  • 每一个装饰器相互独立,需要修改时不会互相影响。
    缺点:
  • 多层装饰比较复杂,就像 Java IO 流,对于初学者不友好。

代码下载 github

原文地址:https://www.cnblogs.com/zhenghengbin/p/9220007.html

时间: 2024-10-10 00:48:40

设计模式(三)_装饰器模式的相关文章

设计模式(三)——装饰器模式(Decorator Pattern)

发现太过于刻意按照计划来写博客,有点不实际,刚好最近在一个网课上复习AOP的知识,讲到了装饰器模式和代理模式,顺便复习总结一下. 首先了解一下装饰器模式,从名字里面可以看出来,装饰器模式就类似于房子装潢吧,比如刚买的毛坯房,只有一个没有门,直接就可以进去. 首先设计一个房子类Room,实现一个进入方法Access /// <summary> /// 抽象接口 用来进行约束 /// </summary> public interface IAccess { void Access()

20181122_C#中AOP初探_装饰器模式的AOP_Remoting实现AOP_Castle实现AOP

一.   什么是AOP: a)         AOP是面向切面编程; 就像oop一样, 它也是一种编程思想; i.    Oop思想→一切皆对象, 对象交互组成功能, 功能叠加组成模块, 模块叠加组成系统; 如果把一个个的类比喻成一个个砖头, 那么系统就是一个房子; 房子是由一块块砖头构成, 所以面向对象非常适合做大型系统; 但是面向对象的在应对系统扩展的时候, 就显得力不从心; 如果砖块需要发生变化, 则就会牵扯到很多地方; 因为面向对象是静态的, 内部就是强耦合的关系; 虽然设计模式可以解

设计模式篇——初探装饰器模式

文章目录 1.装饰器模式介绍 2.装饰器模式类图 3.装饰器模式Demo实现(一个小镇的拉面馆) 4.装饰器模式总结 装饰器模式介绍:装饰器模式可以在不修改任何底层代码的情况下,给对象赋予新的职责(程序运行时的扩展,动态的将责任附加到对象上).属于结构型设计模式. 类图: 我们来看下装饰器模式的类图: 一个简单的Demo(故乡小镇的一个面馆): 在故乡的一个小镇上面,有一家面馆,主营拉面.在这里你可以只点清汤面(SoupNoodle),也可以往里面加佐料,佐料有牛肉(Beef),鱼丸(FishB

【设计模式】之装饰器模式

为什么会有装饰模式? 装饰模式是为了解决继承强依赖性和出现大量子类不方便管理问题而出现的.   1. 概述 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活. 原理:增加一个修饰类包裹原来的类,包裹的方式一般是通过在将原来的对象作为修饰类的构造函数的参数.装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法.修饰类必须和原来的类有相同的接口. 2. 模式中的角色 2.1 抽象构建(Component):定义一个抽象接口,用以给这些对象动态

设计模式入门之装饰器模式Decorator

//装饰模式定义:动态地给一个对象加入一些额外的职责. //就添加功能来说.装饰模式比生成子类更为灵活 //这也提现了面向对象设计中的一条基本原则,即:尽量使用对象组合,而不是对象继承 //Component:组件对象的接口.能够给这些对象动态加入职责 //ConcreateComponet:详细的组件对象.实现组件对象接口.通常就是被装饰器装饰的对象.也就是能够给这个对象加入职责 //Decorator:全部装饰器的抽象父类,须要定义一个与组件接口一致的接口,并持有一个Component对象,

《设计模式》之装饰器模式

装饰器模式 装饰器模式中主要有两个角色: 装饰器(夹克,帽子) 被装饰的对象(老王) 装饰器和被装饰的对象有两个特点,也是装饰器模式的关键: 他们实现同一个接口 装饰器中使用了被装饰的对象 使用: /** * <p> *定义一个接口 * </p> * * @author aodeng-低调小熊猫 * @since 19-7-11 */ public interface Person { /** * 计算累计消费 * @return */ public Double cost();

设计模式三之装饰者模式1

装饰者模式,真是越来越生活化了,其实设计不就是源于生活高于生活吗?人类,一般总是把生活中观察的东西作为原料才能抽象出东西.装饰者模式,就是用各种装饰者对象来给被装饰者装饰,达到人们的多种多样的需求.不举咖啡的例子,实在没喝过几杯正式的咖啡.考虑炒饭,主要的原材料就是饭,但是变种非常多,近几天我就吃过和有深印象的,扬州炒饭.生牛肉炒饭.五香肉丁炒饭.XO酱海鲜炒饭.黄金炒饭.蛋炒饭等,像什么牛肉.葱花.肉丁.鸡蛋就是装饰者了,厨师用他们炒出(装饰出)各种饭.这种模式的优点是可以订制各种各样的需求,

设计模式整理_装饰者模式

装饰者模式将责任附加在对象上,若要扩展功能,装饰者提供了比继承更加有弹性的替代方案. 采用装饰者模式的时候,需要注意以下几点: 装饰者和被装饰者对象,有相同的超类型.(继承是为了有正确的类型,而不是继承行为,行为来自于被装饰者和基础的组件). 可以用一个或多个装饰者包装一个对象. 装饰者可以在所委托被装饰者的行为前或者行为后,增加自己的行为. 如果代码是依赖于具体的组件类型而不是针对抽象组件类型编程的时候,就会导致程序出现问题.因此应该具体类型具体分析. 装饰者模式体现了对于扩展开放和对于修改关

装饰器模式与代理模式比较

当有这样的业务需求的时候——要为写好的代码在目标代码之前或者之后添加部分操作时,此时最笨的方法就是直接在目标代码的前后加上我们需要的功能代码,但是这样违背了java封装的特性.更好一点的方法就是使用设计模式——代理模式,然而,装饰器模式也有同类的功能,那么着两种设计模式到底有什么区别呢?下面就分别来学习一下这两种设计模式. 装饰器模式类图如下: 该类图包括几个部分:一个接口(装饰器与需要被装饰的实体类都需要实现该接口,公用方法在该接口中定义),一个实现类,一个装饰器的接口,具体实现的装饰器. 在