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;
}
}
编写测试结果:
总结
装饰者模式说明:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
画出本例中装饰者模式类图以便理解和记忆
- 【Java学习笔记之七】java函数的语法规则总结
- BZOJ 3038: 上帝造题的七分钟2【线段树区间开方问题】
- BZOJ 3211: 花神游历各国【线段树区间开方问题】
- WP、Win10开发或者WPF开发时绘制自定义窗体~例如:一个手机
- 【Java学习笔记之八】JavaBean中布尔类型使用注意事项
- BZOJ 1597: [Usaco2008 Mar]土地购买【斜率优化+凸包维护】
- BZOJ 1046: [HAOI2007]上升序列【贪心+二分状态+dp+递归】
- 【Java学习笔记之九】java二维数组及其多维数组的内存应用拓展延伸
- BZOJ 1293: [SCOI2009]生日礼物【单调队列】
- Javascript缓存投毒学习与实战
- 【Java学习笔记之十】Java中循环语句foreach使用总结及foreach写法失效的问题
- Codeforces 839B Game of the Rows【贪心】
- Codeforces 839A Arya and Bran【暴力】
- 【Java学习笔记之十一】Java中常用的8大排序算法详解总结
- 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 数组属性和方法
- Win10触控板多指触控、手势操作教程
- 【Vue.js】Vue.js组件库Element中的弹出框、气泡确认框、卡片和走马灯
- 【Vue.js】Vue.js组件库Element中的折叠面板、时间线、分割线和日历
- 从Properties乱码来学习编码
- 【Vue.js】Vue.js组件库Element中的图片、回到顶部、无限滚动和抽屉
- Java逐行读取和写入文件
- Vuejs使用v-for指令实现九九乘法表
- Cypress系列(43)- visit() 命令详解
- 在GitLab pages上快速搭建Jekyll博客
- Dubbo项目中No provider available for the service xxx from registry xxx on the consumer问题的解决思路
- Mysql面对高并发修改的问题处理【2】
- java (多网卡环境下)发送组播广播(multicast/broadcast)失败问题
- activmq:android平台下使用openwire协议连接activemq服务的问题
- Leetcode No.9 回文数
- go-zero微服务框架入门教程