美文网首页设计模式技术干货程序员
设计模式学习(三)——装饰者模式

设计模式学习(三)——装饰者模式

作者: 活成理想中的样子 | 来源:发表于2017-06-18 13:00 被阅读120次

一.需求

小王的便利店里卖柴鸡蛋,而且很受欢迎,一下子让小王夫妇打开了思路:原来只要细心观察小区居民的需求,可以卖的东西还有很多。小王的媳妇儿小芳琢磨着在便利店门口卖煎饼果子!小区里住着不少上班族,他们每天早上急匆匆去上班,一个热乎的煎饼果子又营养,又美味,一定会受到大家喜欢的!

说干就干,一周后,小芳的煎饼摊就正式开始营业了。果然,不一会就排起来长队……一份标准的煎饼果子是5元,放一个鸡蛋,一块薄脆,再撒上葱花,香菜,小芳又别处心裁,加了少许榨菜,咬一口,薄脆的酥脆,煎饼的松软,再加上榨菜的清爽……绝了!

民以食为天,小小的煎饼果子也能吃出不少名堂。有的想多加一个鸡蛋,有的想要两块薄脆,有的想多加一根火腿……不一而足,加一个鸡蛋要多加5角,加一块薄脆5角,加一个火腿1元,大家口味各异,把小芳忙的不可开交。算账容易出错不说,还大大影响了效率。

小王不愧是程序员出身,看到老婆的难处,当即决定给老婆写一个软件用来给煎饼果子收款。

二.初步尝试

尝试一

在仔细分析了面临的问题之后,小王觉得:问题主要在于很多顾客想要一些“豪华版”的煎饼果子,例如加一个鸡蛋,加一个火腿,加一块薄脆,在标准的煎饼果子之外,只要把常见的搭配设计好就可以了。小王的设计如下:

// 标准煎饼果子
class ChineseHamburger {
    private static final float price = 5.0f;

    public float cost() {
        return price;
    }
}

// 标准煎饼果子加一份薄脆
class CHAddCrisp extends  ChineseHamburger {
    private static final float price = 0.5f;

    public float cost() {
        return super.cost() + price;
    }
}

// 标准煎饼果子加一个鸡蛋
class CHAddEgg extends  ChineseHamburger {
    private static final float price = 1.0f;

    public float cost() {
        return super.cost() + price;
    }
}

// 标准煎饼果子加一根火腿
class CHAddHam extends  ChineseHamburger {
    private static final float price = 1.0f;

    public float cost() {
        return super.cost() + price;
    }
}
public class ChineseHamburgerAdmin {
    public static void main(String[] args) {
        ChineseHamburger ch = new ChineseHamburger();
        System.out.println("标准煎饼果子售价" + ch.cost());

        ChineseHamburger chAddEgg = new CHAddEgg();
        System.out.println("标准煎饼果子加鸡蛋售价" + chAddEgg.cost());

        ChineseHamburger chAddCrisp = new CHAddCrisp();
        System.out.println("标准煎饼果子加薄脆售价" + chAddCrisp.cost());
    }
}

类图如下:

运行程序:

标准煎饼果子售价5.0
标准煎饼果子加鸡蛋售价6.0
标准煎饼果子加薄脆售价5.5

这样就大大方便卖夹饼果子了,只要根据客户的需要选择相应的煎饼果子,就可以直接显示价格,不用自己算了,媳妇用着很顺手,小王脸上乐开了花……

没过多久,新的问题又出现了:有的客户想加两根火腿,有的客户想加一个鸡蛋再加一个火腿……客户的需求总是多种多样,又让小王的媳妇应接不暇。

看来,之前设计的那些组合根本不够用,难道还要再加CHAddHamAddEgg、CHAddTwoHam……?显示这不是一个理想的方案,天知道顾客们还会有什么特殊的口味呢?

尝试二
// 标准煎饼果子
class ChineseHamburger {
   private static final float PRICE = 5.0f; // 煎饼果子价格

   private static final float CRISP_PRICE = 0.5f; // 薄脆价格

   private static final float EGG_PRICE = 1.0f; // 鸡蛋价格

   private static final float HAM_PRICE = 1.0f; // 火腿价格

   private int addCrispNum; // 需要加多少薄脆

