读headFirst设计模式 - 装饰者模式

news/2025/4/2 14:58:17/

继承可以在复用父类代码的情况下扩展父类的功能,但同时继承增加了对象之间的耦合度,所以要慎用继承。那么有没有既能扩展父类的功能,又能使对象间解耦的方法呢?答案是肯定的,这就是我们今天要学习的装饰者模式。待会你会看到我会用装饰者模式组装一台电脑。不过现在还是先把书上的例子学习一下。

 

学习书上的例子

Starbuzz咖啡店的系统需要更新一下,他们原来的系统是这样的:

 

可以看到,顾客购买饮料时有具体的子类提供并返回饮料的价格。购买咖啡时,可以在其中加入一些调料,比如蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。Starbuzz会根据所加入的调料收取不同的费用。那么这怎么做呢?也许我们会想到这样的几种解决方法:

1.列出所有的饮料和调料的组合方式。好吧,我想没有人会这么做,这样组合情况太多,用书上的一种说法叫“类爆炸”。

2.在Beverage类中设置各种调料的boolean值以表示是否需要这种调料,如boolean milk, 然后用cost计算出加入各种调料后的价格,然后在子类的cost方法中调用父类的cost方法并加上饮料本身的价格。

分析第2中情况:听起来还不错,但一旦加入新的调料就得修改Beverage类。如果研究出了一种新型的饮料,里面的某些调料可能并不合适,这样导致了饮料拥有加入不合适的调料的方法,这样有什么后果,这样可能会出现一些不好的后果,我们在策略模式一章中就受到教训了(橡皮鸭会飞)。还有如果我想要双倍摩卡了,怎么办?

 

尝试解决问题

现在问题已经出现了,怎么解决呢?人们购买咖啡很自然的状态可能是这样的:先购买一杯咖啡,然后想要什么调料就购买什么调料,想要多少就购买多少。于是我们想这样是否能解决问题:先创建一杯咖啡,然后创建调料并和咖啡动态的组合在一起。通过动态地组合对象,可以写新的代码添加新功能,而无须修改现有代码。既然没有改变现有代码,那么引进bug或产生意外副作用的机会将大幅度减少。这就需要用到装饰者模式。

 

软件设计原则

开放-关闭原则:类应该对扩展开放,对修改关闭。

 

定义装饰者模式

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

 

让Starbuzz的饮料符合装饰者模式

需要解释一个这个图:Beverage类是饮料的抽象类,所有的饮料都要继承自这个类,它有一个获得描述的方法(getDescription())和一个计算价格的抽象方法cost()。4个具体的咖啡类(如Espresso的)继承了Beverage类并重写了cost方法。CondimentDecorator类是一个抽象的装饰类,也继承了Beverage,Milk等是具体的装饰类,在计算价格时在饮料的价格上再加上调料的价格,在获得描述时在描述饮料的时候加上了调料的描述,所以说装饰者增加了行为到被装饰者的对象上。

前面说过要慎用继承,装饰者模式是通过动态的组合对象来添加新的功能,那么这里的CondimentDecorator类为什么继承了Beverage类呢?其实,这里使用继承并不是为了继承行为,而是为了保持类型匹配。也就是说在需要被装饰者类型的时候可以用装饰者类型替换。这样可能不太明白,我举个CondimentDecorator没有继承Beverage的例子:假如顾客点了一杯浓缩咖啡Espresso,需要加入的调料为牛奶Milk和摩卡Mocha,我们需要先创建一杯Espresso从而得到espresso对象,然后将espresso对象作为参数传入创建Milk对象,CondimentDecorator milk = new Milk(espresso); 这样就在浓缩咖啡中加入了牛奶,可是还需要加入摩卡啊,这样是不能同时加入牛奶和摩卡的,所以CondimentDecorator继承Beverage是为了保持类型匹配。

 

开始工作

首先需要抽象的饮料和具体的饮料

Beverage:

/*** 抽象饮料类*/
public abstract class Beverage {protected String description = "Unknow Beverage";//对饮料的描述public String getDescription() {return description;}//计算价格public abstract double cost();
}
View Code

 

浓缩咖啡Espresso:

/*** 浓缩咖啡*/
public class Espresso extends Beverage {public Espresso() {description = "Espresso";}@Overridepublic double cost() {return 1.99;}
}
View Code

具体的饮料只具有自己的描述和价格,其他具体的饮料见下面附录A

接着添加抽象装饰者和具体装饰者,具体的调料装饰者将自己的价格和描述附加到饮料的价格和描述上。

 

抽象调料装饰者CondimentDecorator:

/*** 调料装饰者,具体的调料必须添加自己的描述*/
public abstract class CondimentDecorator extends Beverage {public abstract String getDescription();
}
View Code

 

具体调料装饰者摩卡Mocha:

/*** 摩卡,继承自调料装饰者*/
public class Mocha extends CondimentDecorator {private Beverage beverage;public Mocha(Beverage beverage) {this.beverage = beverage;}@Overridepublic String getDescription() {return beverage.getDescription() + ", Mocha";}@Overridepublic double cost() {return 0.20 + beverage.cost();}
}
View Code

