zoukankan      html  css  js  c++  java
  • 设计模式:装饰者模式介绍及代码示例 && JDK里关于装饰者模式的应用


    0、背景


    来看一个项目需求:咖啡订购项目。

    咖啡种类有很多:美式、摩卡、意大利浓咖啡;
    咖啡加料:牛奶、豆浆、可可。

    要求是,扩展新的咖啡种类的时候,能够方便维护,不同种类的咖啡需要快速计算多少钱,客户单点咖啡,也可以咖啡+料。

    最差方案

    直接想,就是一个咖啡基类,然后所有的单品、所有的组合咖啡都去继承这个基类,每个类都有自己的对应价格。

    问题:那么多种咖啡和料的组合,都相当于是售卖的咖啡的一个子类,全都去实现基本就是一个全排列,显然又会类爆炸。并且,扩展起来,多一个调料,都要把所有咖啡种类算上重新组合一次。

    改进方案

    将调料内置到咖啡基类里,这样不会造成数量过多,当单品咖啡继承咖啡基类的时候,就都拥有了这些调料,同时,点没有点调料,要提供相应的方法,来计算是不是加了这个调料。

    问题:这样的方式虽然改进了类爆炸的问题,但是属性内置导致了耦合性很强,如果删了一个调料呢?加了一个调料呢?每一个类都要改,维护量很大。


    一、装饰者模式


    装饰者模式动态的将新功能附加到对象上,在对象功能扩展方面,比继承更有弹性。

    具体实现起来是这样的,如下类图所示:

    可以看到,在装饰者里面拥有一个 Component 对象,这是核心的部分。

    也就是不像我们想的,给单品咖啡里加调料,而是反向思维,把单品咖啡拿到调料里来,决定对他的操作。

    如果 ConcreteComponent 很多的话,甚至还可以再增加缓冲层。

    用装饰者模式来解决上面的咖啡订单问题,类图设计如下,考虑到具体单品咖啡的种类,增加了一个缓冲层,最基本的抽象类叫 Drink

    其中 Drink 就相当于是前面的 Component,Coffee 是缓冲层,下面的不同 Coffee 就是上面的ConcreteConponent。

    费用的计算方式一改正常思路的咖啡中,而是在调料中,因为 cost 在 Drink 类里也有,所以到最终的计算,其实是带上之前的 cost 结果,如果多种装饰者进行装饰,比如一个coffee加了很多料,那么其实是 递归的思路计算 最后的 cost 。

    这样的话,增加一个单品咖啡,或者增加调料,都不用改变其他地方。

    代码如下,类比较多,但是每个都比较简单:

    /*
        抽象类Drink,相当于Component;
        getset方法提供给子类去设置饮品或调料的信息
        但是:cost方法留给调料部分实现
    */
    public abstract class Drink {
        public String description;
        private float price = 0.0f;
        //价格方法
        public abstract float cost();
        public float getPrice() {
            return price;
        }
        public void setPrice(float price) {
            this.price = price;
        }
        public String getDescription() {
            return description +":"+ price;
        }
        public void setDescription(String description) {
            this.description = description;
        }
    }
    

    接着就是Coffe缓冲层以及下面的实现类,相当于ConcreteComponent:

    public class Coffee extends Drink{
        @Override
        public float cost() {
            return super.getPrice();
        }
    }
    
    public class MochaCoffee extends Coffee{
        public MochaCoffee() {
            setDescription(" 摩卡咖啡 ");
            setPrice(7.0f);
        }
    }
    
    public class USCoffee extends Coffee{
        public USCoffee() {
            setDescription(" 美式咖啡 ");
            setPrice(5.0f);
        }
    }
    
    public class ItalianCoffee extends Coffee {
        public ItalianCoffee(){
            setDescription(" 意大利咖啡 ");
            setPrice(6.0f);
        }
    }
    

    然后是装饰核心,Decorator,和Drink是继承+组合的关系:

    /*
        Decorator,反客为主去拿已经有price的drink,并加上佐料
        加佐料的时候是拿去了Drink对象,但是也是给Drink进行
    */
    public class Decorator extends Drink{
        private Drink drink;
        //提供一个构造器
        public Decorator(Drink drink){
            this.drink = drink;
        }
        @Override
        public float cost() {
            //计算成本,拿到佐料自己的价格+本来一杯Drink的价格
            //这里注意调用的是drink.cost不是drink.getPrice,因为cost才是子类实现的,Drink类的getPrice方法默认是返回0
            return super.getPrice() + drink.cost();
        }
    
        @Override
        public String getDescription() {
            //自己的信息+被装饰者coffee的信息
            return description + " " + getPrice() + " &&" + drink.getDescription();
        }
    }
    

    以及Decorator的实现类,也就是ConcreteDecorator:

    public class Milk extends Decorator{
        public Milk(Drink drink) {
            super(drink);
            setDescription(" 牛奶:");
            setPrice(1.0f);
        }
    }
    
    public class Coco extends Decorator{
        public Coco(Drink drink) {
            super(drink);
            setDescription(" 可可:");
            setPrice(2.0f);//调味品价格
        }
    }
    
    public class Sugar extends Decorator {
        public Sugar(Drink drink) {
            super(drink);
            setDescription(" 糖:");
            setPrice(0.5f);
        }
    }
    

    注意,对于具体的Decorator,这里就体现了逆向思维,拿到的 drink 对象,调用父类构造器得到了一个drink,然后 set 和 get 方法设置调料自己的price和description,父类的方法 cost 就会计算价钱综合。

    那里面的 super.getPrice() + drink.cost() 中的 cost(),就是一个递归的过程。

    最后我们来写一个客户端测试:

    public class Client {
        public static void main(String[] args) {
            //1.点一个咖啡,用Drink接受,因为还没有完成装饰
            Drink usCoffee = new USCoffee();
            System.out.println("费用:"+usCoffee.cost()+" 饮品信息:"+usCoffee.getDescription());
            //2.加料
            usCoffee = new Milk(usCoffee);
            System.out.println("加奶后:"+usCoffee.cost()+" 饮品信息:"+usCoffee.getDescription());
            //3.再加可可
            usCoffee = new Coco(usCoffee);
            System.out.println("加奶和巧克力后:"+usCoffee.cost()+" 饮品信息:"+usCoffee.getDescription());
        }
    }
    

    可以看到,调用的时候,加佐料只要在原来的 drink 对象的基础上,重新构造,将原来的 drink 放进去包装(装饰),最后就达到了效果。

    并且,如果要扩展一个类型的 coffee 或者一个调料,只用增加自己一个类就可以。


    二、装饰者模式在 JDK 里的应用


    java 的 IO 结构,FilterInputStream 就是一个装饰者。

    2.1 这里面 InputStream 就相当于 Drink,也就是 Component 部分;
    2.2 FileInputStream、StringBufferInputStream、ByteArrayInputStream 就相当于是单品咖啡,也就是ConcreteComponent,是 InputStream 的子类;
    2.3 而 FilterInputStream 就相当于 Decorator,继承 InputStream 的同时又组合了InputStream;

    2.4 BufferInputStream、DataInputStream、LineNumberInputStream 相当于具体的调料,是FilterInputStream的子类。

    我们一般使用的时候:

    DataInputStream dataInputStream = new DataInputStream(new FileInputStream("D://test.txt"));
    

    或者:

    FileInputStream fi = new FileInputStream("D:\test.txt");
    DataInputStream dataInputStream = new DataInputStream(fi);
    //具体操作
    

    这里面的 fi 就相当于单品咖啡, datainputStream 就是给他加了佐料。

    更贴合上面咖啡的写法,声明的时候用 InputStream 接他,就可以:

    InputStream fi = new FileInputStream("D:\test.txt");
    fi = new DataInputStream(fi);
    //具体操作
    

    感觉真是完全一样呢。

  • 相关阅读:
    CSS_2
    二柱子与他的计算题
    第一章 类与对象
    深入理解计算机系统之一--整数运算
    指针数组的应用
    对象个数以及构造函数次序
    范式介绍
    内连接与外连接
    TCP三次握手四次分手
    进程间的通信方式
  • 原文地址:https://www.cnblogs.com/lifegoeson/p/13512381.html
Copyright © 2011-2022 走看看