一文搞定设计模式(二) - 装饰者模式

装饰者(Decorate)模式

1. 欢迎来到PeiTea

全国最大的的奶茶店——PeiTea,它们现在需要一个奶茶订单系统,经历了重重的考验,我最终拿到了这一笔收益不菲的订单,现在PeiTea一期要求必须马上上线一个订单功能,要求满足以下需求:

  1. 用户在购买奶茶时可以加入各种小料,例如:芋圆,珍珠,奶豆腐,芝士等;
  2. 系统可以根据不同的奶茶品种和小料,自动换算出对应的金额;
  3. 系统最好还可以支持动态打印小票,可以将茶底和小料打印出来方便制作人员制作。

PeiTea这边提供了他们之前使用的订单代码,类设计如下图:

  • 在这里插入图片描述

2. 问题分析

这样的类设计有什么问题?

  • 每一种奶茶都对应了一个唯一的对象,如果新推出了一个新品种的奶茶,就需要额外维护新的奶茶对象;
  • 如果某一样小料的价格有变动,那样需要修改所有涉及到这个小料的奶茶对象.

我盯着代码研究了一会,调整了一下类设计,如下图:

  • 在这里插入图片描述

***这样的类设计还是存在一些问题的,你知道是什么问题么?(答案在文末)***

3.认识装饰者模式

我们现在了解到,以上两种类设计方式没有办法完全解决类爆炸,设计死板,当有新小料加入时不适用等问题!

 现在,我决定采用一种不一样的设计方法,以奶茶为主体,然后在制作过程中,可以用各种小料来装饰她。比如说:

  • 我需要一杯绿茶为茶底的芋圆芝士奶茶;
  • 我们先创建一个绿茶茶底的奶茶对象
  • 然后我们用芋圆对象去装饰它
  • 最后我们用芝士对象去装饰它

 图示:

  • 在这里插入图片描述

当需要计算价格时,只需要调用最外层的装饰者即可得出当前商品的总价。

  • 我们先调用Cheese对象的getPrice()方法,Cheese对象会委托TaroBalls对象计算出他的价格,再加上自己的价格,然后将总价返回出去:
  • 同上原理,TaroBalls对象会继续委托GreenTea对象计算他的价格,再加上自己的价格,将总价返回给Cheese对象;
  • 一层一层的调用,直到调用到最底层为止。

4. 定义装饰者模式

  1. 我们先来整理一下我们目前所能知道的一切:
    • 装饰者和被装饰对象都具有相同的类型;
    • 我们可以用一个或者多个装饰者来装饰一个对象;
    • 因为装饰者和被装饰对象具有相同的类型,所以当我们在任意一个需要原始对象(被装饰者)的地方,都可以直接用装饰他的对象替换;
    • 对象可以在任何时候被装饰且不限量。
  2. 装饰者模式说明:
    • 装饰者模式动态的将责任附加到对象上,如要扩展功能,装饰者提供了比继承更有弹性的替代方案。

5.现在来开始构建我们的代码吧

5.1 写奶茶茶底的代码

public interface BaseDrink {

    /**
     * 打印饮品描述
     */
    String printDescription();

    /**
     * 获得当前饮品价格
     */
    int getPrice();

}

public class BlackTea implements BaseDrink {

    private final int price = 13; //定义红茶茶底的价格
    private final String description = "红茶底"; //定义红茶茶底的描述

    @Override
    public String printDescription() {
        return this.description;
    }

    @Override
    public int getPrice() {
        return this.price;
    }
}

public class GreenTea implements BaseDrink {

    private final int price = 12; //定义绿茶茶底的价格
    private final String description = "绿茶底"; //定义绿茶茶底的描述

    @Override
    public String printDescription() {
        return this.description;
    }

    @Override
    public int getPrice() {
        return this.price;
    }
}

5.2 写小料的代码

