从汉堡加料说起——浅谈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 ...
随机推荐
- P2762 太空飞行计划问题 网络流
题目描述 W 教授正在为国家航天中心计划一系列的太空飞行.每次太空飞行可进行一系列商业性实验而获取利润.现已确定了一个可供选择的实验集合E={E1,E2,…,Em},和进行这些实验需要使用的全部仪器的 ...
- CF-612D The Union of k-Segments 差分
D. The Union of k-Segments 题意 给出n个线段,以及一个数字k,让求出有哪些线段:线段上所有的点至少被覆盖了k次. 思路 假如忽略掉线段的左右端点范围,肯定是使用差分来维护每 ...
- ObjectOutputStream:对象的序列化流 ObjectInputStream:对象的反序列化流
package com.itheima.demo04.ObjectStream; import java.io.FileOutputStream; import java.io.IOException ...
- python语法学习第三天--列表
列表:python中不用定义类型,类似工厂 列表的创建: ①创建普通列表:[1,2],用逗号隔开 ②创建一个混合列表:[1,‘zyf',3.14,[1,2,3]] ③创建空列表:empty=[] 常用 ...
- HDU 2011 (水)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2011 题目大意:给你 m 个数,对于每个数,求前 n 项和,数列:1 - 1/2 + 1/3 - 1/ ...
- ERROR 1552 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : 问题的解决
找到SpringbootApplication类, 在注释@SpringBootApplication后加上(exclude = {DataSourceAutoConfiguration.class} ...
- 这是一篇致力于解决Linux小白无法安装tunctl工具的文章
计算机网络——搭建Linux下的实验环境并成功安装tunctl 各位如果是来解决安装tunctl的问题的,请直接到2.2部分的内容. 这个学期终于迎来了计算机网络这门课程,也终于能够进一步了解让我无数 ...
- R的安装以及包安装
今天看论文,需要用到R语言的库,于是又折腾了半天.. 其实并没有什么太大的问题,只是在第三方包的下载方面还有python中使用R方面遇到了问题: 第三方包的导入 其实在网上有 ...
- PAT-1057 Stack (树状数组 + 二分查找)
1057. Stack Stack is one of the most fundamental data structures, which is based on the principle of ...
- 【情感分析必备】python文件读写:codecs
codecs在读取文件时,发生错误: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbe in position 0: invalid s ...