其他具体调料见下面附录B

其实可以看到,每个具体的调料类中都要Beverage对象的引用,既然这样可以把Beverage对象引用放到CondimentDecorator类中,大家可以自己调整一下,这里我就不做调整了。我会在后面组装电脑的例子中把被装饰类的引用放到抽象装饰类中。

 

测试一下DecoratorTest:

/*** 装饰者该做的事就是增加行为到被包装的对象上*/
public class DecoratorTest {public static void main(String[] args) throws Exception {Beverage darkRoast = new DarkRoast();System.out.println(darkRoast.getDescription() + "\t" + darkRoast.cost());System.out.println("---------------------");Beverage espresso = new Espresso();espresso = new Mocha(espresso);espresso = new Mocha(espresso);espresso = new Soy(espresso);System.out.println(espresso.getDescription() + "\t" + espresso.cost());}
}
View Code

 

装饰者该做什么:

通过例子可以看到装饰者该做的是:装饰者该做的事就是增加行为到被包装的对象上。

 

jdk中的装饰者:

jdk中也有用到装饰者模式的,那就是IO流中用到了。我想每个人学习IO流的时候都比较痛苦,可能不仅是要区分字节流和字符流,而且一些装饰用的流也企图混淆我们的视线。比如说BufferedInputStream就是一个装饰流,可以用它装饰FileInputStream,所以我们最常用的应该是这样的形式:new BufferedInputStream(new FileInputStream(new File(""))); 和我们上面讲的类似,装饰流也是增加一些行为到被装饰的对象上,比如BufferedInputStream通过缓冲数组来提高性能,提供一个读取整行的readLine方法来进行扩展。下面的图能让你更加了解IO流中的装饰者:

这图上列出的是字节流,字符流也是类似的。但是装饰流使IO中的类更多了,这有时会造成我们的困扰,如果非要说的话,这也算一个“缺点”;

 

自己写例子

学习完书上的例子,我总是想着自己举一个例子,可是要想一个符合的例子真的挺难的,这就是“书到用时方恨少”?哈哈,不扯了,看一下我自己想的例子:我要组装一台电脑,现在只有一个机箱,需要添加其他的配件,就是这里例子了。

 

动手组装电脑

首先需要一个电脑的抽象类Computer,有型号type和价格price2个属性,有获得组成部分comprise()和计算总价prices()2个抽象方法:

/*** 电脑的抽象类,被装饰类的抽象类*/
public abstract class Computer {private String type; //型号private Double price; //价格public abstract String comprise();    //组成部分public abstract Double prices();     //总价public String getType() {return type;}public void setType(String type) {this.type = type;}public Double getPrice() {return price;}public void setPrice(Double price) {this.price = price;}
}
View Code

 

假如现在只有一个先马的机箱作为被装饰者SAMAChassis:

/*** 一个具体的先马机箱,被装饰类*/
public class SAMAChassis extends Computer {public SAMAChassis() {setType("先马机箱");setPrice(136.0);}@Overridepublic Double prices() {return getPrice();}@Overridepublic String comprise() {return getType();}
}
View Code

 

现在要网机箱中加入CPU,主板,内存等配件,将这些配件作为装饰者装饰到机箱上。需要一个装饰者的抽象类DiyDecorator:

