设计模式4 装饰者模式

定义

装饰模式(Decorator Pattern)是动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。——《HEAD First 设计模式》

主要角色

抽象被装饰者Component:最原始的对象,需要被装饰的。如例子里面的Beverage类(超类)。
具体被装饰者ConretetComponent:具体构件,通过继承实现Component抽象类中的抽象方法。要装饰的就是它。就像例子里面的单品咖啡。
抽象装饰者 Decorator:一个抽象类,在其属性里必然有一个private变量指向Component抽象构件。就是例子里面的各种牛奶等调料。
具体装饰者ConcreteDecorator
设计模式4 装饰者模式

举例

源自《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

图源自:设计模式(三)—— 装饰者模式
设计模式4 装饰者模式
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设计模式
设计模式(三)—— 装饰者模式
设计模式之装饰者模式
简说设计模式——装饰模式
设计模式之装饰者模式

上一篇:机器学习---吴恩达---Week3(离散问题与逻辑斯蒂回归)


下一篇:装饰者模式