定义
装饰模式(Decorator Pattern)是动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。——《HEAD First 设计模式》
主要角色
抽象被装饰者Component:最原始的对象,需要被装饰的。如例子里面的Beverage类(超类)。
具体被装饰者ConretetComponent:具体构件,通过继承实现Component抽象类中的抽象方法。要装饰的就是它。就像例子里面的单品咖啡。
抽象装饰者 Decorator:一个抽象类,在其属性里必然有一个private变量指向Component抽象构件。就是例子里面的各种牛奶等调料。
具体装饰者ConcreteDecorator
举例
源自《Head FIRST设计模式》实现咖啡店业务。
咖啡有种类:Espresso、ShortBlack(浓缩)、LongBlack、Decaf (无糖)
调料有:Milk、Soy、Chocolate ,即往基础品种中加入牛奶,巧克力等等。
首先定义Beverage类,它是所有饮料的抽象类,是需要装饰的类:
public abstract class Beverage {
public String description="Unknown Beverage"; //这个描述具体扩展出是什么单品或调料
public String getDescription()
{
return description;
}
public abstract double cost(); //这个是用抽象是因为,在单品种直接返回价格即可
那么调料类也是这样:
public class CondimentDecorator extends Beverage {
//所有的调料装饰者都必须重新实现getDescription()方法,用递归的方式来得到所选饮料的整体描述
public abstract String getDescription();
}
有了这两个基类,接下来就要实现一些饮料:
浓缩咖啡:
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
//返回Espresso(浓缩咖啡)的价格
@Override
public BigDecimal cost() {
return new BigDecimal("1.99");
}
}
深焙咖啡类:
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "DarkRoast";
}
//返回DarkRoast(深焙咖啡)的价格
@Override
public BigDecimal cost() {
return new BigDecimal("0.89");
}
}
接下来,实现一些调料
豆浆调料:
public class Soy extends CondimentDecorator {
//用一个实例变量记录饮料,也就是被装饰者
Beverage beverage;
//把饮料当成参数传进来,记录到变量中
public Soy(Beverage beverage) {
this.beverage = beverage;
}
//在原来饮料的基础上添加上Soy描述(原来的饮料加入Soy调料,被Soy调料装饰)
@Override
public String getDescription() {
return beverage.getDescription() + ",Soy";
}
//在原来饮料的基础上加上Soy的价格(原来的饮料加入Soy调料,被Soy调料装饰)
@Override
public BigDecimal cost() {
return new BigDecimal("0.3").add(beverage.cost());
}
}
同样的再定义一个奶泡调料:
public class Whip extends CondimentDecorator {
Beverage beverage;
public Whip(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ",Whip";
}
public BigDecimal cost() {
return new BigDecimal("0.42").add(beverage.cost());
}
}
这样就定义了两种基本饮料和两种调料了。在实际订单中需要一杯调料为豆浆、奶泡的深焙咖啡饮料:
public class StarbuzzCoffee {
public static void main(String[] args) {
Beverage beverage = new DarkRoast();
beverage = new Soy(beverage);
beverage = new Whip(beverage);
System.out.println("Description: " + beverage.getDescription() + " $" + beverage.cost());
}
}
运行结果为:
Description: darkroast,Soy,Whip $1.61
使用场景
当采用类继承的方式会造成类爆炸,而且不利于后期维护的情况下,可以采用装饰者模式。例如奶茶随意加调料等。
需要动态地给一个对象增加功能,并可以动态地撤销时,可以采用装饰者模式。
优缺点
优点
- 装饰类和被装饰类可以独立发展,而不会相互耦合。它有效地把类的核心职责和装饰功能分开了
- 装饰模式可以动态地扩展一个实现类的功能
- 体现了开闭原则:类应该对扩展开放,对修改关闭
缺点
多层装饰比较复杂。比如我们现在有很多层装饰,出了问题,一层一层检查
Java里面的装饰者模式实例——Java IO
图源自:设计模式(三)—— 装饰者模式
InputStream是抽象组件,需要被装饰的;FileInputStream、StringBufferInputStream、ByteArrayStream是被装饰者包起来的具体组件。
FileInputStream等三个是一些主体,其中FilterInputStream是个抽象装饰者,而下面就是一些具体装饰者。
Java IO举例
实现将输入都转成小写:
//扩展FilterInputStream
public class LowerCaseInputStream extends FilterInputStream{
protected LowerCaseInputStream(InputStream in) {
super(in);
}
//类似cost方法,实现两个read方法,一个针对字节数组,一个针对字节
public int read() throws IOException
{
int c = super.read();//调用上面super(in)的主题对象
return c == -1 ? c : Character.toLowerCase((char)(c));
}
public int read(byte[] b,int offset,int len) throws IOException//针对字节数组
{
int result = super.read(b,offset,len);
for(int i = offset;i < offset + result; i++)
{
b[i] = (byte)Character.toUpperCase((char)(b[i]));
}
return result;
}
}
那么使用这个装饰者模式:
public class InputTest {
public static void main(String[] args) {
int c;
try {
InputStream in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("test.txt")));
while((c = in.read()) >= 0)
{
System.out.print((char)c);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
参考
Head FIRST设计模式
设计模式(三)—— 装饰者模式
设计模式之装饰者模式
简说设计模式——装饰模式
设计模式之装饰者模式