八、装饰者模式—巴厘岛,奶茶店的困扰! #和设计模式一起旅行#
善于思考,方法总比问题多!
故事背景
我和漂亮的模式MM来到巴厘岛,这里火山爆发刚刚结束不久,一切要重新开始,来到这个地方几天后,觉得这个地方还是不错,于是就决定在这里开一个奶茶店,因为这里游客比较多,流量大,反正之前我们也没有开店的体验,那么一拍即合,开个奶茶店,体验一下了。
奶茶店的名字:Beautiful Life milk tea!
名字起好了,那么我们就开始想如何做一个奶茶收费的系统,让我们在卖奶茶的时候帮我们自动计费!因为在购买奶茶的时候,可以在奶茶中加入各种调料,如,绿茶粉、红茶粉,果冻、珍珠等,然后根据加入的东西不同费用也不同!
第一版我进行了简单的设计,代码如下:
public abstract class MilkTea {
private String name;//名字
public String getName(){
return name;
}
//支付的钱
public abstract double cost();
}
public class GreenTea extends MilkTea {
@Override
public double cost() {
return 10.0;
}
}
public class RedTea extends MilkTea {
public RedTea(String name){
super.setName(name);
}
@Override
public double cost() {
return 15.0;
}
}
public class GuoDongGreenTea extends MilkTea {
public GuoDongGreenTea(String name){
super.setName(name);
}
@Override
public double cost() {
//果冻绿茶的价钱= 果冻价钱 + 绿茶价钱
return 3.0 + 10.0;
}
}
// 其他的种类不在一一列举了。
public class Client {
public static void main(String[] args) {
//这里有一个顾客,首先点了一个红茶,然后点了一个 果冻绿茶
MilkTea tea = new RedTea("红茶");
double cost = tea.cost();
System.out.println(tea.getName() + "需要支付:" + cost);
tea = new GuoDongGreenTea("果冻奶茶");
cost = tea.cost();
System.out.println(tea.getName() + "需要支付:" + cost);
}
}
红茶需要支付:15.0
果冻奶茶需要支付:13.0
设计模式MM : 这种方式是很简单,也能实现我们的需求,但是每一种奶茶都需要一个类,如果业务发展快,新开发了很多种类的奶茶的话,那么系统的类就会多到啊爆炸!还有如果某一天珍珠或者果冻降价了,那会出现所有和珍珠或者果冻有关的奶茶都需要修改!
如图:
因为第一版会存在类爆炸问题,所以我又思考了第二版,
/**
* 第二版的实现
*/
public class MilkTea {
private String name;//名字
Boolean hasGuoDong = false; //是否添加果冻
Boolean has* = false; //是否添加珍珠
public String getName(){
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getHasGuoDong() {
return hasGuoDong;
}
public void setHasGuoDong(Boolean hasGuoDong) {
this.hasGuoDong = hasGuoDong;
}
public Boolean getHas*() {
return has*;
}
public void setHas*(Boolean has*) {
this.has* = has*;
}
//支付的钱
public double cost(){
double cost = 0;
if(hasGuoDong){
cost += 3.0;
}else if(has*){
cost += 5.0;
}else {
cost += 0.0;
}
return cost;
}
}
//绿茶
public class GreenTea extends MilkTea {
public GreenTea(String name){
super.setName(name);
}
@Override
public double cost() {
//添加的东西价钱 + 绿茶的价钱
return super.cost() + 10.0;
}
}
//红茶
public class RedTea extends MilkTea{
public RedTea(String name){
super.setName(name);
}
@Override
public double cost() {
//添加的东西价钱 + 红茶的价钱
return super.cost() + 15.0;
}
}
public class Client {
public static void main(String[] args) {
//这里有一个顾客,首先点了一个果冻红茶,然后点了一个珍珠绿茶
MilkTea tea = new RedTea("果冻红茶");
tea.setHasGuoDong(true);
double cost = tea.cost();
System.out.println(tea.getName() + "需要支付:" + cost);
tea = new GreenTea("珍珠绿茶");
tea.setHas*(true);
cost = tea.cost();
System.out.println(tea.getName() + "需要支付:" + cost);
}
}
果冻红茶需要支付:18.0
珍珠绿茶需要支付:15.0
设计模式MM : 这种方式使用继承的关系在超类里面定义各种不同添加物的价钱,使用子类去继承,这样可以防止第一版中的类爆炸问题和如果一个添加物的价格发生变化的话,只需要修改一个地方!但是你上面的实现还是违反了我的原则(设计模式原则):开闭原则!
- 如果添加物的价格改变要改现有的超类代码
- 如果有新的添加物要加入的话,还是要改现有的超类代码
- 万一顾客想要在一杯奶茶中加上两倍的果冻,或者珍珠呢,那么又应该怎么做呢?
- …..
设计模式MM:上面两个版本虽然在一定程度上满足需求,但是不是很好的设计方案,我给你提供一个建议,去看看装饰者模式吧!给爱用继承的你一个全新的设计眼界。
故事主角—装饰者模式
装饰者模式 : 动态的将责任附加到对象上。若要扩展功能,装饰者提供比继承更有弹性的替代方案!
注:使用继承的话,编译时候决定了子类都会继承到相同的行为。使用 组合,可以在运行时候动态的扩展对象的行为。
在装饰模式中,为了让系统具有更好的灵活性和可扩展性,我们通常会定义一个抽象的装饰类。而具体的装饰类作为它的子类,
在装饰者类图中,有一下角色登场。
- Component(抽象构件):抽象构件是具体构件和抽象装饰类的共同父类,申明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理为被装饰的对象以及装饰之后的对象,实现客户端的透明操作!(装饰物和被装饰物的一致性)
ConcreteComponent(具体构件):抽象构件类的子类,实现父类申明方法定义具体的构件对象。(装饰模式中的被装饰者)
Decorator(抽象装饰类):抽象构件类的子类,用户给具体构件增加功能或职责。它维护了一个抽象构件对象的引用,通过引用可以调用到装饰之前构件对象的方法。通过子类扩展改方法,在不改变具体构件的情况下,达到装饰目的。(抽象装饰类Decorator并不是必须的)
ConcreteDecorator(具体装饰类):抽象装饰类的子类,负责向具体构件添加职责。每一个具体的装饰类定义一些新的行为,扩充具体构件对象的行为。
下面用简单代码诠释一下:
//抽象构件,使用一个接口定义
public interface Component{
void method1();
}
//具体的构件,即被装饰对象
public class ConcreteComponent implments Component{
public void method1(){
//do something...
}
}
//定义抽象装饰类,这个类不是必须的,但是为了扩展更多的装饰物,对装饰物进行抽象
public abstract class Decorator implments Component{
private Component component;
public Decorator(Component component){
this.component = component;
}
public void method1(){
component.method1();
}
}
//定义具体的装饰类 X,Y和X一样就写出了
public class ConcreteDecoratorX extends Decorator{
public ConcreteDecoratorX(Component component){
super(component);
}
public void methodX(){
//使用methodX进行包装/装饰
}
public void method1(){
super.method1();
methodX();
}
}
由于在抽象装饰类Decorator中注入的是Component类型的对象,因此我们可以将一个具体构件对象注入其中,再通过具体装饰类来进行装饰;此外,我们还可以将一个已经装饰过的Decorator子类的对象再注入其中进行多次装饰,从而对原有功能的多次扩展。
武功修炼
根据上面对装饰者模式的讲解,说白了我们之前的奶茶是被装饰物,装饰物就是 果冻或者珍珠等等添加物。下面使用装饰者模式来进行设计。
/**
* 奶茶店最终设计方案,抽象构件
* @author:dufyun
* @version:1.0.0
*
*/
public interface MilkTea {
/**
* 奶茶的名字
* @return
*/
String getName();
/**
* 奶茶的价格
* @return
*/
double cost();
}
/**
* 红茶类 --具体的构件,被装饰对象
*
* @author:dufyun
* @version:1.0.0
*/
public class RedTea implements MilkTea{
/**
* 奶茶名字
*/
private static String name = "红茶";
public RedTea(){}
@Override
public String getName() {
return name;
}
@Override
public double cost() {
return 15.0;
}
}
/**
* 绿茶类 --具体的构件,被装饰对象
* @author:dufyun
* @version:1.0.0
*/
public class GreenTea implements MilkTea {
/**
* 奶茶名字
*/
private static String name = "绿茶";
public GreenTea(){}
@Override
public String getName() {
return name;
}
@Override
public double cost() {
return 10.0;
}
}
/**
* 抽象装饰类
*
* @author:dufyun
* @version:1.0.0
*/
public abstract class MilkTeaDecorator implements MilkTea {
private MilkTea milkTea;
public MilkTeaDecorator(MilkTea milkTea){
this.milkTea = milkTea;
}
@Override
public String getName(){
return milkTea.getName();
}
@Override
public double cost() {
return milkTea.cost();
}
public abstract void addOtherMetod();
}
/**
* 果冻类-具体的装饰者,具体装饰构件
*
* @author:dufyun
* @version:1.0.0
*/
public class GuoDong extends MilkTeaDecorator {
private static String name = "果冻";
public GuoDong(MilkTea milkTea){
super(milkTea);
}
@Override
public String getName() {
return super.getName()+ ":" + name;
}
@Override
public double cost() {
return 3.0 + super.cost();
}
@Override
public void addOtherMetod() {
System.out.println("其他的增强方法");
}
}
/**
* 珍珠类-具体的装饰者,具体装饰构件
*
* @author:dufyun
* @version:1.0.0
*/
public class * extends MilkTeaDecorator {
private static String name = "珍珠";
public *(MilkTea milkTea){
super(milkTea);
}
@Override
public String getName() {
return super.getName()+ ":" + name;
}
@Override
public double cost() {
return 5.0 + super.cost();
}
@Override
public void addOtherMetod() {
System.out.println("其他的增强方法");
}
}
public class Client {
public static void main(String[] args) {
//绿茶 10.0 红茶 15.0 果冻一份 3.0 珍珠一份5.0
//这里有一个顾客A,首先点了一杯红茶,然后需要加一份果冻和一份珍珠
//被装饰者在最内层
MilkTea teaA = new GuoDong(new *(new RedTea()));
String nameA = teaA.getName();
double costA = teaA.cost();
System.out.println(nameA + " : " + costA);
//这里有一个顾客B,首先点了一杯绿茶,然后需要加两份份果冻和三份珍珠
MilkTea teaB = new GuoDong(new GuoDong( //两份果冻
new *(new *(new *(//三份珍珠
new GreenTea()))
)));
String nameB = teaB.getName();
double costB = teaB.cost();
System.out.println(nameB + " : " + costB);
}
}
红茶:珍珠:果冻 : 23.0
绿茶:珍珠:珍珠:珍珠:果冻:果冻 : 31.0
- 我: 设计模式mm。你看我这次实现的这个代码怎么样,我根据设计模式装饰者模式进行实现。
- 设计模式MM:这个实现比之前的好很多了,既不会出现类的迅速爆炸,也符合设计模式原则:开闭原则,如果新增装饰物业不用修改之前的代码。并且使用组合方式,动态的扩展对象行为,也能很好解决上面说的如果一个顾客想要两份果冻和珍珠的问题。这种设计使得系统扩展更加方便,也更容易维护,很棒!不过需要注意的是这种方式也会有很多小类,类的数量也会很多!
武功深入—Java IO中的装饰者模式
通过对装饰者的学习,发现在Java中的IO有使用到装饰者模式,那么下面我们对象Java IO 中的装饰者模式进行简单的学习。
这里只拿出Java InputStream的简单类图介绍,OutputStream等类似。(JavaIO中的输入流和输出流的设计)
- 其中 InputStream 为抽象构件,FileInputStream、ByteArrayInputStream、ObjectInputStream等是具体构件实现。
- FilterInputStream为抽象装饰类,LineInputStream、BufferedInputStream等是具体的装饰实现!
//一般在写InputStream时候
InputStream in = new BufferedInputStream(new FileInputStream(new File("d:/test.txt")));
根据对装饰者的学习,和Java Io类图的了解,编辑一个自己的输入装饰者:把输入流中的所有小写字符转成大写字符。
text.txt文件中的内容
Do you love Pattern Design!
Yes ! I love!
/**
* 自定义实现输入流程中小写字符转大写字符
*
* @author:dufyun
* @version:1.0.0
*/
public class UpperCaseInputStream extends FilterInputStream {
/**
* Creates a <code>FilterInputStream</code>
* by assigning the argument <code>in</code>
* to the field <code>this.in</code> so as
* to remember it for later use.
*
* @param in the underlying input stream, or <code>null</code> if
* this instance is to be created without an underlying stream.
*/
protected UpperCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c :Character.toUpperCase((char) c));
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int rs = super.read(b, off, len);
for (int i = off; i < off + rs; i++) {
b[i] = (byte)Character.toUpperCase((char) b[i]);
}
return rs;
}
//test 方法 ,不建议写main进行测试
public static void main(String[] args) {
try {
InputStream in = new UpperCaseInputStream(
new BufferedInputStream(
new FileInputStream(new File("d:/test.txt")))) ;
int c;
while ( (c = in.read()) >= 0){
System.out.print((char)c);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
打印结果:
DO YOU LOVE PATTERN DESIGN!
YES ! I LOVE!
java Io使用装饰者模式,也有一个“缺点”,就是在设计中会有大量的小类,数量实在太多。但是不要怕,如果你了解 了装饰者模式那么很容易辨识出装饰类是如何组织的了。
故事结局 — 装饰者模式总结
通过上面对装饰者模式的介绍,总体上对装饰者模式有了一个整体的认识。装饰者模式降低系统的耦合度,可以动态的增加或删除对象的职责,并使得需要装饰的具体构件类和具体装饰类独立变化。
优点:
- 对于扩展一个对象功能,装饰模式比继承更加灵活,并且不会导致类个数急剧增加。
- 通过一种动态的方式来扩展一个对象的功能,可以使用配置文件在运行时选择不同的具体装饰者,实现不同的行为。
- 可以对一个对象进行多次装饰,可以使用不同的排列组合,能够创建很多不同的行为,得到功能更强大的对象。
- 具体的构件和装饰类可以独立变化,当新增具体构件或者具体装饰类,原有类库代码无须修改,符合“开闭原则”
缺点:
- 在对系统进行设计的时候,会产生很多的小对象,大量的小对象产生会占用更多的系统资源,在一定程度上影响程序性能。。
- 提供一种比继承更加灵活的解决方案,但同时意味着比继承更容易出错,排错也困难。
使用场景:
- 在不影响其他对象的情况下,以动态的、透明的方式给单个对象添加职责。
- 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展的时候可以使用装饰者模式。
Next 期待下一篇吧!完成了奶茶店奶茶如何高效的实现计费,我们的奶茶店总算经过一系列努力开了起来,生意很红火,钞票不断的进入口袋。于是扩大店面,进行了装修转了很多的酷炫灯,但是遇到一个问题,如何使用遥控器控制所有的灯呢!下一篇:命令模式,封装调用。使用命令控制奶茶店中酷炫的灯!
参考
- 史上最全设计模式导学
- 《Head First 设计模式》
本专栏文章列表
一、设计模式-开篇—为什么我要去旅行? #和设计模式一起旅行#
二、设计模式-必要的基础知识—旅行前的准备 #和设计模式一起旅行#
三、设计模式介绍—她是谁,我们要去哪里? #和设计模式一起旅行#
四、单例模式—不要冒充我,我只有一个! #和设计模式一起旅行#
五、工厂模式—旅行的钱怎么来 #和设计模式一起旅行#
六、策略模式—旅行的交通工具 #和设计模式一起旅行#
七、观察者模式——关注我,分享旅途最浪漫的瞬间! #和设计模式一起旅行#
八、装饰者模式—巴厘岛,奶茶店的困扰! #和设计模式一起旅行#
九、命令模式—使用命令控制奶茶店中酷炫的灯 #和设计模式一起旅行#
十、模板方法模式—制作更多好喝的饮品! #和设计模式一起旅行#
十一、代理模式 —专注,做最好的自己!#和设计模式一起旅行#
十二、适配器模式——解决充电的烦恼 #和设计模式一起旅行#
十三、外观模式—— 简化接口 #和设计模式一起旅行#
十四、迭代器模式—— 一个一个的遍历 #和设计模式一起旅行#
十五、组合模式—— 容器与内容的一致性 #和设计模式一起旅行#
十六、状态模式—用类表示状态 #和设计模式一起旅行#
十七、访问者模式-访问数据结构并处理数据 #和设计模式一起旅行#
十八、职责链模式-推卸责任,不关我的事,我不管!#和设计模式一起旅行#
十九、原型模式—通过复制生产实例 #和设计模式一起旅行#
二十、设计模式总结—后会有期 #和设计模式一起旅行#
如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到,谢谢!
如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!
欢迎访问我的csdn博客,我们一同成长!
不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!