装饰者模式定义

装饰者模式动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

从定义来看,除了提到比继承更有弹性,其他的还是非常模糊,下面就先给出其UML类图。

从UML类图可以看到装饰者基类(Decorator)主要使用了一个其基类的组合,另外装饰者分别含有自己的新特性,如ConcreteDecoratorA,有自己特有的字段,ConcreteDecoratorB有自己特有的方法。该模式设计的巧妙的地方就在于以上的特点。

为什么说巧妙呢?因为装饰者都含有一个Component的引用,就好比你是老板要找c#开发的,遇到了一个不仅会c#的,还会java的你一样,这样的好处是不容置疑的,不仅可以让你做asp.net,如果哪一天公司要开发andriord了,你照样可以胜任,而不需要再去找andriord的童鞋了,当然也可能是不仅具有C#技能,还可能是能说会道的你。如果哪一天自己想跑业务,自己随时都可以去。跑累了,想体验一下代码生活,照样继续干代码工作……在这个过程中,就像自己被知识武装了起来,所以叫做装饰者模式。

当然有老板要找个什么都会的,全部集中在了一个人身上,也有一定的缺陷,或许一会让你搞andriord,一会让你去搞c#,一会让你跑业务,这样可能会有影响的。敲代码刚刚有思路,就让你去跑业务,显然是有些不科学。

既要武装自己,又要不能老板不时地打扰你的思路,这就需要一个原则:

类应该对扩展开放,对修改关闭。

就是要鼓励学习新东西,但是要禁止打断写代码的思路。这样才能提高效率,那人手不够怎么办,找个新人。

说了这么多,还是觉得通过一个例子才能更好的说明。

装饰者设计模式例子

下面就根据head first设计模式书中的例子,来看看冷饮店是如何卖饮料的。

刚刚开店,只卖矿泉水(MineralWater)和咖啡(Coffee),价钱直接就调用一下其cost()方法就可以的出来。可是聪明的老板一定会想到如果加一下白糖(Sugar)或者牛奶(Milk)是不是可以多赚些呢?但是算账不好算。不可能让矿泉水和咖啡中添加两个方法,AddSugar和AddMilk,这样会产生的问题,如果再加调料,会继续修改饮料的类,这样好不容易写好的类,可能就要继续就改,总共的价格可能也要修改。实在不是好办法。

下面就来看看装饰者模式怎么处理这样的问题。

如果你既是c#程序员,也是andriord程序员,老板说了,我不打断你的思路,白天你可以做C#项目,晚上做andriord的项目,工资的算法你说了算。

你说晚上是白天的2倍,老板说好,财务说,我只看你晚上有没有干,晚上含有白天的引用,计算方法:WageofNight()+Day.wageOfDay()。

同样的道理咖啡店也会通过这样的方式,调料引用一个饮料,总价格通过饮料的价钱加上引用调料的价格。

好了,上图了。咖啡店中饮料类图如下:

感觉不上代码,还是会觉得少些什么,下面就给出对应的代码:

public abstract class Beverage
{
public string description = "所有饮料的基类"; public abstract double Cost(); public virtual string GetDesciption()
{
return description;
} }
public class Coffee : Beverage
{
public Coffee()
{
description = "我是热咖啡";
}
public override double Cost()
{
return 1.50;
} }
public class MineralWater : Beverage
{
public MineralWater()
{
description = "我是矿泉水";
}
public override double Cost()
{
return 1.0;
} }
public abstract class BeverageDecrator : Beverage
{
public Beverage beverage; public abstract string GetDesciption(); }
public class Milk : BeverageDecrator
{
public Milk(Beverage beverage)
{
this.beverage = beverage;
}
public override string GetDesciption()
{
return beverage.description + "-------加牛奶";
}
public override double Cost()
{
return beverage.Cost() + 0.5;
} }
public class Sugar : BeverageDecrator
{
public Sugar(Beverage beverage)
{
this.beverage = beverage;
}
public override string GetDesciption()
{
return beverage.description + "--------加糖";
}
public override double Cost()
{
return 0.5 + beverage.Cost();
}
}

测试代码:

class Program
{
static void Main(string[] args)
{
Console.WriteLine("小二哥,来一杯矿泉水加糖"); Beverage mineralWater = new MineralWater();
Sugar sugar = new Sugar(mineralWater);
Console.WriteLine("客官,来了,请慢用");
Console.WriteLine(sugar.GetDesciption());
Console.WriteLine("小二哥,结账"); Console.WriteLine("客官,共"+sugar.Cost()+"¥");
Console.WriteLine("客官慢走"); Console.WriteLine("----------------------------"); Console.WriteLine("小二哥,来一杯咖啡加牛奶"); Beverage coffee = new Coffee();
Milk milk = new Milk(coffee);
Console.WriteLine("客官,来了,请慢用");
Console.WriteLine(milk.GetDesciption());
Console.WriteLine("小二哥,结账"); Console.WriteLine("客官,共" + milk.Cost() + "¥");
Console.WriteLine("客官慢走"); Console.ReadKey();
}
}

