相信大家都在都在汉堡店吃过汉堡,有些汉堡店很有特色,推出了汉堡订制服务,即,可以在汉堡中加料,加肉饼,加生菜之类(有点类似我们本地的肥肠粉里面加冒结子)。更是让不少吃货大快朵颐,大呼过瘾,加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主要用于如下场景:

  • 想要方便的添加一些行为,而这些行为又不属于类的核心行为。
  • 添加行为的时候,不希望出现类数量爆炸的时候。

最新文章

  1. 【原】iOS学习48地图
  2. 问题:C++ 删除数组指针实用 delete []变量 汇编怎么实现的?
  3. adb常用命令
  4. Android 显示意图激活另外一个Actitity
  5. Java开发人员最常犯的10个错误
  6. 查看mysql表结构和表创建语句的方法(转)
  7. CSS鼠标响应事件经过、移动、点击示例介绍
  8. Codeforces Round #329 (Div. 2) D. Happy Tree Party LCA/树链剖分
  9. Xcode 设置 ARC&amp;MRC混用
  10. 微软BI 之SSIS 系列 - 利用 SSIS 模板快速开发 SSIS Package
  11. PHP undefined index的解决办法
  12. HDU 3078 Network LCA
  13. wndows make images
  14. Long类型比较大小,long型和Long型区别
  15. html_web存储
  16. CASE WHEN 及 SELECT CASE WHEN的用法(转)
  17. 機器學習基石(Machine Learning Foundations) 机器学习基石 课后习题链接汇总
  18. Python函数(一)之杵臼之交
  19. 解决: Homestead 环境下, yarn install --no-bin-links, NPM run dev, 命令报错
  20. IPC_管道

热门文章

  1. Java笔记(day23-day26)
  2. spring源码阅读笔记10:bean生命周期
  3. 使用kubeadm部署k8s集群[v1.18.0]
  4. 无线脉冲水表LoRaWAN方案芯片ASR6500S
  5. 201771030115-牛莉梅 实验一 软件工程准备-&lt;初学《构建之法--现代软件工程》的疑问&gt;
  6. Coursera课程笔记----P4E.Capstone----Week 2&amp;3
  7. 房价预测Task1
  8. Windows10系统优化(批处理)
  9. Fragment 嵌套Fragment注意事项
  10. 你了解C#的协变和逆变吗