当前位置: 首页 > news >正文

设计模式(四)—— 装饰者模式

目录

问题描述

版本(一)

版本(二)—— 装饰者模式 

1. 版本(一)存在的缺点

2. 装饰者模式

3. 装饰者模式实现咖啡订单系统

装饰者模式的应用——java I/O

 一个非常好的例子—— 编写自己的java I/O装饰者


问题描述

 

 

版本(一)

只使用继承

package HeadFirst.DecoratorPattern;

abstract public class Beverage {
    protected String description;
    //蒸奶
    protected boolean milk = false; //是否要
    protected int milkAmount = 0;  //要几份
    protected double milkCost = 2.0;  //一份多少钱
    //豆浆
    protected boolean soy = false;
    protected int soyAmount = 0;
    protected double soyCost = 1.0;
    //摩卡
    protected boolean mocha = false;
    protected int mochaAmount = 0;
    protected double mochaCost = 3.0;
    //奶泡
    protected boolean whip = false;
    protected int whipAmount = 0;
    protected double whipCost = 2.0;

    public String getDescription(){
        return this.description;
    }

    public double cost(){  //计算所有小料花的价钱
        double total = 0.0;
        if(milk) total = total + this.milkAmount*this.milkCost;
        if(soy) total = total + this.soyAmount*this.soyCost;
        if(mocha) total = total + this.mochaAmount*this.mochaCost;
        if(whip) total = total + this.whipAmount*this.whipCost;
        return total;
    }

    //-----------运行时设置要与不要---------
    public void setMilk(boolean milk) {
        this.milk = milk;
    }
    public void setSoy(boolean Soy){
        this.soy = soy;
    }
    public void setMocha(boolean mocha) {
        this.mocha = mocha;
    }
    public void setWhip(boolean whip) {
        this.whip = whip;
    }

    //------------运行时设置要几份---------
    public void setMilkAmount(int milkAmount) {
        this.milkAmount = milkAmount;
    }
    public void setSoyAmount(int soyAmount) {
        this.soyAmount = soyAmount;
    }
    public void setMochaAmount(int mochaAmount) {
        this.mochaAmount = mochaAmount;
    }
    public void setWhipAmount(int whipAmount) {
        this.whipAmount = whipAmount;
    }

}

DarkRoast.java为例 

package HeadFirst.DecoratorPattern;

public class DarkRoast extends Beverage{
    private double cost = 12.0;
    
    DarkRoast(){
        description = "DarkRoast";
    }
    public double cost(){ //重写父类的cost方法
        return super.cost() + this.cost;
    }
}

客户端点单测试:

package HeadFirst.DecoratorPattern;

public class Client {
    public static void main(String[] args) {
        //来了一位顾客,点了一倍DarkRoast,加1份蒸奶,加2份摩卡
        DarkRoast darkRoast = new DarkRoast();
        darkRoast.setMilk(true);
        darkRoast.setMilkAmount(1);
        darkRoast.setMocha(true);
        darkRoast.setMochaAmount(2);

        System.out.println("您需要支付:"+darkRoast.cost()+"元");
    }
}

 运行结果:

您需要支付:20.0元

其实这样已经挺好的了哈哈哈。

  • 如果要更改咖啡涨价或者小料涨价,我们只需要打开父类或者子类修改价钱就行了(尽管这样违背了开闭原则,但是开闭原则有时候该违背就要违背!)。
  • 如果要增加新的小料奥利奥Oreo,只需要打开父类,加入新的成员变量Oreo、OreoAmount、OreoCost,加入新的成员方法setOreoAmount()和setOreo(),然后在cost方法中给Oreo再加一行就行了(当然,也违背了开闭原则)
  • 如果要退出咖啡新品,更简单的了,直接增加一个咖啡子类就好啦,根本不违背开闭原则

非常好的问题:为什么Duck问题只使用继承不行,而咖啡点单问题勉强可以呢?

