headfirst设计模式(3)—装饰者模式

好久没写设计模式了,自从写了两篇之后,就放弃治疗了,主要还是工作太忙了啊(借口,都是借口),过完年以后一直填坑,填了好几个月,总算是稳定下来了,可以打打酱油了。

为什么又重新开始写设计模式呢?学习使我快乐啊(我装逼起来我自己都害怕),其实主要是最近填坑的时候看源代码有点晕,很多代码不知道他们为什么要那么写啊,好气啊

当时第二篇写完,其实就在准备第三篇了,但是,一直也没有写,看了好几遍,但是一直掌握不到精髓(其实现在也掌握不到),感觉挺模糊的,也就一直拖啊拖,拖延症晚期患者已经不用抢救了。。。

先来举个栗子

故事背景:星巴兹咖啡,由于快速扩展,他们现在的订单系统已经跟不上他们的饮料供应需求了,先看一下当前的设计

/**
 * 饮料超类
 * @author Skysea
 *
 */
public abstract class Beverage {

    protected String description;//描述

    public abstract double cost();//消费金额

    public Beverage(String description){
        this.description = description;
    }

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

下面是各种咖啡,这里就只展示一种:

/**
 * 首选咖啡
 * @author Skysea
 *
 */
public class HouseBlend extends Beverage{

    public HouseBlend(String description) {
        super(description);
    }

    @Override
    public double cost() {
        return 3.5;
    }

}

很简单的代码,咖啡继承饮料超类。

啥都不说了,先提需求:

1,购买咖啡时,要求可以在其中加入各种调料,例如:蒸奶,豆浆,摩卡...等等(产品每次给我们说的就是,具体的他们还没定好,能不能做成活的?就是随时可以扩展的那种。知道我为啥要来学设计模式了吗?会被玩死的,真的)

2,各种调料也会参与计价,而且每种调料的价格不一定相同

需求清楚了吧?不清楚?没关系,反正下周上线,不存在的,嘿嘿嘿

先来给出第一版实现(直接扩展超类):

/**
 * 饮料超类
 * @author Skysea
 *
 */
public abstract class Beverage {

    protected String description;//描述

    public abstract double cost();//消费金额

    private boolean milk;//牛奶

    private boolean soy;//豆浆

    private boolean mocha;//摩卡

    private boolean whip;//奶泡

    //省略get set方法...
}

不就是下周上线吗?不就是要扩展吗?要多少,就在超类里面加就好了啊,然后再对每一种饮料进行处理,像这样:

/**
 * 首选咖啡
 * @author Skysea
 *
 */
public class HouseBlend extends Beverage{

    public HouseBlend(String description) {
        super(description);
    }

    @Override
    public double cost() {

        double cost = 3.5;
        if(hasMilk()){
            cost += 0.5;
        }

        if(hasMocha()){
            cost += 0.6;
        }

        if(hasSoy()){
            cost += 0.3;
        }

        if(hasWhip()){
            cost += 0.4;
        }

        return cost;
    }

}

感觉每一种都要写这么多if好麻烦啊,不存在的,这种东西,抽出来嘛:

/**
 * 饮料超类
 * @author Skysea
 *
 */
public abstract class Beverage {

    /**
     * 调料消费
     * @return
     */
    protected double flavourCost(){
        double cost = 0.0;
        if(hasMilk()){
            cost += 0.5;
        }
        if(hasMocha()){
            cost += 0.6;
        }
        if(hasSoy()){
            cost += 0.3;
        }
        if(hasWhip()){
            cost += 0.4;
        }
        return cost;
    }
        //...
}

子类:

/**
 * 首选咖啡
 * @author Skysea
 *
 */
public class HouseBlend extends Beverage{

    public HouseBlend(String description) {
        super(description);
    }

