从汉堡加料说起——浅谈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主要用于如下场景:
- 想要方便的添加一些行为,而这些行为又不属于类的核心行为。
- 添加行为的时候,不希望出现类数量爆炸的时候。
最新文章
- 【原】iOS学习48地图
- 问题:C++ 删除数组指针实用 delete []变量 汇编怎么实现的?
- adb常用命令
- Android 显示意图激活另外一个Actitity
- Java开发人员最常犯的10个错误
- 查看mysql表结构和表创建语句的方法(转)
- CSS鼠标响应事件经过、移动、点击示例介绍
- Codeforces Round #329 (Div. 2) D. Happy Tree Party LCA/树链剖分
- Xcode 设置 ARC&;MRC混用
- 微软BI 之SSIS 系列 - 利用 SSIS 模板快速开发 SSIS Package
- PHP undefined index的解决办法
- HDU 3078 Network LCA
- wndows make images
- Long类型比较大小,long型和Long型区别
- html_web存储
- CASE WHEN 及 SELECT CASE WHEN的用法(转)
- 機器學習基石(Machine Learning Foundations) 机器学习基石 课后习题链接汇总
- Python函数(一)之杵臼之交
- 解决: Homestead 环境下, yarn install --no-bin-links, NPM run dev, 命令报错
- IPC_管道
热门文章
- Java笔记(day23-day26)
- spring源码阅读笔记10:bean生命周期
- 使用kubeadm部署k8s集群[v1.18.0]
- 无线脉冲水表LoRaWAN方案芯片ASR6500S
- 201771030115-牛莉梅 实验一 软件工程准备-<;初学《构建之法--现代软件工程》的疑问>;
- Coursera课程笔记----P4E.Capstone----Week 2&;3
- 房价预测Task1
- Windows10系统优化(批处理)
- Fragment 嵌套Fragment注意事项
- 你了解C#的协变和逆变吗