Head First设计模式——装饰者模式
前言:对于设计模式我们有时候在想是否有必要,因为实际开发中我们没有那么多闲工夫去套用这么多设计模式,也没有必要为了模式而模式。
通常这些模式会引入新的抽象层,增加代码的复杂度,但是当我们掌握了这些设计模式,
在系统中比较棘手或者需要以后修改扩展的地方采用了合适的设计模式会让我们的系统易于扩展维护甚至工作变得轻松很多。
对于这一点我深有体会,有时候设计的比较好的功能模块在后来客户改变需求的时候变得很容易且方便添加修改。
但是如果比较糟糕偷懒的方式会让我们对自己的代码修改变得害怕,害怕客户提需求,害怕去动自己的代码。
所以对于框架或者这些设计模式我们可以不用过度使用,但是要用的时候能有储备或者脑袋里面会闪现出对应的解决方案。
这样不光会提升我们的编码兴趣对我们开发的产品也更有信心。
本篇就聚焦Head First设计模式中的装饰者模式进行学习和总结。
咖啡店结算案例
咖啡店卖各种咖啡,根据调料不一样就会有不同价格的咖啡饮品可选,那么对应的咖啡饮品价格也不一样。
咖啡是需要按照基础价格+调料组合计算价格的,那按照继承的方式我们抽象一个饮料基类Beverage,Beverage拥有Cost抽象方法用于计算价格。
其他咖啡饮品继承Beverage实现Cost方法根据自己的调料计算价格。
public abstract class Beverage {
public abstract void Description();
public abstract float Cost();
}
public class DarkRoast : Beverage
{
public override void Description()
{
//深焙咖啡
}
public override float Cost()
{
//各种调料价格计算
}
}
对于继承的方式,其他子类都要去实现一遍价格计算无法复用,如果子类比较多那么继承的子类会“爆炸”,新增加子类对于我们来说就是一项重复工作。
如果说某一种调料的价格变化我们还得去每个子类里面改变价格。
那我们针对变化的调料部分是不是让他们按照实际需求组合,在子类中确定自己加哪些调料就行了,似乎这种方式会减少重写和维护难度。
按照这个思路将Beverage改造,将是否有调料定义成变量,然后Cost方法不再抽象而是提供实现。
public abstract class Beverage
{
//牛奶
public bool Milk { get; set; }
//糖
public bool Suger { get; set; }
//摩卡
public bool Mocha { get; set; }
public abstract void Description();
public virtual float Cost()
{
float price = 0;
if (Milk)
{
price += 1;
}
if (Suger)
{
price += 2;
}
if (Mocha)
{
price += 3;
}
return price;
}
}
public class DarkRoast : Beverage
{
public override void Description()
{
Console.WriteLine("深焙咖啡");
}
public override float Cost()
{
Milk = true;
Suger = true;
return 1.1f+base.Cost();
}
}
这种方式比之前的继承的确好了许多,不过还是有如下几个问题:
1、调料价格改变会使我们改变现有代码。
2、需要添加新的调料,我们就需要添加新的方法并改变Beverage基类的Cost方法。
3、某些调料可能并不适合其他饮品,例如茶还要继承这些不属于它的调料。
4、如果顾客需要双份糖,Cost方法就会失效。
接下来我们就用"装饰者模式"来更好的设计该案例。
认识装饰者模式
这里有一个设计模式很重要的设计原则:类应该对扩展开放,对修改关闭(开闭原则)
装饰模式同样遵循开闭原则。
对于咖啡店来说,主体是饮料不变,变化的是调料。我们以饮料为主体,其他调料来“装饰”饮料。比如顾客想要加了Mocha(摩卡)的 DarkRoast(深焙咖啡),我们要做的是:
1、制造一个DarkRoast对象
2、以Mocha(摩卡)对象装饰它
3、调用Cost()方法,并依赖委托(非C# delegate,只是概念)将调料的价钱加上去
利用装饰者模式
首先我们修改主体饮料Beverage基类,GetDescription 返回描述,Cost由子类自己实现定价。
public abstract class Beverage
{
string description = "Unkonwn Beverage";
public virtual string GetDescription() {
return description;
}
public abstract float Cost();
}
深焙咖啡类
public class DarkRoast : Beverage
{
public DarkRoast() {
description = "深焙咖啡";
}
public override float Cost()
{
return 1.1f;
}
}
装饰类我们定义一个抽象基类Condiment(调料)并继承Beverage。
为什么要定义一个抽象装饰基类,因为装饰类可能需要抽象出其他方法,而且因为我们用装饰类去装饰了被装饰者后 我们本身也应该变成可已让别人装饰的类。所以装饰类继承CondimentDecorator,CondimentDecorator继承的是Beverage。此例暂未加入装饰类拥有的属性和方法
public abstract class CondimentDecorator:Beverage
{
}
我们再实现两个装饰类:Mik和Suger,用变量记录被装饰者,在构造函数里面设置被装饰者。
public class Milk : CondimentDecorator
{
//用变量记录被装饰者
Beverage beverage;
public Milk(Beverage beverage) {
this.beverage = beverage;
}
public override string GetDescription()
{
return beverage.GetDescription() + "+Milk";
}
public override float Cost()
{
return beverage.Cost() + 1;
}
}
public class Suger : CondimentDecorator
{
//用变量记录被装饰者
Beverage beverage;
public Suger(Beverage beverage)
{
this.beverage = beverage;
}
public override string GetDescription()
{
return beverage.GetDescription() + "+Suger";
}
public override float Cost()
{
return beverage.Cost() + 2;
}
}
编写测试结果:
总结
装饰者模式说明:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
画出本例中装饰者模式类图以便理解和记忆
- 机器学习虾扯淡之Logistic回归No.44
- 大数据计数原理1+0=1这你都不会算(一)No.47
- 机器学习虾扯蛋之SVD奇异值分解No.48
- 提高Spark姿势水平 No.73
- 好好玩的螺旋算法No.69
- linux学习第四十篇:访问日志不记录静态文件,访问日志切割,静态元素过期时间
- linux学习第四十一篇:配置防盗链,访问控制Directory,访问控制FilesMatch
- linux学习第四十二篇:限定某个目录禁止解析php, 限制user_agent,PHP相关配置
- 简易但不简单的配置中心No.79
- linux学习第四十三篇:LNMP架构介绍,mysql安装,php安装,Nginx介绍
- linux学习第四十四篇:Nginx安装,Nginx默认虚拟主机,Nginx域名重定向
- linux学习第四十二篇:PHP扩展模块安装
- linux学习第四十五篇:Nginx访问日志,Nginx日志切割,静态文件不记录日志和过期时间
- 合格的配置中心应有的素养No.76
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- java获取程序执行时间
- Redis数据增多了,是该加内存还是加实例?
- 针对mysql delete删除表数据后占用空间不变小的问题
- 报错信息:(VI_1): ip address associated with VRID 80 not present in MASTER advertt : 192.168.1.8
- 如何保证集合是线程安全的? ConcurrentHashMap如何实现高效地线程安全?
- 如果MySQL的 InnoDB 文件的损坏,该如何手动恢复?
- 如何使用慢查询快速定位执行慢的 SQL?
- 如何使用 EXPLAIN 精准查看执行计划?
- MySQL怎么查看 SQL 的具体执行成本?
- Python 爬虫进阶必备 | 某外卖优惠平台内容加密参数分析
- pytest 自动化测试框架(二)
- Web | Django 与数据库交互,你需要知道的 9 个技巧
- 商机负责人与商机团队负责人不一致时更新团队负责人为商机负责人语句
- mysql常用语句集合(仅供工作日常学习参考)
- 新一代Notebook神器出现,Jupyter危险了!