    @Override
    public double cost() {
        return 3.5 + flavourCost();
    }

}

感觉也挺好的啊,每次要添加的时候,先去超类添加一个属性,然后在超类的 flavourCost()方法中,添加一段代码,以前的子类完全不用动,逻辑也是妥妥的,一切都是很OK的

但是,这样写至少有三处是不符合逻辑的:

1,所有的子类的cost方法都必须要加上一句 xxx + flavourCost(),不觉得写的次数太多了吗?一般一个方法写N次,那大部分最后会变成坑

2,超类的所有属性并不是每一个子类都能用上的

3,违背了开闭原则,每一次扩展虽然不用修改子类,但是却会去修改父类的属性,以及父类的flavourCost()方法

在现在的这个需求下,这些不合理都是体现不出来有什么问题的,但是,代码中不符合逻辑的东西早晚有一天会对整个模块的设计造成巨大的影响,两种情况除外:

1,你不干了(钱没给够或者我不开心毕竟程序员都是非常任性的),对模块的影响跟你没有半毛钱的关系

2,这个模块不扩展,不维护,或者扩展、维护还没有达到临界点(时间根据前期逻辑混乱程度成反比)

别问我怎么知道的,因为我TM还没走,所以就来学习来了,我擦,跑题了。。。

下面来聊聊刚学的正确姿势:

装饰者模式

先说超类 Beverage(不变,保持最最原始的样子):

/**
 * 饮料超类
 * @author Skysea
 *
 */
public abstract class Beverage {

    protected String description;//描述

    public abstract double cost();//消费金额

    public Beverage(String description){
        this.description = description;
    }

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

为什么不变?

要回答这个问题,首先要考虑,整个模块中,到底是什么东西一直变来变去?没错,就是调料啊。

一般变化的怎么处理?

抽离出来,把它和稳定的东西抽离出来,即保证了它的扩展性,又提高了代码的稳定性,遵循了开闭原则

所以为什么不变?1,它很稳定。2,它很稳定。没有3,我要逼死强迫症

接下来,贴一下它的子类代码,调整的地方:

    public HouseBlend(String description) {
        super(description);
    }

把 description放在构造函数内写死,为啥?你创建一个咖啡叫啥名字,难道还要客户端帮你想好?

让他去做也是可以的,但是有两个问题你要想清楚:

1,他知道你住哪里吗?

2,你走夜路吗?

卧槽,走远了走远了...

继续:

/**
 * 首选咖啡
 * @author Skysea
 *
 */
public class HouseBlend extends Beverage{

    public HouseBlend() {
        super("首选咖啡");
    }
    @Override
    public double cost() {
        return 3.5;
    }
}

为了保证最后的测试类可以正常的跑,我把其他的也写一些出来...

/**
 * 浓咖啡
 * @author Skysea
 *
 */
public class Espresso extends Beverage {

    public Espresso() {
        super("浓咖啡");
    }

    @Override
    public double cost() {
        return 2.5;
    }

}
/**
 * 焦炒咖啡
 * @author Skysea
 *
 */
public class DarkRoast extends Beverage{

    public DarkRoast() {
        super("焦炒咖啡");
    }

    @Override
    public double cost() {
        return 3.2;
    }
}

然后来到最重要的装饰超类CondimentDecorator (第一版):

/**
 * 调料装饰类
 * @author Skysea
 *
 */
public abstract class CondimentDecorator extends Beverage {

    protected Beverage beverage;

    public CondimentDecorator(String description) {
        super(description);
    }

    //重写cost方法
    @Override
    public String getDescription(){
        return this.beverage.getDescription() + ", " +this.description;
    }

    //直接实现cost方法
    public double cost(){
        return this.beverage.cost() + condimentCost();
    }

    protected abstract double condimentCost();//重新抽象一个调料的价格方法

}

装饰子类(Mocha ):

/**
 * 调料:摩卡
 * @author Skysea
 *
 */
public class Mocha extends CondimentDecorator {
    public Mocha() {
        super("摩卡");
    }
    @Override
    protected double condimentCost() {
        return 0.6;
    }
}

是不是感觉很简单?所有的逻辑都在装饰超类里面处理好了,感觉妥妥的,没什么不对,而且其他的子类实现起来也是非常的轻松

上面的装饰类是我自己的第一版实现,写完之后,我发现,这个东西,和我们最初想要的装饰类有很大的区别:

不灵活

现在我们来看看装饰者模式的定义:

装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。(百度百科)

想要应对越复杂的变化,那么就要给子类赋予越大的权限,让它接触越多的东西,只有这样才能做灵活,当然,灵活也是相对的,这个也是必须在实际项目中取舍

所以,上面的装饰类实现,根本无法满足当前需求所需要的灵活性,比如:使用摩卡的时候,所有的饮料满20减5块怎么玩?是不是感觉玩不动了?

问题在哪里呢?