请注意上述代码是如何使用组合从而达到可以自由的扩展以及修改的,最终的可以互不影响的扩展和修改。为了进一步演示自由的扩展和修改的,进一步提需求:现在我觉得加牛奶要贵些,然后送一张优惠券。

其他的不改变,只改变Milk类,即可。

public class Milk : BeverageDecrator
{
public Milk(Beverage beverage)
{
this.beverage = beverage;
}
public override string GetDesciption()
{
return beverage.description + "-------加牛奶";
}
public override double Cost()
{
SendCoupon();
return beverage.Cost() + ;
} public void SendCoupon()
{
Console.WriteLine("送优惠券一张");
}
}

输出结果:

看看现在修改了牛奶类,没有改变其他地方,就可以得到我们想要的结果了。从而实现了前面所说的原则,开闭原则。

上面设计的问题以及其解决方案

上面的设计好像是遵循了开闭原则,其实则没有,因为类要对扩展开放,对修改关闭,我们很明显,对修改没有关闭,只是对扩展开放而已,现在就来解决修改关闭的问题,要到达这样的效果,就确定了不能改变类的任何地方,只能对类进行扩展,是通过另外一个类,来进行对改类的扩展,这样做的目的是不仅扩展了想要扩展的类,还可以灵活的扩展自己想扩展的类。已达到复用的效果。接下来,看看如何在不改变原有的基础上进行扩展,从而遵循开闭原则。

注意上面的BeverageExtend类,其类多了个SendCoupon方法,(这个方法也是为了显眼而已),用来送优惠券,我们现在修改的是sugar类或Milk类,但是通过的是BeverageExtend类来扩展的,这样才是真正到达了开闭原则。

下面给出扩展类和测试代码

public class BeverageExtend : BeverageDecrator
{ public BeverageExtend(Beverage beverage)
{
this.beverage = beverage;
}
public virtual void SendCoupon()
{
Console.WriteLine("总价为" + this.Cost());
Console.WriteLine("送您一张优惠券");
} public override string GetDesciption()
{
return beverage.GetDesciption() + "——————加优惠券";
}
public override double Cost()
{
return beverage.Cost() + 1.5;
}
}

测试代码:

static void Main(string[] args)
{
Console.WriteLine("小二哥,来一杯矿泉水加糖"); MineralWater mineralWater = new MineralWater();
Sugar sugar = new Sugar(mineralWater);
Console.WriteLine("客官,来了,请慢用");
Console.WriteLine(sugar.GetDesciption());
Console.WriteLine("小二哥,结账"); Console.WriteLine("客官,共"+sugar.Cost()+"¥");
Console.WriteLine("客官慢走"); Console.WriteLine("----------------------------"); Console.WriteLine("小二哥,来一杯矿泉水加糖"); MineralWater mineralWater1 = new MineralWater();
Sugar sugar1 = new Sugar(mineralWater1);
BeverageExtend beverage = new BeverageExtend(sugar1);
Console.WriteLine("客官,来了,请慢用");
Console.WriteLine(beverage.GetDesciption());
Console.WriteLine("小二哥,结账"); Console.WriteLine("客官,共" + beverage.Cost() + "¥");
Console.WriteLine("客官慢走");
Console.WriteLine("----------------------------"); Console.WriteLine("小二哥,来一杯咖啡加牛奶"); Coffee coffee = new Coffee();
Milk milk = new Milk(coffee); BeverageExtend beverage2 = new BeverageExtend(milk); Console.WriteLine("客官,来了,请慢用");
Console.WriteLine(beverage2.GetDesciption());
Console.WriteLine("小二哥,结账"); Console.WriteLine("客官,共" +beverage2.Cost() + "¥");
Console.WriteLine("客官慢走"); Console.ReadKey();
}

下面给出测试结果图:

小结

本文主要通过装饰者模式,引出了设计模式中的开闭原则,通过冷饮店的售价,来体现装饰者如何实现开闭原则的,但是需要注意要对类进行扩展是通过另外一个装饰类来完成的,而不是通过在本身类中实现的。

源码:DecratorPattern.zip