/*** 抽象装饰类,diy配件, 各种配件都有型号和价格,故放在抽象的父类中处理,其他属性暂且不计*/
public abstract class DiyDecorator extends Computer {private Computer computer;public String comprise() {//这里的this代表具体的装饰者子类,可选的return computer.comprise() + "---" + this.getType();}public Double prices() {return computer.prices() + this.getPrice();}public DiyDecorator(Computer computer) {this.computer = computer;}public DiyDecorator(Computer computer, String type, Double price) {this.computer = computer;this.setType(type);this.setPrice(price);}
}
View Code

 

这里设计到上面提到的一个问题,我把Computer的引用放到了抽象类DiyDecorator中以增加代码的复用。同时也现实了comprise方法和prices方法,这样,子类只需要调用父类的构造方法即可。

说到这里,又想起来一个问题,我们知道不能创建抽象类的对象,那么,那么抽象类为什么有构造方法呢?其实,从这个例子就可以看出,抽象类的构造方法是用来实例化成员变量的。

 

具体的装饰类CPU:

/*** 具体装饰类:CPU类*/
public class CPU extends DiyDecorator {public CPU(Computer computer, String type, Double price) {super(computer, type, price);}public CPU(Computer computer) {super(computer);}
}
View Code

其他的具体装饰类我就不写了,也不写附录了,很简单。

 

测试一下DecoratorTest:

/*** 我们现在来组装一台电脑,开始只有一个机箱,其他什么都没有*/
public class DecoratorTest {public static void main(String[] args) {run1();System.out.println("------------------");run2();}private static void run1() {Computer computer = new SAMAChassis();computer.setType("金河田预见");computer.setPrice(215.0);computer = new CPU(computer, "酷睿i5 4590", 999.5);System.out.println(computer.comprise() + "\t总价为:" + computer.prices());}private static void run2() {SAMAChassis chassis = new SAMAChassis();CPU cpu = new CPU(chassis);cpu.setType("酷睿i5");cpu.setPrice(999.0);Mainboard mainboard = new Mainboard(cpu);mainboard.setType("技嘉B150");mainboard.setPrice(636.5);Memory memory = new Memory(mainboard, "金士顿8g", 412.5);Power computer = new Power(memory, "航嘉500w", 435.0);System.out.println(computer.comprise() + "\t总价为:" + computer.prices());}
}
View Code

 

小结一下

装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。装饰者模式符合开放-关闭原则:对扩展开放,对修改关闭。

 

 

附录A

深焙咖啡DarkRoast

/*** 深焙咖啡*/
public class DarkRoast extends Beverage {public DarkRoast() {description = "DarkRoast";}@Overridepublic double cost() {return 0.99;}
}
View Code

 

低咖啡因咖啡Decaf

/*** 低咖啡因咖啡*/
public class Decaf extends Beverage {public Decaf() {description = "Decaf";}@Overridepublic double cost() {return 1.05;}
}
View Code

 

综合咖啡HouseBlend

/*** 综合咖啡*/
public class HouseBlend extends Beverage {public HouseBlend() {description = "HouseBlend";}@Overridepublic double cost() {return 0.89;}
}
View Code

 

 

附录B

豆浆Soy

/*** 豆浆*/
public class Soy extends CondimentDecorator {private Beverage beverage;public Soy(Beverage beverage) {this.beverage = beverage;}@Overridepublic String getDescription() {return beverage.getDescription() + ", Soy";}@Overridepublic double cost() {return 0.15 + beverage.cost();}
}
View Code

 

奶泡Whip

/*** 具体的装饰者,奶泡*/
public class Whip extends CondimentDecorator {private Beverage beverage;public Whip(Beverage beverage) {this.beverage = beverage;}@Overridepublic String getDescription() {return beverage.getDescription() + ", Whip";}@Overridepublic double cost() {return 0.10 + beverage.cost();}
}
View Code

 

转载于:https://www.cnblogs.com/pdzbokey/p/6560678.html


http://www.ppmy.cn/news/262267.html

相关文章

Python练习题

【程序 1】 题目:有 1、2、3、4个数字,能组成多少个互不相同且无重复数字的三位数?都是多少? if __name__ __main__:count 0for i in range(1,5):for j in range(1,5):for k in range(1,5):#if(i!j and j!k and k!i):if (i!j) …

【严重】Nacos 集群Raft反序列化漏洞

漏洞描述 Nacos是一个用于动态服务发现和配置以及服务管理的平台。 攻击者可以在Nacos集群处理某些Jraft请求时利用Hessian进行无限制的反序列化,从而造成远程代码执行。 由于Nacos默认监听7848端口处理Raft协议请求,攻击者可能通过向7848端口发送恶意…

2020中国国产比较好的自行车品牌全球十大运动户外品牌排行榜

骑自行车是世界卫生组织极力推崇的最佳有氧运动之一,骑自行车不仅是极好的减肥塑身方式,还可以提升身体的整体机能,提高人体免疫力和抵抗力,让你远离各种疾病的侵扰,每天精力充沛,享受健康生活。如果您坚持…

21辐轮王土拨鼠4-15岁全世界十大进口儿童自行车品牌排行榜

经常骑自行车有助于孩子长高。骑自行车可以促进生长激素的分泌,促进骨骼生长。户外新鲜的空气,充足的阳光,儿童在骑行过程中体温不断升高,促进了新陈代谢,改变了免疫细胞的数量和功能的活性,有助于提升免疫…

2022全球顶级碳纤维自行车品牌排名辐轮王第一土拨鼠引领十强

如今很多人忙于工作学习,严重缺乏户外运动,各种疾病纷纷找上门来。想要保持身体健康其实也不难。世界卫生组织医学专家长期研究后发现,每天坚持骑行一个小时,体质就会大大提高,平时不但不容易生病,而且每天…

质量好的自行车品牌有哪些辐轮王土拨鼠全球顶级自行车品牌排行榜

在欧美发达国家,骑自行车健身十分火爆盛行,因为那里的人们更加了解骑自行车所带来的巨大好处,世界卫生组织曾经大量研究发现,骑自行车的好处数不胜数,骑车能很好的减肥瘦身并让人拥有一个健康体型,增加自信…

618买什么不会出错、2022年最值得入手的运动健身好物

大家好啊!可是还有不到一周的时间618这个电商大促可就要到了呢!想必大家的购物车里面已经装满了要在618下单的好东西了。作为一个比较喜欢扒便宜好货的博主,在618开始的这段时间可是做足了功课,整理出了一份618性价比超高的运动好…

2021辐轮王进口中国国内儿童山地自行车哪个品牌好比较好骑行

很多中国的家长不知道甚至完全没有意识到,经常带孩子到户外骑自行车可以提高孩子的免疫力,让孩子享受“空气大餐”,不但可以促进儿童新陈代谢,还能增强孩子呼吸系统抗病的能力。户外阳光充足,经常让孩子享受“日光浴”…