    //直接实现cost方法
    public double cost(){
        return this.beverage.cost() + condimentCost();
    }

就是在这里,这个地方把cost方法定的太死板了,也不能说这样不对,只是,这样所有继承CondimentDecorator的其实只能算装饰者模式的特例,要实现这样的东西,就必须在CondimentDecorator与Beverage中间再抽象一层。

就像这样CondimentDecorator extends Decorator,Decorator extends Beverage,其他的调料继承 Decorator (咳,别听我乱BB)

讲讲正确的装饰类及实现吧:

/**
 * 调料装饰类
 * @author Skysea
 *
 */
public abstract class CondimentDecorator extends Beverage {

    protected Beverage beverage;

    public CondimentDecorator(Beverage beverage, String description){
        super(description);
        this.beverage = beverage;
    }

    //根据情况来觉得是不是要抽象出来让子类实现,原因和刚才抽象condimentCost()一样,分情况,没有什么是对不对的
    @Override
    public String getDescription(){
        return this.beverage.getDescription() + ", " +this.description;
    }

}

调料实现类:

/**
 * 调料:牛奶
 * @author Skysea
 *
 */
public class Milk extends CondimentDecorator{

    public Milk(Beverage beverage) {
        super(beverage, "牛奶");
    }

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

}
/**
 * 调料:摩卡
 * @author Skysea
 *
 */
public class Mocha extends CondimentDecorator {
    public Mocha(Beverage beverage) {
        super(beverage, "摩卡");
    }

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

}
/**
 * 调料:豆浆
 * @author Skysea
 *
 */
public class Soy extends CondimentDecorator{

