从汉堡加料说起——浅谈C#中的Decorator模式
相信大家都在都在汉堡店吃过汉堡,有些汉堡店很有特色,推出了汉堡订制服务,即,可以在汉堡中加料,加肉饼,加生菜之类(有点类似我们本地的肥肠粉里面加冒结子)。更是让不少吃货大快朵颐,大呼过瘾,加6,7层肉饼的感觉简直不要太好。
那么大饱口福之后,让我们来思考一个问题,汉堡是要钱的,加的料,比如肉饼,生菜,也都是收费的,如果让我们来设计出一套类,计算客户买汉堡的消费,我们应该怎么做比较合适?这里为了简单起见,我们就假定加的肉饼是beef,生菜是tomatto。
第一种设计
建立3个类,一个表示汉堡,另外两个表示肉饼和生菜。汉堡类中有办法添加肉饼和生菜。结算费用的时候,直接调用汉堡类的方法。

在代码中则以这样的形式呈现。
class Beef
{
public double GetCost()
{
return 10;
}
}
class Tomatto
{
public double GetCost()
{
return 5;
}
}
class Hamburg
{
public List<Beef> Beefs { get; private set; } = new List<Beef>();
public List<Tomatto> Tomattos { get; private set; } = new List<Tomatto>();
public void AddBeef(Beef beef)
{
Beefs.Add(beef);
}
public void AddTomatto (Tomatto tomatto)
{
Tomattos.Add(tomatto);
}
public double GetCost()
{
var result = 20d; //hamburg's cost
Beefs.ForEach(b => result += b.GetCost());
Tomattos.ForEach(t => result += t.GetCost());
return result;
}
}
这就是最简单的一种实现方法,然而它有以下几个弊端。
- 类数量过多,应该通过抽象减少类数量,如果以后还有鸡肉饼,小龙虾饼,岂不是又要加新的类?而其实这些类彼此都是相似的。
- 不满足开闭原则。如果以后有了其他可以添加的料,我们会不可避免的修改Hamburg类。
- Hamburg类与具体的料耦合。
所以,这种最简单的做法,如果对于一个小项目或者很简单的案例,我们还可以容忍,如果对于一个大项目,或者预计到未来会出现需求改变的时候,我们就需要改进我们的设计方案。
第二种设计,抽象出料接口
第一种设计中很大的一个缺陷来自于,不管是牛肉饼也罢,生菜也罢,它们都汉堡的一种添加物,对于计费系统,关心的也仅仅是添加物的名字和价格而已,所以,我们应该抽象出接口来进行汉堡类和具体添加物类的解耦。

代码实现如下:
interface Addin
{
double GetCost();
}
class Beef:Addin
{
public double GetCost()
{
return 10;
}
}
class Tomatto:Addin
{
public double GetCost()
{
return 5;
}
}
class Hamburg
{
public List<Addin> Addins { get; private set; } = new List<Addin>();
public void AddAddin(Addin addin)
{
Addins.Add(addin);
}
public double GetCost()
{
var result = 20d;
Addins.ForEach(a => result += a.GetCost());
return result;
}
}
在第二版设计中,我们提炼出了接口addin,使得hamburg类依赖于addin而不直接依赖于具体某个添加物类。同时也保证了开闭原则的实现,就算新的添加物上线,我们也不用修改hamburg类了,我们似乎达到了设计的理想境界。
但是真的这样就无懈可击了吗?
第三种设计,Decorator模式
虽然我们第二种设计解决了依赖于具体类的问题并实现了开闭原则,但是还是会有人觉得不爽,因为大家觉得,虽然第二种设计没有什么大问题了,但是在语义上面,我们希望能保证hamburg类的纯洁性。什么意思呢,就是说,hamburg自己代表自己的价格就行了,添加物毕竟是外来物,没有必要深入到hamburg类的内部。
所以,我们就再次更新我们的设计,这次我们祭出Decorator模式。