非常好的回答:和Duck问题不同,鸭子超类每增加一种行为(比如吃玉米粒),就要重新考虑所有子类是否具备这一种行为,然后所有子类都要重写这一行为。但是咖啡点单问题特殊在,是否具备某种小料,是顾客点单时才能确定的,程序员实现咖啡子类的时候是不需要去确定的,所以不存在重写的。

 

版本(二)—— 装饰者模式 

1. 版本(一)存在的缺点

 版本(一)的设计方式存在两个缺点,我认为第二点比较严重:

  • 违背了开闭原则,这个前面已经详细解释过了
  • 设计死板。
    • 版本(一)规定了每种咖啡的类中包含了所有的小料,哪怕这种小料根本不适合加到这种咖啡中(永远用不到)。比如咖啡店出了一款新品“红茶”,那么“奥利奥碎”这种小料就永远用不到。
    • 如果顾客只点了一杯DarkRoast,什么小料都不加,

开闭原则

软件的每个地方都采用开闭原则也没有必要,这会导致代码变得复杂且难以理解。

装饰者模式可以完美解决版本(一)存在的缺点。 

2. 装饰者模式

定义:动态地将装饰品附加到被装饰对象上

         装饰者模式完全遵循开闭原则

实现装饰者模式的关键在于被装饰者和装饰者继承共同的父类,尽管这并不符合现实中的逻辑(奶茶和珍珠怎么能是同一父类呢)。

应用场景

模板类图

  • 被装饰者和装饰者继承共同的父类
  • 具体的装饰类把Component的引用作为成员变量
  • 里面的超类可以是抽象类也可以是接口
  • 装饰者可以在被装饰者行为之前/后加上自己的行为,甚至将被装饰者行为整个取代掉

装饰者模式的优缺点

缺点:

  • 装饰者会导致设计过程中出现许多小对象,如果过度使用,程序会变得很复杂。new BBBBB( new BBBB ( new BBB ( new BB ( new B()))))

3. 装饰者模式实现咖啡订单系统

 下面这个图更容易理解一些,“装饰者一层层包裹上去”

 

 抽象类 Beverage.java

abstract public class Beverage {
    protected String description = "Unknown";
    protected double cost;
    
    public abstract String getDescription();
    public abstract double cost();
}

 抽象类 CondimentDecorator.java

abstract public class CondimentDecorator extends Beverage{
}

DarkRoast.java 

public class DarkRoast extends Beverage{
    DarkRoast(){
        description = "DarkRoast";
        cost = 0.99;
    }
    @Override
    public String getDescription() { //重写父类的getDescription方法
        return this.description;
    }
    @Override
    public double cost(){ //重写父类的cost方法
        return this.cost;
    }
}

 Milk.java

public class Milk extends CondimentDecorator{
    Beverage beverage; // !!!

    Milk(Beverage beverage){  //实现Milk对咖啡的包裹
        this.beverage = beverage;
        this.cost = 0.1;//蒸奶0.1一份
    }
    @Override
    public String getDescription() {
        return beverage.getDescription()+",Milk";
    }
    @Override
    public double cost() {
        return beverage.cost() + this.cost;
    }
}

Mocha.java 

public class Mocha extends CondimentDecorator{
    Beverage beverage;

    Mocha(Beverage beverage){
        this.beverage = beverage;
        this.cost = 0.2;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription()+",Mocha";
    }

    @Override
    public double cost() {
        return beverage.cost()+this.cost;
    }
}

客户端测试:

public class Client {
    public static void main(String[] args) {
        Beverage darkRoast = new DarkRoast(); //一杯DarkRoast
        darkRoast = new Milk(darkRoast);  //加一份Milk
        darkRoast = new Mocha(darkRoast); //加一份Mocha
        System.out.println(darkRoast.getDescription());
        System.out.println("您需要支付:"+darkRoast.cost()+"元");
    }
}

 输出:

DarkRoast,Milk,Mocha
您需要支付:1.29元

非常好的问题:有不变的咖啡,变化的小料,为什么不能封装变化(写一个小料interface,然后小料各自实现interface成为类,以组合/依赖的方式和咖啡类组合),使用策略模式呢?

