ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

结构型模式——装饰者模式

2021-07-13 11:33:55  阅读:210  来源: 互联网

标签:BigDecimal 模式 装饰 饮料 调料 beverage public 结构型


一、介绍

装饰者模式是一种结构型模式,通过这种模式,可以动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

二、案例分析

还是以传说中的星巴克咖啡馆咖啡订单问题说事儿:

1)、不采用装饰者模式

  • 1、现在呢,有一个咖啡馆,它有一套自己的订单系统,当顾客来咖啡馆的时候,可以通过订单系统来点自己想要的咖啡。他们原先的设计是这样子的:
    在这里插入图片描述
  • 2、此时、咖啡馆为了吸引更多的顾客,需要在订单系统中允许顾客选择加入不同调料的咖啡,例如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分。下面是他们的第一次尝试:
    在这里插入图片描述
  • 3、很显然之前的设计肯定是不行的,简直分分钟把人逼疯的节奏,有木有!这时,有个人提出了新的方案,利用实例变量和继承,来追踪这些调料。具体为:先从Beverage基类下手,加上实例变量代表是否加上调料(牛奶、豆浆、摩卡、奶泡……)
    在这里插入图片描述
    在这里插入图片描述
    以上这种设计虽然满足了现在的需求,但是我们想一下,如果出现下面情况该怎么办呢?
    ①、调料价钱的改变会使我们更改现有代码。
    ②、一旦出现新的调料,我们就需要加上新的方法,并改变超类中的cost()方法。
    ③、以后可能会开发出新饮料。对这些饮料而言(例如:冰茶),某些调料可能并不适合,但是在这个设计方式中,Tea(茶)子类仍将继承那些不适合的方法,例如:hasWhip()(加奶泡)。
    ④、万一顾客想要双倍摩卡咖啡,怎么办?
    很明显,上面的设计并不能够从根本上解决我们所碰到的问题。并且这种设计违反了开放关闭原则的,所以才有了现在的装饰者,装饰者可以非常完美的解决以上的所有问题。

2)、采用装饰者模式

  • 1、我们要以饮料为主体,然后在运行时以调料来“装饰”(decorate)饮料。比方说,如果顾客想要摩卡和奶泡深焙咖啡,那么,要做的是:
    ①、拿一个深焙咖啡(DarkRoast)对象
    ②、以摩卡(Mocha)对象装饰它
    ③、以奶泡(Whip)对象装饰它
    ④、调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。
    但是如何“装饰”一个对象,而“委托”又要如何与此搭配使用呢?那就是把装饰者对象当成“包装者”。让我们看看这是如何工作的:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 2、我们将我们所知道的写下来:
    ①、装饰者和被装饰对象有相同的超类型。
    ②、你可以用一个或多个装饰者包装一个对象。
    ③、既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
    ④、装饰者可以在所委托被装饰者的行为之前与 / 或之后,加上自己的行为,以达到特定的目的。
    ⑤、对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
    下面,我们来看一下装饰者模式的类图:
    在这里插入图片描述
    利用装饰者模式来实现我们的订单系统的类图:
    在这里插入图片描述

三、案例实现

  • 饮料抽象类
import java.math.BigDecimal;

/**
 * 饮料抽象类
 *
 * @author zhangxs
 **/
public abstract class Beverage {
    String description = "Unknown Beverage";

    public String getDescription() {
        return description;
    }

    /**
     * cost方法是用来返回饮料的价钱(需在具体类中实现)
     */
    public abstract BigDecimal cost();
}
  • 深焙咖啡类
import java.math.BigDecimal;

/**
 * 深焙咖啡类(一种具体的饮料)
 *
 * @author zhangxs
 **/
public class DarkRoast extends Beverage {
    /**
     * 说明他是DarkRoast饮料
     */
    public DarkRoast() {
        description = "DarkRoast";
    }

    /**
     * 实现cost方法,用来返回DarkRoast(深焙咖啡)的价格
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("3.00");
    }
}
  • 低咖啡因咖啡类
import java.math.BigDecimal;

/**
 * 低咖啡因咖啡类(一种具体的饮料)
 *
 * @author zhangxs
 **/
public class Decaf extends Beverage {
    /**
     * 说明他是Decaf饮料
     */
    public Decaf() {
        description = "Decaf";
    }

    /**
     * 实现cost方法,用来返回Decaf(低咖啡因咖啡)的价格
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("4.00");
    }
}
  • 浓缩咖啡类
import java.math.BigDecimal;

/**
 * 浓缩咖啡类(一种具体饮料)
 *
 * @author zhangxs
 **/
public class Espresso extends Beverage {
    /**
     * 说明他是Espresso饮料
     */
    public Espresso() {
        description = "Espresso";
    }

