本文是对面向对象设计模式--装饰者模式的全面解析,主要分为定义解析、以咖啡店案例讲解装饰者模式、多案例练习加深对装饰者模式的理解、最后总结知识要点与装饰者模式的一些优缺点与适用场景。
第一篇:定义解析
策略模式是GoF四人帮整理的《设计模式-可复用面向对象软件基础》一书中23种设计模式中归类为结构型模式中的一种,23种设计模式根据它们的用途分为三大类:5种创建型模式、7种结构型模式、11种行为型模式。
引自GoF四人帮对装饰者模式(Decorator)的定义与类图:
动态的给一个对象添加一些额外的职责。就扩展功能而言,它比生成子类的方式更为灵活。
装饰者模式又称为包装模式,它主要是为了扩展对象的功能,通过持有对象的引用,把对象包装起来,可以在调用对象的方法之前或者之后增加新的功能,以达到给对象添加一些额外的职责,就像为对象添加了一些装饰。
另一个要点是,包装者可以有很多个,对象被包装了一层之后,依然可以继续再包装来添加新的职责。添加职责的目的其实就是为了扩展对象的功能,通过使用装饰者模式可以使得系统具有非常好的弹性。遵循了面向对象原则:对扩展开放,对修改关闭。如果有新的需求变更,对象功能扩展,只需要新增一个装饰者类,将对象包装起来即可扩展对象的功能,而不需要对旧有对象代码进行修改。
通过继承,子类进行方法重写同样也可以扩展对象的功能。但它的弹性比较差,一旦业务变更或新增业务功能时,就需要打开实现类的代码进行修改,这样不仅需要检查旧有代码,同时还要保证新增代码的正确性。类的继承关系是在编译期就已经确定好了,运行期间不能动态的更改,使用装饰者模式可以在运行期间动态的、不限量的给对象添加装饰者,扩展功能。装饰者模式是替代继承来扩展对象功能更好的方案。
ConcreteCompent是需要扩展功能的对象或者叫组件对象。
Decorator是装饰者类共同实现的接口或抽象类,它和ContreateCompent具有相同的超类型,并且持有一个待扩展对象的引用。
ConcreteDecoratorA等是具体的装饰者类,它们可以给引用的组件对象加上新的方法。新的方法通过在旧的方法的前面或者后面做一些计算来添加。
第二篇:以咖啡店案例讲解装饰者模式
基础需求:连锁咖啡店因业务扩大,需要对订单系统进行升级,旧的咖啡种类有:HouseBlend 综合咖啡、Darkroast 深焙咖啡、Decaf 低咖啡因咖啡、Espresso 浓咖啡;旧的订单系统如下:
Beverage(饮料)是一个抽象类,店内所有饮料都需要继承此类。
description是对饮料的具体描述,由每一个子类设置,通过getDescription()获取。
cost()用于计算饮料的价钱,是抽象方法,必须由子类实现。
业务升级:购买咖啡时也可以在其中加入各种调料,例如蒸奶(Steamed milk)、豆浆(Soy)、摩卡(Mocha)或者覆盖奶泡(Whip)。搭配不同的调料组合,相应地计算咖啡价钱时需要加上调料的价钱来计算咖啡的最终价格。
第一次尝试:每一种调料组合搭配的咖啡,通过创建一个新类来代表。
缺点:采用此种设计将会生成大量的类,造成“类爆炸”,这将会是维护的恶梦。一旦有变更,如牛奶价格上涨,就需要对相关的所有类进行修改。
违反的两个面向对象设计原则:
1)面向接口编程,而不是实现编程。Beverage是抽象类,除非继承,否则无法扩展。
2)封装变化,把变化的部分抽离出来,与不变的部分分开。这里变化的部分就是需要加入的各种调料组合,使得计算的价格发生变化。
第二次尝试:利用实例变量和继承实现。
在Beverage饮料抽象类中加上实例变量代表是否加上调料。当有需求改变:
1)调料的价格变更,需要修改代码,修改cost()计算咖啡价格的代码。
2)一旦有新的调料,需要修改代码,添加新的实例变量,及修改cost()计算咖啡的价格。
3)当开发新的饮料,某些调料可能并不适合。如冰红茶和不需要加奶泡调料。
4)顾客想要双倍摩卡将无法实现。
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。
利用组合扩展对象的行为,就可以运行时动态地进行扩展。
开放-关闭原则: 类应该对扩展开放,对修改关闭。
我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。
如能实现这样的目标,会使我们的设计具有弹性,可以应对改变,可以接受新的功能来应对改变的需求。如:观察者模式,通过加入新的观察者,我们可以在任何时候扩展主题,而且不需修改主题中的代码。
对扩展开放,对修改关闭。并不需要代码中每个地方都遵循,这通常也做不到。为实现对扩展开放,对修改关闭原则,通常会引入新的抽象层次,增加代码的复杂度。你需要把注意力集中在设计中最有可能改变的地方,然后应用开放-关闭原则。
使用装饰者模式:现状:我们已经认识到利用继承已经无法完全解决问题。在咖啡店案例中遇到的问题有:类数量爆炸、设计死板、以及基类加入新功能并不适用于所有子类。
新的做法:以饮料为主体,然后在运行时用调料来“装饰”饮料。
如:顾客想要一杯加摩卡和奶泡的深焙咖啡。
1)拿一个深焙咖啡(DarkRoast)对象。
2)以摩卡(Mocha)装饰它。
3)以奶泡(Whip)装饰它。
4)调用cost()方法,并依赖委托将调料的价钱加上去。
使用装饰者模式设计咖啡店类图:
要点:
1)装饰者对象和被装饰者对象有相同的超类型。
2)可以用一个或多个装饰者包装一个对象。
3)装饰者可以在被装饰者行为之前或之后加上自己的行为,以达到特定的目的。
4)对象可以在任何时候被装饰。可以在运行时动态的、不限量的用装饰者来装饰对象。
5)装饰者利用继承来达到“类型匹配”,而不是利用继承“获得行为”。装饰者能够取代被装饰者。
6)将装饰者与组件组合时,就是在加入新的行为。新的行为并不是通过继承得来,而是通过组合对象得来的。
第三篇:案例练习
练习案例源码:https://github.com/chentian114/100-dayaction/tree/master/designpattern-1
BeverageDemo咖啡店案例:
有四种咖啡(综合咖啡、深焙咖啡、低咖啡因咖啡、浓缩咖啡),每种咖啡可以加不同的配料组合。配料包括(摩卡、奶泡、豆浆、牛奶),每加一种配料,咖啡的价格要加上相应配料的价钱。
PancakeDemo煎饼摊案例:
煎饼小摊卖两种煎饼手抓饼和肉夹膜,煎饼可以自由搭配不同的配料组合,每增加一样配料价钱加上该配料的价钱。配料有:黄瓜丝、火腿、肉松、煎蛋
HeroDemo英雄学习技能案例:
英雄李白,随着等级提升能够学习新的技能,包括Q,W,E。
MonkeyDemo孙大圣变化案例:
孙大圣本体是只猴子,他可以变成不同的动物:鱼、鸟。他的每一种变化都给他带来一种附加的本领。他变成鱼儿时,就可以到水里游泳;他变成鸟儿时,就可以在天上飞行。
第四篇:总结
装饰者模式又名包装模式。装饰者模式以对客户端透明的方式扩展对象的功能,是继承的一个替代方案。
装饰模式有透明和半透明两种,这两种的区别就在于装饰角色的接口与抽象构件角色的接口是否完全一致。
1)透明的装饰模式也就是理想的装饰模式,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。
2)装饰角色的接口比抽象构件角色的接口宽的话,装饰角色实际上已经成了一个适配器角色,称为“半透明”的装饰模式。
装饰者模式:增强功能、不改变接口。
半透明装饰者模式:增强功能、改变接口。
适配器模式:不增加功能、改变接口。
装饰者模式的优点:
1)目的在于扩展对象的功能。装饰者模式提供比继承更好的灵活性。装饰是动态的,运行时可以修改的;继承是静态的,编译期便已确定好。
2)通过使用不同的装饰类及对它们的排列组合,可以创造出许多不同行为的组合。
装饰者模式的缺点:
1)利用装饰者模式,常常造成设计中有大量的小类,数量太多,会造成使用此API的人带来困扰,不容易理解。
2)采用装饰者在实例化组件时,将增加代码复杂度。一旦使用装饰者,不仅要实例化组件,同时要将组件实例包装进装饰者。
3)调试时不易排查错误。
适用场景:
1)需要扩展一个类的功能, 或给一个类增加附加功能。
2)需要动态地给一个对象增加功能, 这些功能可以再动态地撤销。
3)需要为一批的兄弟类进行改装或加装功能, 当然是首选装饰模式