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

目录

问题描述

版本(一)

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

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!

 程序输出:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要开发一个咖啡店点单系统,你需要先了解咖啡店点单系统的需求和功能。以下是一些可能需要实现的功能: 1. 菜单管理:咖啡店可能有不同种类的饮品和食物,需要有一个菜单管理系统来管理它们。这个系统应该允许添加、更新和删除菜单项,以及根据菜单项属性(如价格、类型等)进行搜索和排序。 2. 订单管理:顾客应该能够在线浏览菜单并创建订单。订单管理系统应该允许顾客添加、删除和更改订单中的菜单项,以及计算订单总额。店员需要能够查看和管理订单,包括确认订单、取消订单以及标记订单为已完成。 3. 支付管理:咖啡店需要有一个支付管理系统来处理订单支付。这个系统应该允许顾客选择不同的支付方式(如现金、信用卡、移动支付等),并提供相应的接口进行支付。店员也需要能够查看支付状态和处理支付问题。 4. 员工管理:咖啡店需要有一个员工管理系统来管理员工信息和权限。这个系统应该允许添加、删除和更新员工信息,以及控制员工访问系统的权限。 以上是咖啡店点单系统的一些基本功能,你可以设计一个基于 Eclipse 平台的 Java 应用程序来实现这些功能。你需要使用 Java 编程语言和相关库来实现这些功能,如 Swing GUI 库、JDBC 数据库连接库等。同时,你还需要设计数据库模式和表结构来存储和管理菜单、订单和员工信息。 在实现过程中,你可以按照以下步骤进行: 1. 设计数据库模式和表结构。 2. 编写 Java 代码来实现各个功能模块,如菜单管理、订单管理、支付管理和员工管理。 3. 使用 Swing GUI 库来设计用户界面,包括菜单浏览、订单管理、支付页面和员工信息页面。 4. 使用 JDBC 数据库连接库来实现与数据库的交互。 5. 测试和调试应用程序,确保各个功能模块能够正常工作。 最后,你可以将应用程序打包成可执行 JAR 文件,以便在不同操作系统上运行。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值