设计模式 -- 装饰者模式
这个设计模式还是第一次使用,初次使用觉得特别设计特别别扭,还是要好好钻研一番,否则搞不好使用它会给你带来麻烦。
1、什么是装饰者模式?
装饰者模式能够动态的将功能和职责添加到对象上。听起来觉得很不错呢,可是我们为什么要使用装饰者模式呢?用《Head First》的话说就是解决掉继承的滥用问题,前几个设计模式里我们都有提到“多用组合,少用继承”这个设计原则,这个模式里将继续讨论这个问题。通常我们想到扩展一个类的功能时,总是最先想到“继承”,继承有时候很好用,但是当设计很复杂的类关系时,继承的“黑暗面”会远远大于它的带来的便利。举个例子说奥迪汽车是个基类,这个类有很多它的子类,比如说,A1,A4,A6,A8等等,然后呢,还有带GPS的A1,A4,A6,A8,还有带雷达的A1,A4,A6,A8。有一天啊,我发明了一个先进的防爆系统,专供奥迪车使用,那么A1,A4,A6,A8都要重新加上一个防爆系统;再有一天,奥迪推出了A100,那么接踵而来的就是奥迪A100,带雷达的A100,带GPS的A100,带防爆的A100,带雷达和GPS的A100,带雷达和防爆的A100,带雷达,防爆,和GPS的A100!!!!Oh,No!!简直是太繁琐了,这就是这就是滥用继承引起的“类爆炸”啊!!哈哈,不急不急,想想现实生活中我们是怎么买汽车的吧,我们总是买来汽车,在给汽车安装一些雷达,GPS,防爆系统等设备吧,这就对了,雷达,GPS,防爆系统他们是独立于汽车存在的,是用来装饰汽车的,我们应该把他们抽象出来作为类存在,这样我们想给一个装雷达,或者装GPS不久容易的多了吗?
2、装饰者模式设计原则
2.1封装变化
这里面我们可变化的部分是两部分,汽车会变化(总是会有不同型号的汽车被生产);装饰者会变化(总是会有新的装饰者被发明)。那么我们至少已经有了两个被封装的变化了:汽车类 和 装饰者类。那么所谓的A1,A4,A6,A8当然就是汽车的子类,而雷达,GPS,防爆系统就是装饰者的子类喽!
2.2多用组合,少用继承
呵呵,结合刚才的叙述,你是不是已经知道这个模式是解决继承带来的“类爆炸”问题的了?那么,这里面的组合又从何而来啊?被装饰了雷达的汽车还是汽车,也就是说汽车有了新的功能之后并没有发生类型的转变,也就是说装饰者也是汽车的子类。
2.3 对扩展开放,对修改封闭
我觉得这是装饰者模式最核心的设计思想了,我们在不修改系统底层代码的情况下(一般都是已有的类),来进行功能的扩展。这样的设计弹性很大,系统可以随时接受新功能来应对需求的变化。是不是想到了观察者了?其实这是一个基本的设计原则,很多设计模式都遵循这个原则,如工厂模式,观察者模式等等。
2.4其他:针对接口编程,而不针对实现编程,对象之间的松耦合设计。
3、如何实现装饰者模式?
实现装饰者模式需要注意以下问题:
3.1 装饰者和被装饰者应该具有相同的超类型;
3.2 可以使用一个或者多个装饰者包装一个对象;
3.3 装饰者可以在所委托的被装饰者的行为之前与(或)之后,加上自己的行为,已达到特定的目的;
3.4 既然装饰者和被装饰者有相同的超类型,那么在任何需要原始对象的场合,我们都可以使用被装饰过的对象来替代它。
3.5 对象可以在任何时候被装饰,所以可以在运行时动态的,不限量的用你喜欢的装饰者来装饰对象。
4、装饰者模式类图
我们在具体的看一个例子,背景是一个咖啡店的订单系统,咖啡嘛,我们都知道,分好多种,除此之外,他的配料也分为很多种,具体来看一下使用装饰者模式怎么实现这个系统吧
看到了这个类图是不是觉得比继承好很多呢?
关于刚刚提到的汽车的例子,我们也可以给出类图
让我们来看看实现吧
5、代码示例
//饮料类---抽象类 public abstract class Beverage { protected string description = "Unknown Beverage"; public abstract string getDescription(); public abstract double cost(); } //装饰者类 --调料抽象类, 继承自被装饰者(饮料) public abstract class ComdimentDecorator:Beverage { }
//低咖啡因咖啡 -- 继承自抽象饮料类 public class Decaf : Beverage { public Decaf() { description = "Decaf coffee"; } public override double cost() { return 1.05; } public override string getDescription() { return this.description; } } public class DarkRoast : Beverage { public DarkRoast() { description = "Dark Roast Coffee"; } public override double cost() { return 0.99; } public override string getDescription() { return this.description; } } //浓缩咖啡类--继承自饮料抽象类 public class Espresso : Beverage { public Espresso() { description = "Espresso"; } public override double cost() { return 1.99; } public override string getDescription() { return this.description; } } //黑咖啡 -- 继承自抽象饮料类 public class Houseblend : Beverage { public Houseblend() { description = "House Blend Coffee"; } public override double cost() { return .89; } public override string getDescription() { return this.description; } }
//奶泡 public class Whip : ComdimentDecorator { public Beverage beverage; public Whip(Beverage beverage) { this.beverage = beverage; } public override string getDescription() { return beverage.getDescription() + " , Whip"; } public override double cost() { return 0.10 + beverage.cost(); } } //豆浆 public class Soy : ComdimentDecorator { public Beverage beverage; public Soy(Beverage beverage) { this.beverage = beverage; } public override string getDescription() { return beverage.getDescription() + " , Soy"; } public override double cost() { return 0.15 + beverage.cost(); } } //牛奶 public class Milk : ComdimentDecorator { public Beverage beverage; public Milk(Beverage beverage) { this.beverage = beverage; } public override string getDescription() { return beverage.getDescription() + " , Milk"; } public override double cost() { return 0.20 + beverage.cost(); } } //摩卡 public class Mocha : ComdimentDecorator { public Beverage beverage;//记录被装饰者 public Mocha(Beverage beverage) { this.beverage = beverage; } public override string getDescription() { return beverage.getDescription() + " , Mocha"; } public override double cost() { return 0.20 + beverage.cost(); } }
//测试代码 static void Main(string[] args) { //实例化一杯不需要调料的浓缩咖啡 Beverage be1 = new Espresso(); Console.WriteLine(be1.getDescription() + ", $" + be1.cost()); Console.WriteLine(); //双倍摩卡加奶泡的烧烤咖啡 Beverage be2 = new DarkRoast(); be2 = new Mocha(be2); be2 = new Mocha(be2); be2 = new Whip(be2); Console.WriteLine(be2.getDescription() + ", $" + be2.cost()); Console.WriteLine(); //加豆浆的综合咖啡 Beverage be3 = new Houseblend(); be3 = new Soy(be3); Console.WriteLine(be3.getDescription() + ", $"+be3.cost()); Console.ReadKey(); } }
关于装饰者这个类我们到底采用抽象类还是接口是无所谓的,我个人认为在这里没什么区别,当然也有的人直接采用类来实现,可以达到目的,但是设计不好,毕竟我们应该遵循针对接口编程,不针对实现编程这个设计的原则。所有的原始设计都来源于生活,但最终成熟的设计应该是高于生活的。