设计模式—— 装饰者模式
Posted 玛丽莲茼蒿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式—— 装饰者模式相关的知识,希望对你有一定的参考价值。
目录
问题描述
版本(一)
只使用继承
package HeadFirst.DecoratorPattern;
abstract public class Beverage
protected String description;
//蒸奶
protected boolean milk = false; //是否要
protected int milkAmount = 0; //要几份
protected double milkCost = 2.0; //一份多少钱
//豆浆
protected boolean soy = false;
protected int soyAmount = 0;
protected double soyCost = 1.0;
//摩卡
protected boolean mocha = false;
protected int mochaAmount = 0;
protected double mochaCost = 3.0;
//奶泡
protected boolean whip = false;
protected int whipAmount = 0;
protected double whipCost = 2.0;
public String getDescription()
return this.description;
public double cost() //计算所有小料花的价钱
double total = 0.0;
if(milk) total = total + this.milkAmount*this.milkCost;
if(soy) total = total + this.soyAmount*this.soyCost;
if(mocha) total = total + this.mochaAmount*this.mochaCost;
if(whip) total = total + this.whipAmount*this.whipCost;
return total;
//-----------运行时设置要与不要---------
public void setMilk(boolean milk)
this.milk = milk;
public void setSoy(boolean Soy)
this.soy = soy;
public void setMocha(boolean mocha)
this.mocha = mocha;
public void setWhip(boolean whip)
this.whip = whip;
//------------运行时设置要几份---------
public void setMilkAmount(int milkAmount)
this.milkAmount = milkAmount;
public void setSoyAmount(int soyAmount)
this.soyAmount = soyAmount;
public void setMochaAmount(int mochaAmount)
this.mochaAmount = mochaAmount;
public void setWhipAmount(int whipAmount)
this.whipAmount = whipAmount;
以DarkRoast.java为例
package HeadFirst.DecoratorPattern;
public class DarkRoast extends Beverage
private double cost = 12.0;
DarkRoast()
description = "DarkRoast";
public double cost() //重写父类的cost方法
return super.cost() + this.cost;
客户端点单测试:
package HeadFirst.DecoratorPattern;
public class Client
public static void main(String[] args)
//来了一位顾客,点了一倍DarkRoast,加1份蒸奶,加2份摩卡
DarkRoast darkRoast = new DarkRoast();
darkRoast.setMilk(true);
darkRoast.setMilkAmount(1);
darkRoast.setMocha(true);
darkRoast.setMochaAmount(2);
System.out.println("您需要支付:"+darkRoast.cost()+"元");
运行结果:
您需要支付:20.0元
其实这样已经挺好的了哈哈哈。
- 如果要更改咖啡涨价或者小料涨价,我们只需要打开父类或者子类修改价钱就行了(尽管这样违背了开闭原则,但是开闭原则有时候该违背就要违背!)。
- 如果要增加新的小料奥利奥Oreo,只需要打开父类,加入新的成员变量Oreo、OreoAmount、OreoCost,加入新的成员方法setOreoAmount()和setOreo(),然后在cost方法中给Oreo再加一行就行了(当然,也违背了开闭原则)
- 如果要退出咖啡新品,更简单的了,直接增加一个咖啡子类就好啦,根本不违背开闭原则
非常好的问题:为什么Duck问题只使用继承不行,而咖啡点单问题勉强可以呢?
非常好的回答:和Duck问题不同,鸭子超类每增加一种行为(比如吃玉米粒),就要重新考虑所有子类是否具备这一种行为,然后所有子类都要重写这一行为。但是咖啡点单问题特殊在,是否具备某种小料,是顾客点单时才能确定的,程序员实现咖啡子类的时候是不需要去确定的,所以不存在重写的。
版本(二)—— 装饰者模式
1. 版本(一)存在的缺点
版本(一)的设计方式存在两个缺点,我认为第二点比较严重:
- 违背了开闭原则,这个前面已经详细解释过了
- 设计死板。
- 版本(一)规定了每种咖啡的类中包含了所有的小料,哪怕这种小料根本不适合加到这种咖啡中(永远用不到)。比如咖啡店出了一款新品“红茶”,那么“奥利奥碎”这种小料就永远用不到。
- 如果顾客只点了一杯DarkRoast,什么小料都不加,
开闭原则
软件的每个地方都采用开闭原则也没有必要,这会导致代码变得复杂且难以理解。
装饰者模式可以完美解决版本(一)存在的缺点。
2. 装饰者模式
定义:动态地将装饰品附加到被装饰对象上
装饰者模式完全遵循开闭原则
实现装饰者模式的关键在于:被装饰者和装饰者继承共同的父类,尽管这并不符合现实中的逻辑(奶茶和珍珠怎么能是同一父类呢)。
应用场景:
模板类图:
- 被装饰者和装饰者继承共同的父类
- 具体的装饰类把Component的引用作为成员变量
- 里面的超类可以是抽象类也可以是接口
- 装饰者可以在被装饰者行为之前/后加上自己的行为,甚至将被装饰者行为整个取代掉
装饰者模式的优缺点
缺点:
- 装饰者会导致设计过程中出现许多小对象,如果过度使用,程序会变得很复杂。new BBBBB( new BBBB ( new BBB ( new BB ( new B()))))
3. 装饰者模式实现咖啡订单系统
下面这个图更容易理解一些,“装饰者一层层包裹上去”
抽象类 Beverage.java
abstract public class Beverage
protected String description = "Unknown";
protected double cost;
public abstract String getDescription();
public abstract double cost();
抽象类 CondimentDecorator.java
abstract public class CondimentDecorator extends Beverage
DarkRoast.java
public class DarkRoast extends Beverage
DarkRoast()
description = "DarkRoast";
cost = 0.99;
@Override
public String getDescription() //重写父类的getDescription方法
return this.description;
@Override
public double cost() //重写父类的cost方法
return this.cost;
Milk.java
public class Milk extends CondimentDecorator
Beverage beverage; // !!!
Milk(Beverage beverage) //实现Milk对咖啡的包裹
this.beverage = beverage;
this.cost = 0.1;//蒸奶0.1一份
@Override
public String getDescription()
return beverage.getDescription()+",Milk";
@Override
public double cost()
return beverage.cost() + this.cost;
Mocha.java
public class Mocha extends CondimentDecorator
Beverage beverage;
Mocha(Beverage beverage)
this.beverage = beverage;
this.cost = 0.2;
@Override
public String getDescription()
return beverage.getDescription()+",Mocha";
@Override
public double cost()
return beverage.cost()+this.cost;
客户端测试:
public class Client
public static void main(String[] args)
Beverage darkRoast = new DarkRoast(); //一杯DarkRoast
darkRoast = new Milk(darkRoast); //加一份Milk
darkRoast = new Mocha(darkRoast); //加一份Mocha
System.out.println(darkRoast.getDescription());
System.out.println("您需要支付:"+darkRoast.cost()+"元");
输出:
DarkRoast,Milk,Mocha
您需要支付:1.29元
非常好的问题:有不变的咖啡,变化的小料,为什么不能封装变化(写一个小料interface,然后小料各自实现interface成为类,以组合/依赖的方式和咖啡类组合),使用策略模式呢?
非常好的回答:因为顾客不来的话,程序员根本不知道什么小料该和什么咖啡组合啊!和策略模式不同的是,装饰者模式适用于没有稳定对象的场景,即只有在运行时才知道对象是什么样子。就拿例题来说,如果一个顾客要DarkRoust里加1份摩卡,2份蒸奶,3份豆浆,4份奶泡;另一个顾客要HouseBlend加10份蒸奶和2份奶泡,需求成千上万,谁又能全部想到呢?就算穷举一遍,哪个系统能把它们都封装成类呢?所以符合顾客需求的类/对象只能在运行时生成,也就是在【前台点单】时生成,这就是装饰者模式和策略模式的最大区别。
非常好的问题:这种场景是不是用工厂模式、生成器模式、桥接模式也能实现
回答:有待学习
装饰者模式的应用——java I/O
以InputStream为例,看看javaI/O是如何利用装饰者模式的
其实,我们之前学习java I/O的时候,“处理流包裹在节点流上”就是装饰者去包裹被装饰者。
一个非常好的例子—— 编写自己的java I/O装饰者
装饰者类LowerCaseInputStream.java
import java.io.*;
public class LowerCaseInputStream 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 LowerCaseInputStream(InputStream in) //实现LowerCaseInputStream对in的包裹
super(in);
@Override
public int read() throws IOException
int c = super.read();
if(c == -1)
return -1;
else
return Character.toLowerCase((char)c); //变小写
@Override
public int read(byte[] b, int off, int len) throws IOException
//字节怎么变成字符???
return -1;
@Override
public int read(byte[] b) throws IOException
//字节怎么变成字符???
return -1;
测试
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Client
public static void main(String[] args)
LowerCaseInputStream lowerCaseInputStream = null; //装饰
try
//1)
FileInputStream fileInputStream = new FileInputStream(new File("E:\\\\Java Project\\\\helloworld\\\\iostream\\\\UpperCase.txt"));
lowerCaseInputStream = new LowerCaseInputStream(fileInputStream);
//2)读
int data;
while((data = lowerCaseInputStream.read())!=-1)
System.out.print((char)data);
catch (IOException e)
e.printStackTrace();
finally
try
if(lowerCaseInputStream != null)
lowerCaseInputStream.close();
catch (IOException e)
e.printStackTrace();
其中,我们的输入文件UpperCase.txt里的内容是:
STUPID TEACHER!
程序输出:
以上是关于设计模式—— 装饰者模式的主要内容,如果未能解决你的问题,请参考以下文章