装饰者模式——(head first 设计模式3)的更多相关文章

  1. Java 的设计模式之一装饰者模式

    刚开始接触装饰者的设计模式,感觉挺难理解的,不够后来花了一个晚上的时间,终于有头绪了 装饰者设计模式:如果想对已经存在的对象进行装饰,那么就定义一个类,在类中对已经有的对象进行功能的增强或添加另外的行 ...

  2. [Head First设计模式]山西面馆中的设计模式——装饰者模式

    引言 在山西面馆吃鸡蛋面的时候突然想起装饰者这个模式,觉得面馆这个场景跟书中的星巴兹咖啡的场景很像,边吃边思考装饰者模式.这里也就依葫芦画瓢,换汤不换药的用装饰者模式来模拟一碗鸡蛋面是怎么出来的吧.吃 ...

  3. Java设计模式12:装饰器模式

    装饰器模式 装饰器模式又称为包装(Wrapper)模式.装饰器模式以多客户端透明的方式扩展对象的功能,是继承关系的一个替代方案. 装饰器模式的结构 通常给对象添加功能,要么直接修改对象添加相应的功能, ...

  4. JAVA 设计模式 装饰者模式

    用途 装饰者模式 (Decorator) 动态地给一个对象添加一些额外的职责.就增加功能来说,Decorator 模式相比生成子类更为灵活. 装饰者模式是一种结构式模式. 结构

  5. 浅谈设计模式--装饰者模式(Decorator Pattern)

    挖了设计模式这个坑,得继续填上.继续设计模式之路.这次讨论的模式,是 装饰者模式(Decorator Pattern) 装饰者模式,有时也叫包装者(Wrapper),主要用于静态或动态地为一个特定的对 ...

  6. Head First 设计模式 --3 装饰者模式 开闭原则

    装饰者模式:动态的将责任附加到对象上,若要扩展功能,装饰者提供了比集成更有弹性的替代方案.设计原则:1:封装变化2:多用组合,少用继承3:针对接口编程,不针对实现编程4:为对象之间的松耦合设计而努力5 ...

  7. Java 设计模式泛谈&装饰者模式和单例模式

    设计模式(Design Pattern) 1.是一套被反复使用.多人知晓的,经过分类编目 的 代码设计经验总结.使用设计模式是为了可重用代码,让代码更容易维护以及扩展. 2.简单的讲:所谓模式就是得到 ...

  8. Java设计模式——装饰者模式

    JAVA 设计模式 装饰者模式 用途 装饰者模式 (Decorator) 动态地给一个对象添加一些额外的职责.就增加功能来说,Decorator 模式相比生成子类更为灵活. 装饰者模式是一种结构式模式 ...

  9. C#设计模式(9)——装饰者模式(Decorator Pattern)

    一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...

  10. 【读书笔记】读《JavaScript设计模式》之装饰者模式

    一.定义 装饰者模式可用来透明地把对象包装在具有同样接口的另一个对象之中.这样一来,你可以给一个方法添加一些行为,然后将方法调用传递给原始对象.相对于创建子类来说,使用装饰者对象是一种更灵活的选择(装 ...

随机推荐

  1. 如何使用jmeter来实现更大批量的并发的解决方案

    近期在用JMeter进行负载测试的 时候,发现使用单台机器模拟测试超过比如500个进程的并发就有些力不从心或者说不能如实的反应实际情况,在执行的过程中,JMeter自身会自动关闭, 要解决这个问题,则 ...

  2. 微信小程序基于scroll-view实现锚点定位

    代码地址如下:http://www.demodashi.com/demo/14009.html 一.前期准备工作 软件环境:微信开发者工具 官方下载地址:https://mp.weixin.qq.co ...

  3. PmException--- SQL(统计报表)

    select TAGS,GROUP_CONCAT(TAGS) t from EXCEPTION_RESULT e,PM_TASK t ' and t.OWNER='admin' group by TA ...

  4. HighCharts终极版本

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  5. url-pattern / /*匹配

    http://hi.baidu.com/atell/item/522112d3db45081fd90e44e1 struts2中配置为 <url-pattern>/*</url-pa ...

  6. if语句的数据驱动优化(Java版)

    举个栗子,如果我要输出数字对应的中文描述,我可以用这种方法来写: int num=2; if (num==1){ System.out.println("一"); } else i ...

  7. AR_标准应收过账至总账基本操作(流程)

    2014-06-04 Created By BaoXinjian

  8. 信号的捕捉与sigaction函数

    一.内核如何实现信号的捕捉 如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号.由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下: 1. 用户程序注册了SI ...

  9. 图解最小生成树 - 克鲁斯卡尔(Kruskal)算法

    我们在前面讲过的<克里姆算法>是以某个顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树的.同样的思路,我们也可以直接就以边为目标去构建,因为权值为边上,直接找最小权值的边来构建生成树 ...

  10. linux write系统调用如何实现

    在Linux下我们在使用设备的时候,都会用到write这个函数,通过这个函数我们可以象使用文件那样向设备传送数据.可是为什么用户使用write函数就可以把数据写到设备里面去,这个过程到底是怎么实现的呢 ...