非常好的回答:因为顾客不来的话,程序员根本不知道什么小料该和什么咖啡组合啊!和策略模式不同的是,装饰者模式适用于没有稳定对象的场景,即只有在运行时才知道对象是什么样子。就拿例题来说,如果一个顾客要DarkRoust里加1份摩卡,2份蒸奶,3份豆浆,4份奶泡;另一个顾客要HouseBlend加10份蒸奶和2份奶泡,需求成千上万,谁又能全部想到呢?就算穷举一遍,哪个系统能把它们都封装成类呢?所以符合顾客需求的类/对象只能在运行时生成,也就是在【前台点单】时生成,这就是装饰者模式和策略模式的最大区别。

非常好的问题:这种场景是不是用工厂模式、生成器模式、桥接模式也能实现

回答:有待学习

 

装饰者模式的应用——java I/O

以InputStream为例,看看javaI/O是如何利用装饰者模式的

其实,我们之前学习java I/O的时候,“处理流包裹在节点流上”就是装饰者去包裹被装饰者。

 一个非常好的例子—— 编写自己的java I/O装饰者

装饰者类LowerCaseInputStream.java 

import java.io.*;

public class LowerCaseInputStream extends FilterInputStream {
    /**
     * Creates a <code>FilterInputStream</code>
     * by assigning the  argument <code>in</code>
     * to the field <code>this.in</code> so as
     * to remember it for later use.
     *
     * @param in the underlying input stream, or <code>null</code> if
     *           this instance is to be created without an underlying stream.
     */
    protected LowerCaseInputStream(InputStream in) {  //实现LowerCaseInputStream对in的包裹
        super(in);
    }

    @Override
    public int read() throws IOException {
        int c = super.read();
        if(c == -1){
            return -1;
        }else{
            return Character.toLowerCase((char)c);  //变小写
        }
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        //字节怎么变成字符???
        return -1;
    }

    @Override
    public int read(byte[] b) throws IOException {
        //字节怎么变成字符???
        return -1;
    }
}

 测试

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class Client {
    public static void main(String[] args) {
        LowerCaseInputStream lowerCaseInputStream = null; //装饰
        try {
            //1)
            FileInputStream fileInputStream = new FileInputStream(new File("E:\\Java Project\\helloworld\\ioStream\\UpperCase.txt"));
            lowerCaseInputStream = new LowerCaseInputStream(fileInputStream);

            //2)读
            int data;
            while((data = lowerCaseInputStream.read())!=-1){
                System.out.print((char)data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(lowerCaseInputStream != null)
                lowerCaseInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 其中,我们的输入文件UpperCase.txt里的内容是:

STUPID TEACHER!

 程序输出:

相关文章:

  • 我的网站织梦/网站查询域名ip
  • 济南网站建设 力选聚搜网络/百度点击器下载
  • coding wordpress博客/百度云搜索引擎
  • 如何在网站源码做授权/企业查询app
  • 信用网站一体化建设/搜狗优化排名
  • 建网站 网站内容怎么做/如何自己制作网站
  • [架构之路-48]:目标系统 - 系统软件 - Linux下的网络通信-4-快速数据平面开发套件DPDK-工作原理
  • 创意电子学00课:需要准备的材料汇总
  • 什么是轨到轨?这种运放和普通运放比有什么特点和优点?
  • 面试官:小伙子,说说C/C++是如何进行内存管理的?我:……
  • STL容器 —— map和set的模拟实现
  • 软考知识点---06文件管理与作业管理---01文件管理
  • API网关基础认知
  • 腾讯云Ubuntu18.04配置深度学习环境
  • 反常积分敛散性的比较判别法专题(及常用反常积分)
  • pythond大屏可视化
  • 知识图谱-生物信息学-医学论文(Chip-2022)-BCKG-基于临床指南的中国乳腺癌知识图谱的构建与应用
  • 力扣 每日一题 902. 最大为 N 的数字组合【难度:困难,rating: 1989】(数学 / 数位dp)