   private int addEggNum; // 需要加多少鸡蛋

   private int addHamNum; // 需要加多少火腿

   public ChineseHamburger(int addCrispNum, int addEggNum, int addHamNum) {
       this.addCrispNum = addCrispNum;
       this.addEggNum = addEggNum;
       this.addHamNum = addHamNum;
   }

   // 计算煎饼以及附加的原料的价格
   public float cost() {
       return PRICE
               + addCrispNum * CRISP_PRICE
               + addEggNum * EGG_PRICE
               + addHamNum * HAM_PRICE;
   }
}

public class ChineseHamburgerAdmin {
   public static void main(String[] args) {
       ChineseHamburger ch = new ChineseHamburger(0, 0, 0);
       System.out.println("标准煎饼果子售价" + ch.cost());

       ChineseHamburger chAddEgg = new ChineseHamburger(0, 1, 0);
       System.out.println("标准煎饼果子加鸡蛋售价" + chAddEgg.cost());

       ChineseHamburger chAddCrisp = new ChineseHamburger(1, 0, 0);
       System.out.println("标准煎饼果子加薄脆售价" + chAddCrisp.cost());

       ChineseHamburger chAddCrispAddEgg = new ChineseHamburger(1, 1, 0);
       System.out.println("标准煎饼果子加薄脆加鸡蛋售价" + chAddCrispAddEgg.cost());
   }
}

这个方案比之前的方案代码少了很多,既避免了类爆炸,又使得逻辑更灵活,现在随便加多少鸡蛋,加多少火腿都可以从容应对了。

谁知,又过了几周,大家对加火腿,加鸡蛋渐渐的也吃腻了,顾客就是上帝,这帮上帝可是真难伺候啊!小王和媳妇绞尽脑汁想丰富口味,他们又尝试了在煎饼里加肉松,加培根,加土豆丝……

但是这样一来,之前的程序就又需要修改了:需要在ChineseHamburger类中再添加AddMeatFlossNum(加肉松数量)、AddBaconNum(加培根数量)、AddPotatoesNum(加土豆丝数量),并且需要修改cost方法。

这无疑违反了编程的一个基本原则:开闭原则。即代码应该对扩展开放,对修改关闭。换句话说,每当逻辑升级,最好通过新增代码实现,而不修改现有代码。

三.更好的方案

下面我们来看另一种实现。

// 抽象类:面饼,可以是煎饼果子,也可以是卷饼,手抓饼等等
abstract class Biscuit {

    abstract float cost();
}


// 煎饼果子继承面饼抽象类
class ChineseHamburger extends Biscuit{
    private static final float PRICE = 5.0f; // 煎饼果子价格

    @Override
    public float cost() {
        return PRICE;
    }
}

// 面饼装饰者,针对当前场景,没有声明其他方法,只继承Biscuit中的方法
abstract class  BiscuitDecorator extends Biscuit{

}

// 面饼装饰者之一:薄脆
class Crisp extends BiscuitDecorator {

    private static final float PRICE = 0.5f;

    // 需要装饰的面饼
    private Biscuit biscuit;

    public Crisp(Biscuit biscuit) {
        this.biscuit = biscuit;
    }

    @Override
    float cost() {
        return biscuit.cost() + PRICE;
    }
}

// 面饼装饰者之一:鸡蛋
class Egg extends BiscuitDecorator {

    private static final float PRICE = 1.0f;

    // 需要装饰的面饼
    private Biscuit biscuit;

    public Egg(Biscuit biscuit) {
        this.biscuit = biscuit;
    }

    @Override
    float cost() {
        return biscuit.cost() + PRICE;
    }
}

// 面饼装饰者之一:火腿
class Ham extends BiscuitDecorator {

    private static final float PRICE = 0.5f;

    // 需要装饰的面饼
    private Biscuit biscuit;

    public Ham(Biscuit biscuit) {
        this.biscuit = biscuit;
    }

