学习编程|Head First设计模式: 装饰者模式

作者:sky201902091605996

链接:https://www.nowcoder.com/profile/503309565/note/detail/360625

来源:牛客网

装饰者模式(Decorator Pattern)

*利用组合(composition)和委托(delegation)可以在运行时实现继承行为的效果,动态地给对象加上新的行为。

*利用继承扩展子类的行为,是在编译时静态决定的;利用组合的做法,可以在运行时动态地扩展对象的行为。

软件设计原则:类应该对扩展开放,对修改关闭。这就是我们常说的开放关闭原则。

*开放-关闭原则使类容易扩展,在不修改代码的情况下,通过搭配实现新的行为。这样的设计可以应对改变,比如增加新功能或需求发生变更。

OO设计技巧:允许系统在不修改代码的情况下,进行功能扩展。

装饰者模式:动态地将责任加到对象身上。如果要扩展功能,装饰者模式提供了比继承更有弹性的替代方案。

装饰者模式中,装饰者可以在被装饰者的行为之前或之后,加上自己的行为,以实现特性的目的。

装饰者模式的几个缺点:

(1)有时在设计中加入大量的小类,变得不容易理解。

(2)有的客户端代码依赖于特定的类型(这是个比较糟糕的习惯,违反了“针对接口编程,而不是针对实现编程”的设计原则),当服务器端引入装饰者模式时,客户端就会出现状况。

(3)装饰者模式使得实例化组件的复杂度提升。

*遵循开放-关闭原则设计系统,努力使关闭的部分(不变)和开放的部分(变化)隔离开来。

问题背景:

设计一个咖啡订单系统。咖啡可以加配料,不同的配料收费不同。如果一个顾客想要摩卡(Mocha)和奶泡(Whip)深焙咖啡(DarkRoast)该怎样去计算费用呢?

类结构图

Beverage(饮料)是一个抽象类,店内所提供的饮料都必须继承此类。cost()方法是抽象的,子类必须定义自己的实现。description(叙述)的实例变量,由每个子类设置,用来描述饮料,如“超优深焙咖啡豆”。利用getDescription()方法返回此叙述。购买咖啡时,可以要求在其中加入各种调料,如:牛奶、豆浆、摩卡等。咖啡馆会根据所加入的调料收取不同的费用。如果直接用继承,会造成类爆炸。如果从基类Beverage下手,加上实例变量代表是否加上调料(牛奶、豆浆、摩卡……),看起来是不错,但是不符合我们的设计原则。如调料价钱改变会使我们更改基类代码、一旦出现新的调料,我们就需要加上新方法,并改变超类中的cost()等,所以我们使用到了装饰者模式。

先从Beverage类下手

public abstract class Beverage{ publicstring description=“Uknown Beverage” //叙述方法已经实理 public String getDescription(){ return description } //价线必须在子类中实理 public abstract double cost(); }

实现调料(Condiment)类,同时这个类也是个装饰者类

//必须让CondimentDecorator 能够取代Beverage public abstract class CondimentDecorator extends Beverage{ //所有转饰者必须重新实现getDescription public abstract String getDescription(); }基类已经创建好了,让我们来实现那些饮料(Beverage)类

//让Espresso扩展自Beverage,因为Espresso是一种饮料

public class Espresso extends Beverage { //设置饮料的描述 public Espresso() { description = “Espresso”; } //计算Espresso的价钱,现在不需要管调料的价钱 public double cost() { return 1.99; } } public class HouseBlend extends Beverage { public HouseBlend() { description = “HouseBlend”; } public double cost() { return .89; } }

我们已经完成了抽象组件(Beverage),有了具体组件(HouseBlend),也有了抽象装饰者(CondimentDecorator)。现在,我们就来实现具体装饰者:

//Mocha是一个装饰者,所以扩展自CondimentDecorator public class Mocha extends CondimentDecorator { //用一个实例变量记录饮料,也就是被装饰者 Beverage beverage; //把饮料当做构造器参数,再由构造器将此饮料记录在实例变量中 public Mocha(Beverage beverage) { this.beverage = beverage; } //利用委托的做法,得到一个叙述,然后在其后加上调料的叙述 public String getDescription() { return beverage.getDescription() + “,Mocha”; } //首先把调用委托给被装饰者对象,然后再加上Mocha的价钱 public double cost() { return .20 + beverage.cost(); } }

测试代码:

public class App { public static void main(String[] args) { //订一杯Espresso,不需要调料 Beverage espresso = new Espresso(); System.out.println(espresso.getDescription() + ” $” + espresso.cost()); //订一杯调料为摩卡的HouseBlend咖啡 Beverage houseBlend = new HouseBlend(); houseBlend = new Mocha(houseBlend); System.out.println(houseBlend.getDescription() + ” $” + houseBlend.cost()); } }

输出为:

Espresso $1.99

HouseBlend,Mocha $1.09

总结

装饰者模式:动态地将责任附件到对象上。若要扩展功能,装饰者提东了比继承更有弹性的替代方案。

*装饰者和被装饰对象有相同的超类型

*你可以用一个或者多个装饰者包装一个对象。

*既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。

*装饰者可以在所委托被装饰者的行为前与/或之后,加上自己的行为,已达到特定的目的。

*对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象

设计原则:类应该对扩展开放,对修改关闭(开放-关闭原则)。注意:遵循开放-关闭原则,通常会引入新的抽象层次,增加代码的复杂度。你需要把注意力集中在设计中最有可能改变的地方,然后应用开放-关闭原则。在选择需要被扩展的代码部分时要小心,每个地方都采用开放-关闭原则,是一种浪费,也没必要,还会导致代码变得复杂且难以理解。

要点:

继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式。在我们的设计中,应该允许行为可以被扩展,而无须修改现有的代码。组合和委托可用于在运行时动态地加上新的行为。除了继承,装饰者模式也可以让我们扩展行为,装饰者模式意味着一群装饰者类,这些类用了包装具体组件。装饰者类反映出被装饰的组件类型(事实上,他们具有相同的类型,都经过接口或继承实现)。装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。你可以用无数个装饰者包装一个组件。装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。

更多名企笔试真题解析、面试经验交流、招聘信息内推,尽在牛客!

求职之前,先上牛客!快快下载拿offer!

与作者交流:https://www.nowcoder.com/profile/503309565/note/detail/360625

更多笔经面经:https://www.nowcoder.com/discuss?order=0&type=2

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片