读headFirst设计模式 - 装饰者模式
继承可以在复用父类代码的情况下扩展父类的功能,但同时继承增加了对象之间的耦合度,所以要慎用继承。那么有没有既能扩展父类的功能,又能使对象间解耦的方法呢?答案是肯定的,这就是我们今天要学习的装饰者模式。待会你会看到我会用装饰者模式组装一台电脑。不过现在还是先把书上的例子学习一下。
学习书上的例子
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();
}
浓缩咖啡Espresso:
/**
* 浓缩咖啡
*/
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
} @Override
public double cost() {
return 1.99;
}
}
具体的饮料只具有自己的描述和价格,其他具体的饮料见下面附录A
接着添加抽象装饰者和具体装饰者,具体的调料装饰者将自己的价格和描述附加到饮料的价格和描述上。
抽象调料装饰者CondimentDecorator:
/**
* 调料装饰者,具体的调料必须添加自己的描述
*/
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
具体调料装饰者摩卡Mocha:
/**
* 摩卡,继承自调料装饰者
*/
public class Mocha extends CondimentDecorator {
private Beverage beverage; public Mocha(Beverage beverage) {
this.beverage = beverage;
} @Override
public String getDescription() {
return beverage.getDescription() + ", Mocha";
} @Override
public double cost() {
return 0.20 + beverage.cost();
}
}
其他具体调料见下面附录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());
}
}
装饰者该做什么:
通过例子可以看到装饰者该做的是:装饰者该做的事就是增加行为到被包装的对象上。
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;
}
}
假如现在只有一个先马的机箱作为被装饰者SAMAChassis:
/**
* 一个具体的先马机箱,被装饰类
*/
public class SAMAChassis extends Computer {
public SAMAChassis() {
setType("先马机箱");
setPrice(136.0);
} @Override
public Double prices() {
return getPrice();
} @Override
public String comprise() {
return getType();
}
}
现在要网机箱中加入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);
}
}
这里设计到上面提到的一个问题,我把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);
}
}
其他的具体装饰类我就不写了,也不写附录了,很简单。
测试一下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());
}
}
小结一下
装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。装饰者模式符合开放-关闭原则:对扩展开放,对修改关闭。
附录A
深焙咖啡DarkRoast:
/**
* 深焙咖啡
*/
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "DarkRoast";
} @Override
public double cost() {
return 0.99;
}
}
低咖啡因咖啡Decaf:
/**
* 低咖啡因咖啡
*/
public class Decaf extends Beverage {
public Decaf() {
description = "Decaf";
} @Override
public double cost() {
return 1.05;
}
}
综合咖啡HouseBlend:
/**
* 综合咖啡
*/
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "HouseBlend";
} @Override
public double cost() {
return 0.89;
}
}
附录B
豆浆Soy:
/**
* 豆浆
*/
public class Soy extends CondimentDecorator {
private Beverage beverage; public Soy(Beverage beverage) {
this.beverage = beverage;
} @Override
public String getDescription() {
return beverage.getDescription() + ", Soy";
} @Override
public double cost() {
return 0.15 + beverage.cost();
}
}
奶泡Whip:
/**
* 具体的装饰者,奶泡
*/
public class Whip extends CondimentDecorator {
private Beverage beverage; public Whip(Beverage beverage) {
this.beverage = beverage;
} @Override
public String getDescription() {
return beverage.getDescription() + ", Whip";
} @Override
public double cost() {
return 0.10 + beverage.cost();
}
}
读headFirst设计模式 - 装饰者模式的更多相关文章
- 从源码角度理解Java设计模式——装饰者模式
一.饰器者模式介绍 装饰者模式定义:在不改变原有对象的基础上附加功能,相比生成子类更灵活. 适用场景:动态的给一个对象添加或者撤销功能. 优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个 ...
- Java设计模式——装饰者模式
JAVA 设计模式 装饰者模式 用途 装饰者模式 (Decorator) 动态地给一个对象添加一些额外的职责.就增加功能来说,Decorator 模式相比生成子类更为灵活. 装饰者模式是一种结构式模式 ...
- JAVA设计模式--装饰器模式
装饰器模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰 ...
- 【设计模式】Java设计模式 - 装饰者模式
Java设计模式 - 装饰者模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 原创作品,更多关注我CSDN: 一个有梦有戏的人 准备将博客园.CSDN一起记录分享自 ...
- JAVA 设计模式 装饰者模式
用途 装饰者模式 (Decorator) 动态地给一个对象添加一些额外的职责.就增加功能来说,Decorator 模式相比生成子类更为灵活. 装饰者模式是一种结构式模式. 结构
- 设计模式-装饰器模式(Decrator Model)
文 / vincentzh 原文连接:http://www.cnblogs.com/vincentzh/p/6057666.html 目录 1.概述 2.目的 3.结构组成 4.实现 5.总结 1.概 ...
- [Head First设计模式]山西面馆中的设计模式——装饰者模式
引言 在山西面馆吃鸡蛋面的时候突然想起装饰者这个模式,觉得面馆这个场景跟书中的星巴兹咖啡的场景很像,边吃边思考装饰者模式.这里也就依葫芦画瓢,换汤不换药的用装饰者模式来模拟一碗鸡蛋面是怎么出来的吧.吃 ...
- 浅谈设计模式--装饰者模式(Decorator Pattern)
挖了设计模式这个坑,得继续填上.继续设计模式之路.这次讨论的模式,是 装饰者模式(Decorator Pattern) 装饰者模式,有时也叫包装者(Wrapper),主要用于静态或动态地为一个特定的对 ...
- javascript设计模式——装饰者模式
前面的话 在程序开发中,许多时候都并不希望某个类天生就非常庞大,一次性包含许多职责.那么可以使用装饰者模式.装饰者模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象.本文将 ...
随机推荐
- 如何做到Zero Downtime重启Go服务?
graceful的实践 使用endless库来实现,比如接入gin: r := gin.Default() r.GET("/", index) endless.ListenAndS ...
- twemproxyRedis协议解析探索——剖析twemproxy代码正编
这篇文章会对twemproxyRedis协议解析代码部分进行一番简单的分析,同时给出twemproxy目前支持的所有Redis命令.在这篇文章开始前,我想大家去简单地理解一下有限状态机,当然不理解也是 ...
- 小学生之Hibernate插入数据修改数据使用数据库默认值的实现
最近在写一个案例,定时任务对数据库进行更新操作,废话不多说,上代码: @Component("taskJob") public class TaskJob extends Hibe ...
- css3 3d学习心得
css3 3d学习心得 卡片反转 魔方 banner图 首先我们要学习好css3 3d一定要有一定的立体感 通过这个图片应该清楚的了解到了x轴 y轴 z轴是什么概念了. 首先先给大家看一个小例子: 卡 ...
- 制作 OpenStack Windows 镜像 - 每天5分钟玩转 OpenStack(152)
这是 OpenStack 实施经验分享系列的第 2 篇. OpenStack 通过 Glance 镜像部署 instance,上一节我们介绍了 linux 镜像制作方法,windows 镜像与 lin ...
- Azure机器学习入门(四)模型发布为Web服务
接Azure机器学习(三)创建Azure机器学习实验,下一步便是真正地将Azure机器学习的预测模型发布为Web服务.要启用Web服务发布任务,首先点击底端导航栏的运行即"Run" ...
- spring入门--Spring框架底层原理
上一篇的博客,我们可以看出来,spring可以维护各个bean (对象),并向其中注入属性值.那么,如果们要把一个对象的引用注入另外一个对象呢?应该怎么处理呢? 我们知道,对于对象中的属性来说,我们注 ...
- 安装msdn出现的问题及解决
安装msdn出现的问题及解决 用xx.iso 镜象文件安装 运行第一个镜象文件的setup.exe安装到一部分提示:安装程序无法打开文件 C:\Documents and Settings\empty ...
- 学习笔记——Java内部类练习题
1.尝试在方法中编写一个匿名内部类. package com.lzw; public class AnonymityInnerClass { } class OuterClass4{ public O ...
- Ansible详解(二)
Ansible系列命令 Ansible系列命令有如下: ansible:这个命令是日常工作中使用率非常高的命令之一,主要用于临时一次性操作: ansible-doc:是Ansible模块文档说明,针对 ...