public class Cheese implements BaseDrink {
    private final int price = 4;//定义芝士的价格
    private final String description = "加芝士";//定义芝士的描述
    /**
     * 要让Cheese对象拥有一个可以引用的BaseDrink对象,步骤如下:
     * 1. 用一个实例变量记录奶茶,也就是被装饰者
     * 2. 让被装饰者记录到实例变量中,这里的实现方式为通过构造器记录
     */
    private BaseDrink baseDrink;

    public Cheese(BaseDrink baseDrink){
        this.baseDrink = baseDrink;
    }

    @Override
    public String printDescription() {
        return this.baseDrink.printDescription()+" "+this.description;
    }

    @Override
    public int getPrice() {
        return this.baseDrink.getPrice()+this.price;
    }
}

// 奶豆腐,芋圆,珍珠的代码和芝士的代码基本一致,这里就不重复写入

5.3 卖奶茶咯

public class MilkTeaStore {

    public static void main(String[] args) {
        //需要一个绿茶奶茶,加一份珍珠和一份芝士
        GreenTea greenTea = new GreenTea();
        Pearl pearl = new Pearl(greenTea);
        Cheese cheese = new Cheese(pearl);
        System.out.println("您点了"+cheese.printDescription()+",总价为:"+cheese.getPrice());
        //还需要一个红茶奶茶,加双份芋圆和一份奶豆腐
        BlackTea blackTea = new BlackTea();
        TaroBalls taroBalls1 = new TaroBalls(blackTea);
        TaroBalls taroBalls2 = new TaroBalls(taroBalls1);
        MilkTofu milkTofu = new MilkTofu(taroBalls2);
        System.out.println("您点了"+milkTofu.printDescription()+",总价为:"+milkTofu.getPrice());
    }
}

运行效果如下:

在这里插入图片描述

5.4 小问题

  1. 当我点了俩份芋圆时,打印出来的小票是加芋圆 加芋圆,如何通过装饰者模式,让他们变成加芋圆 x2呢?
  2. 奶茶都有小杯,中杯,大杯之分,三种分量的奶茶显然不能是同一种价格,所以如何通过装饰者模式来实现呢?

6. JDK中的装饰者模式

JDK中最出名的装饰者模式就在java.io包中,像BufferedInputStream和DataInputStream对象都扩展自FilterInputStream对象,而FileterInputStream对象时实现了InputStream接口的抽象的装饰对象。

6.1 编写属于自己的JAVA I/O对象

/**
 * @author iverson
 * @Package: decorate.test2
 * @description: 小写流,将所有大写字母转为小写字母
 * @date 2021/3/5  1:06
 */
public class LowerCaseInputStream extends FilterInputStream {

    public LowerCaseInputStream(InputStream inputStream){
        super(inputStream);
    }

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

    public static void main(String[] args) {
        int c ;
        try {
            InputStream in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("src/decorate/test2/test.txt")));
            while ((c = in.read())>=0){
                System.out.print((char)c);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

运行图示:

  • 在这里插入图片描述

7. 总结

  1. 装饰模式中的设计原则
    • 开闭原则:对类的扩展开发,对修改关闭;在不修改现有代码的情况下,使类具有新的行为。
  2. 装饰模式的要点
    • 装饰者和被装饰者类要具有相同的类型;
    • 装饰者可以在被装饰者的行为前面或者后面加上自己的行为,甚至可以直接替换掉被装饰者的整个行为,从而达到特定的目的;
    • 可以用无数个装饰者来装饰一个被装饰者;
    • 装饰者会导致设计中出现许多的小对象,容易让程序复杂化。’

8. 问题简答

问题1:

  • 调整价钱还是会较大幅度的调整我们的代码;
  • 一旦有了新的小料,就需要在每个类中加上新的方法,还需要修改getPrice()方法
  • 无法满足客户要两份同一个小料的需求;
  • 有的奶茶不需要一些小料还是会被强制实现一些不必要的方法。

问题2:

  • 我们可以写一个装饰类,用来解析最后返回的字符串(将printDescription()方法的返回值更改为ArrayList会让此方法更容易实现)

问题3:

  • 同上。

欢迎各位提出您的意见或建议,您的关注和点赞是我更新的最大的动力!谢谢各位~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值