    /**
     * 实现cost方法,用来返回Espresso(浓缩咖啡)的价格
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("2.00");
    }
}
  • 调料装饰着抽象类
/**
 * 调料装饰着抽象类(继承自饮料抽象类)
 *
 * @author zhangxs
 **/
public abstract class CondimentDecorator extends Beverage {
    /**
     * 所有的调料装饰者都必须重新实现getDescription()方法
     * 这样才能够用递归的方式来得到所选饮料的整体描述
     */
    @Override
    public abstract String getDescription();
}
  • 摩卡调料类
import java.math.BigDecimal;

/**
 * 摩卡调料类(继承自CondimentDecorator)
 *
 * @author zhangxs
 **/
public class Mocha extends CondimentDecorator {
    /**
     * 用一个实例变量记录饮料,也就是被装饰者
     */
    Beverage beverage;

    /**
     * 构造器初始化饮料变量
     */
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    /**
     * 在原来饮料的基础上添加上Mocha描述(原来的饮料加入Mocha调料,被Mocha调料装饰)
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",Mocha";
    }

    /**
     * 在原来饮料的基础上加上Mocha的价格(原来的饮料加入Mocha调料,被Mocha调料装饰)
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("0.2").add(beverage.cost());
    }
}
  • 豆浆调料类
import java.math.BigDecimal;

/**
 * 豆浆调料类(继承自CondimentDecorator)
 *
 * @author zhangxs
 **/
public class Soy extends CondimentDecorator {
    /**
     * 用一个实例变量记录饮料,也就是被装饰者
     */
    Beverage beverage;

    /**
     * 构造器初始化饮料变量
     */
    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }

    /**
     * 在原来饮料的基础上添加上Soy描述(原来的饮料加入Soy调料,被Soy调料装饰)
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",Soy";
    }

    /**
     * 在原来饮料的基础上加上Soy的价格(原来的饮料加入Soy调料,被Soy调料装饰)
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("0.3").add(beverage.cost());
    }
}
  • 奶泡调料类
import java.math.BigDecimal;

/**
 * 奶泡调料类(继承自CondimentDecorator)
 *
 * @author zhangxs
 **/
public class Whip extends CondimentDecorator {
    /**
     * 用一个实例变量记录饮料,也就是被装饰者
     */
    Beverage beverage;

    /**
     * 构造器初始化饮料变量
     */
    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }

    /**
     * 在原来饮料的基础上添加上Whip描述(原来的饮料加入Whip调料,被Whip调料装饰)
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",Whip";
    }

    /**
     * 在原来饮料的基础上加上Whip的价格(原来的饮料加入Whip调料,被Whip调料装饰)
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("0.4").add(beverage.cost());
    }
}
  • 咖啡馆(模拟顾客下单)
/**
 * 咖啡馆(供应咖啡)
 *
 * @author zhangxs
 **/
public class StarbuzzCoffee {
    public static void main(String[] args) {
        // 订一杯Espresso(2.00),不需要调料,打印出它的描述与价钱。
        Beverage beverage = new Espresso();
        System.out.println("Description: " + beverage.getDescription() + " $" + beverage.cost());

        // 制造出一个DarkRoast(3.00)对象,用Mocha(0.2)装饰它,用第二个Mocha(0.2)装饰它,用Whip(0.4)装饰它,打印出它的描述与价钱。
        Beverage beverage2 = new DarkRoast();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println("Description: " + beverage2.getDescription() + " $" + beverage2.cost());

        // 再来一杯调料为豆浆(Soy 0.3)、摩卡(Mocha 0.2)、奶泡(Whip 0.4)的Decaf(低咖啡因咖啡 4.00),打印出它的描述与价钱。
        Beverage beverage3 = new Decaf();
        beverage3 = new Soy(beverage3);
        beverage3 = new Mocha(beverage3);
        beverage3 = new Whip(beverage3);
        System.out.println("Description: " + beverage3.getDescription() + " $" + beverage3.cost());
    }
}
  • 结果
Description: Espresso $2.00
Description: DarkRoast,Mocha,Mocha,Whip $3.80
Description: Decaf,Soy,Mocha,Whip $4.90

四、总结

  • 设计原则:多用组合,少用继承,开放扩展,关闭修改。
  • 优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
  • 缺点:多层装饰比较复杂。
  • 适用场景:扩展一个类的功能、动态增加功能、动态撤销。
  • JDK中实际应用:在java中I/O便使用了装饰者模式,如下图:
    在这里插入图片描述

标签:BigDecimal,模式,装饰,饮料,调料,beverage,public,结构型
来源: https://blog.csdn.net/weixin_38264394/article/details/118193168

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有