    public Soy(Beverage beverage) {
        super(beverage, "豆浆");
    }

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

}

....

测试类:

/**
 * 装饰模式测试类
 * @author Skysea
 *
 */
public class Test {
    public static void main(String[] args) {
        //初始化浓咖啡
        Beverage beverage = new Espresso();//$2.5
        System.out.println(beverage.getDescription() + " $"+ beverage.cost());

        //初始化焦炒咖啡
        Beverage beverage2 = new DarkRoast();//$3.2
        //用调料来装饰它
        beverage2 = new Mocha(beverage2);//$0.6
        beverage2 = new Mocha(beverage2);//$0.6
        beverage2 = new Soy(beverage2);//$0.3 

        System.out.println(beverage2.getDescription() + " $"+ beverage2.cost());

        //初始化焦炒咖啡
        Beverage beverage3 = new HouseBlend();//3.5
        beverage3 = new Mocha(beverage3);//$0.6
        beverage3 = new Milk(beverage3);//$0.5
        beverage3 = new Soy(beverage3);//$0.3 

        System.out.println(beverage3.getDescription() + " $"+ beverage3.cost());
    }
}

运行结果:

为了证明我没有随便拿个假截图来骗你们,我特意把价格都标到测试类后面的

为啥后面那么长一串?这肯定不是我的锅啊,double的锅,推荐用BigDecimal来玩这个,当然也可以用long型,或者int都是可以的,实际项目也是这样

用什么主要看:

1,精度要求高不高

2,速度要求快不快

3,看产品会不会让你保留小数点2位和保留小数点3位换着来玩的情况(都是泪,不说了..)

时间: 2024-10-25 05:27:15

headfirst设计模式(3)—装饰者模式的相关文章

设计模式 2 —— 装饰者模式

设计模式目录: 设计模式 1 ——观察者模式 设计模式 2 —— 装饰者模式

Design Pattern 设计模式 Decorator 装饰者模式

1 设计一个基类 2 继承这个基类,并包含这个基类的一个对象 3 创建继承类的时候,初始化这个基类,因为是基类指针,所以可以是所有基类的衍生类对象,那么就可以叠加任意多个衍生类对象了. 关键是在衍生类中包含一个基类对象,然后有了继承和包含两重关系,可以使得一个类一个类对象叠加,达到装饰目的. 等于是创建了一个对象指针链表,一个一个对象发挥其相应的作用. 下面程序装饰一个桌子,同时包含释放内存的处理. 关键要知道基类的析构函数必须使用虚函数,为什么? 1 如果基类不是析构函数,那么如果衍生类中包含

java设计模式之 装饰器模式

适AT java设计模式之 装饰器模式 装饰器模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构. 这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,动态给一个对象添提供了额外的功能. 我们通过下面的实例来演示装饰器模式的用法.模拟一个人从想吃饭.找饭店.享受美食.结束吃饭的过程 代码展示: 首先创建一个被修饰的接口 Eat package deco

设计模式之装饰者模式(Decorator)

1.定义 装饰者模式动态地将责任附加到对象上.若要扩展功能,装饰者提供了比继承更有弹性的替代方案 2.类图 Component抽象构件:Component是一个接口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对象.在装饰者模式中,必然有一个最基本.最核心.最原始的接口或抽象类充当Component抽象构件. ConcreteComponent具体构件:ConcreteComponent是最核心.最原始.最基本的接口或抽象类的实现,你要装饰的就是它. Decorator装饰角色:一般是一

设计模式之装饰者模式(三)

欢迎大家的持续关注.上一次,我们结合第一篇推导出来的类图,到第二篇根据类图进行实际代码的编写,对装饰者模式有了一个整体的概念以及实战.不知道对你帮助如何呢?小编已经有门道了,看完接下来的一部分,你会恍然大悟,原来实际编码中你一直在用装饰者模式. 真实世界的装饰者:Java I/O 看到标题,是不是就很想往下看,到底是I/O中的什么呢,让你早已经拥有了装饰者模式的实践?就如书上给的描述,你第一次(还有第二次和第三次)看到这些API发出"哇"的惊叹时,放心,你不是唯一收到惊吓的人.下面,我

掌握设计模式之装饰者模式

![](http://ww4.sinaimg.cn/large/006tNc79ly1g4ztauvhzej30p00dw45p.jpg) ## 前言 当应用开发中,我们要为一个对象在原有功能上进行扩展增强时,往往采用继承的方式,而继承过多时就会使得功能类更加复杂,不利于维护,而设计模式中装饰者模式可以帮助我们更好对应这种场景,装饰者模式可以做到让对象能够动态地进行功能扩展,而不影响其他对象. 那究竟它是如何实现的呢,又如何实际应用呢,就让我们一起来学习下这个模式吧. ## 模式定义 装饰者模式

设计模式之装饰者模式

设计模式系列都是学习HeadFirst设计模式得出的学习心得,中间的例子也会采用书中的例子.这里有必要解释一下,在下面星巴克咖啡的例子中,有几种基本的咖啡,还有牛奶.豆浆等等可以向咖啡中添加,这里说明防止下面不懂. 今天我们来了解一下装饰者模式. 回想一下java的io包,各种stream排上倒海,初学者根本分不清楚到底怎么用,眼花缭乱.其实,它的实验遵循了装饰者设计模式.顾名思义,装饰者就可以简单的理解成用一个东西来装饰另一个东西.比如,你要做鱼吃,在你做好出国之后需要加入香菜.我们就可以简单

java_设计模式_装饰者模式_Decorator Pattern(2016-07-28)

装饰模式又名包装(Wrapper)模式.装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案. 装饰模式的结构 装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任.换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同.装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展. 装饰模式的类图如下: 在装饰模式中的角色有: ● 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象. ● 具体构件(ConcreteComponent)角色

python设计模式之装饰器模式

装饰器模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能. import time def log_calls(func): def wrapper(*args,**kwargs): now=time.time() print("Calling{0} with {1} and {2}

【设计模式】装饰者模式-明月装饰了你的窗子

装饰者模式 使用装饰者模式,可以动态的给一个对象添加一些额外的职责.这适用于,我们只希望给某个对象而不是整个类添加一些功能的场景.通过使用含有某个特定功能的类来"包裹"原始的类,提供给原始的类某些它本身不具备的特性.比如,我们有一杯"茉莉茶",现在加上一颗"柠檬",那我们就有了一杯"柠檬茉莉花茶"."柠檬"作为一个装饰者,提供了"茉莉茶"本身没有的清爽口感.当然,这也带来了一定的负担,你