    @Override
    float cost() {
        return biscuit.cost() + PRICE;
    }
}
public class ChineseHamburgerAdmin {
    public static void main(String[] args) {
        Biscuit ch = new ChineseHamburger();
        System.out.println("标准煎饼果子售价" + ch.cost());

        // 用鸡蛋装饰煎饼果子
        Biscuit chAddEgg = new Egg(ch);
        System.out.println("标准煎饼果子加鸡蛋售价" + chAddEgg.cost());

        // 用薄脆装饰煎饼果子
        Biscuit chAddCrisp = new Crisp(ch);
        System.out.println("标准煎饼果子加薄脆售价" + chAddCrisp.cost());

        // 用薄脆装饰已经加了鸡蛋的煎饼果子
        Biscuit chAddCrispAddEgg = new Crisp(chAddEgg);
        System.out.println("标准煎饼果子加薄脆加鸡蛋售价" + chAddCrispAddEgg.cost());
    }
}

当前这种实现的类图如下:

这种实现的好处在于:灵活、扩展性强。煎饼果子和煎饼果子装饰者都继承自面饼类,装饰者可以随意对煎饼果子进行装饰,如果需要加肉松,之前的代码都无需修改,只要再实现一个肉松类继承BiscuitDecorator即可。同样,如果以后想卖卷饼、手抓饼也没问题,只要实现卷饼类实现Biscuit ,然后用装饰者装饰就可以了。

四.模式总结

想必大家已经看出来了,我们最后使用的方式就是装饰者模式了。

使用场景

当需要动态地增加责任和行为到对象上时。

例如我们都很熟悉了java IO类就大量的使用了装饰者模式,我们一般这样来声明一个输入流:

BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("file.txt")));

使用InputStreamReader类装饰FileInputStream类,从而完成字节流到字符流的转换,再用BufferedReader类装饰InputStreamReader,实现有缓冲的读,优化性能。

类图

在装饰者模式中,主要有两种角色:组件和装饰者,装设者和组件继承自同一个父类,因此装饰者可以任意装饰其他组件,充分的利用多态的特性来进行行为扩展。

优点

1.可扩展性,添加新的行为无需修改已有代码
2.灵活性,可以根据自己的需求随意对组件进行装饰,动态的改变行为

缺点

1.会增加很多小类,每一种装饰行为需要实现一个特定的装饰类,增加维护成本
2.如果装饰者链很长,则增加了程序的复杂性,出现问题时排查成本高

参考资料:

1.《Head First设计模式》
2.设计模式读书笔记-----装饰者模式

本文已迁移至我的博客:http://ipenge.com/28811.html

相关文章

  • 装饰者模式——IO流运用

    推荐博客Java设计模式学习09Java设计模式学习09Java设计模式学习09 装饰者模式还是比较难懂的。。。。...

  • 装饰对象:装饰者模式

    装饰对象:装饰者模式   这是《Head First设计模式(中文版)》第三章的读书笔记。   装饰者模式,可以称...

  • 设计模式

    设计模式 单例模式、装饰者模式、

  • 设计模式学习(三)——装饰者模式

    一.需求 小王的便利店里卖柴鸡蛋,而且很受欢迎,一下子让小王夫妇打开了思路:原来只要细心观察小区居民的需求,可以卖...

  • 设计模式学习专栏四--------装饰者模式

    设计模式学习专栏四--------装饰者模式 场景 设计星巴兹咖啡, 主体(DarkRoast等) + 配料 (...

  • 设计模式笔记汇总

    目录 设计原则 “依赖倒置”原则 未完待续... 设计模式 设计模式——策略模式 设计模式——装饰者模式 设计模式...

  • java IO 的知识总结

    装饰者模式 因为java的IO是基于装饰者模式设计的,所以要了解掌握IO 必须要先清楚什么事装饰者模式(装饰者模式...

  • 设计模式

    常用的设计模式有,单例设计模式、观察者设计模式、工厂设计模式、装饰设计模式、代理设计模式,模板设计模式等等。 单例...

  • 设计模式学习笔记(三)装饰者模式

    定义 装饰者模式动态地将责任附加到对象上.若要扩展对象,装饰者提供了比继承更有弹性的替代方案. 实现要点 继承属于...

  • 32 Java设计模式系列-装饰者模式

    装饰者模式 装饰者模式是非常常见的设计模式之一,写个笔记,记录一下我的学习过程和心得。 首先了解一些装饰模者式的定...

网友评论

本文标题:设计模式学习(三)——装饰者模式

本文链接:https://www.haomeiwen.com/subject/snseqxtx.html