以下是Decorotor模式中需要注意的点:
- 装饰类基类和被装饰对象都继承自同一个接口,装饰基类内部还聚合了一个此接口对象。
- 装饰具体类在计算中,先计算自己那部分,再调用基类方法,基类方法一般是计算内部聚合的那个对象, 这样确保了装饰模式可以一层嵌套一层。
我们看看具体代码。
abstract class Food
{
public abstract double GetCost();
}
class Hamburger : Food
{
public override double GetCost()
{
return 20;
}
}
class FoodDecorate : Food
{
private Food _food = null;
public FoodDecorate(Food food)
{
_food = food;
}
public override double GetCost()
{
return _food.GetCost();
}
}
class TomatoDecorator : FoodDecorate
{
public TomatoDecorator(Food food) : base(food) { }
public override double GetCost()
{
return 5 + base.GetCost();
}
}
class BeefDecorator : FoodDecorate
{
public BeefDecorator(Food food) : base(food) { }
public override double GetCost()
{
return 10 + base.GetCost();
}
}
因为不管是Hamburg还是Decorator,大家都实现了Food接口,同时Decorator聚合的也是Food对象,所以在客户端我们可以很方便的写
BeefDecorator beefAddHamburg = new BeefDecorator(new BeefDecorator(new Hamburger()));
Console.WriteLine(beefAddHamburg.GetCost());
以此来表示加了两层牛肉的hamburg。怎么样,这是不是比第二种设计又方便了一点呢?
总结一下,Decorator主要用于如下场景:
- 想要方便的添加一些行为,而这些行为又不属于类的核心行为。
- 添加行为的时候,不希望出现类数量爆炸的时候。
从汉堡加料说起——浅谈C#中的Decorator模式的更多相关文章
- 浅谈JavaScript中的原型模式
在JavaScript中创建对象由很多种方式,如工厂模式.构造函数模式.原型模式等: <pre name="code" class="html">/ ...
- 浅谈 JavaScript 中的继承模式
最近在读一本设计模式的书,书中的开头部分就讲了一下 JavaScript 中的继承,阅读之后写下了这篇博客作为笔记.毕竟好记性不如烂笔头. JavaScript 是一门面向对象的语言,但是 ES6 之 ...
- 浅谈Java中的equals和==(转)
浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: 1 String str1 = new String("hello"); 2 String str ...
- 浅谈Linux中的信号处理机制(二)
首先谢谢 @小尧弟 这位朋友对我昨天夜里写的一篇<浅谈Linux中的信号处理机制(一)>的指正,之前的题目我用的“浅析”一词,给人一种要剖析内核的感觉.本人自知功力不够,尚且不能对着Lin ...
- 浅谈Java中的对象和引用
浅谈Java中的对象和对象引用 在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起 ...
- 浅谈Java中的equals和==
浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: String str1 = new String("hello"); String str2 = ...
- 转【】浅谈sql中的in与not in,exists与not exists的区别_
浅谈sql中的in与not in,exists与not exists的区别 1.in和exists in是把外表和内表作hash连接,而exists是对外表作loop循环,每次loop循环再对内表 ...
- 浅谈iOS中的userAgent
浅谈iOS中的userAgent User-Agent(用户代理)字符串是Web浏览器用于声明自身型号版本并随HTTP请求发送给Web服务器的字符串,在Web服务器上可以获取到该字符串. 在公司产 ...
- 浅谈JavaScript中的闭包
浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...
随机推荐
- centos8-django项目部署 nginx+uwsgi
1.虚拟环境virtualenv安装 1.安装virtualenv pip3 install virtualenv 2.创建目录,把项目文件传过来 mkdir My cd My 3.创建独立运行环境- ...
- B. Long Path dp
https://codeforces.com/problemset/problem/407/B 这个题目是一个dp,有那么一点点的递归的意思,这个应该算一个找规律的dp, dp[i]定义为第一次到第i ...
- Spring官网阅读(十六)Spring中的数据绑定
文章目录 DataBinder UML类图 使用示例 源码分析 bind方法 doBind方法 applyPropertyValues方法 获取一个属性访问器 通过属性访问器直接set属性值 1.se ...
- 【Spark】通过创建DataFrame读取不同类型文件内容
文章目录 读取文本文件 第一种方法:通过RDD配合case class转换DataFrame 步骤 一.创建测试所需的文本文件 二.在spark-shell中执行以下操作 第二种方法:通过sparkS ...
- 【Kafka】监控及运维——kafka-eagle
目录 简单介绍 概述 安装部署 一.环境要求 二.下载源码包并解压 三.准备数据库 四.修改配置文件 五.配置环境变量 六.启动kafka-eagle 七.成功运行 简单介绍 概述 Kafka-eag ...
- Neo4j填坑记录-Neo4jClient建立节点、建立关系相关
最近一个项目需要用到知识图谱,选用了neo4j图数据库,在这过程中遇到几个坑,记录一下 1.无法登录,疯狂提示“WebSocket connection failure. Due to securit ...
- js理论-函数中的Arguments对象
详情参考:https://github.com/mqyqingfeng/Blog/issues/14 如果: arguments和实参的关系,以及arguments的属性 附上代码和注解 functi ...
- 搜索引擎优化(SEO)
一.SEM SEM(Search Engine Marketing)即搜索引擎营销.SEM是一种新的网络营销模式.SEM所做的就是全面有效地利用所搜引擎来进行网络行销推广.SEM追求最高的性价比,以最 ...
- 我的.vimrc配置
termux平台下,通过pkg update && pkg upgrade && pkg install vim来获得vim.可是,当我配置.vimrc时发现如果我每在 ...
- 【漫画】CAS原理分析!无锁原子类也能解决并发问题!
本文来源于微信公众号[胖滚猪学编程].转载请注明出处 在漫画并发编程系统博文中,我们讲了N篇关于锁的知识,确实,锁是解决并发问题的万能钥匙,可是并发问题只有锁能解决吗?今天要出场一个大BOSS:CAS ...