设计模式(三):装饰器模式

一、概述

  装饰器模式动态地将责任附加到对象上。想要扩展功能,装饰者提供了有别于继承的另一种选择。简单描述就是包装对象,让对象提供新的行为。

二、解决问题

  当一个类想要获得一个行为,我们会想到面向对象四大特性之一的继承,继承能够让子类从父类中获得行为,实现很好的代码复用。但这种继承而来的行为是在编译时静态决定的,而且所有的子类都会继承相同的行为。如果我们想要扩展对象的行为,就要创建一个子类来修改父类的方法(也就是覆盖父类行为),每扩展一个行为就要创建一个子类,这样会带来很多问题。第一,如果需要扩展的行为有很多,则子类就要创建无数多个(导致类爆炸)。第二、如果子类中的行为需要依赖某个成员变量,当这个成员变量发生改变,子类的代码就要修改(代码维护噩梦)

 

 如上图,getCost()方法获取茶的价钱,我们每新上市一种茶饮料就要创建一个子类;当茶的某些配料价格发生变化时,我们要修改子类代码。

装饰者模式就是解决以上的问题的,它利用组合的做法扩展对象行为,可以在运行时动态地进行扩展,写新的代码添加新功能,而无须修改现有代码。

 三、结构类图

四、成员角色

   1.抽象组件(Component)角色:定义一个将要接收附加责任的类,即继承该抽象类的类都有了装饰和被装饰的能力。

  2.具体组件(ConcreteComponent)角色:可以被动态加上新行为,被装饰者修饰的类。

  3.装饰者(Decorator)角色:装饰者抽象类,继承该类都具有装饰者的能力。

  4.具体装饰者(ConcreteDecorator)角色:为具体组件添加新行为。

五、应用实例

下面我们用奶茶饮料价格的例子来解析装饰者的用法,买奶茶的时候我们可以可以买纯奶茶,也可以添加珍珠,咖啡等配料,但价格肯定不一样

  //首次我们创建抽象组件,也就是茶饮料

public abstract class Tea {
	String description = "Unknown Tea";
	//茶的描述
	public String getDescription(){
		return description;
	}
	//返回茶的价钱
	public abstract double getCost();
}

  //接着创建抽象配料,就是装饰者

public abstract class CondimentDecorator extends Tea{
	//所有调料必须重新实现描述方法
	public abstract String getDescription();
}

   //创建奶茶,具体组件,继承抽象组件,被装饰的对象

public class MilkTea extends Tea{

	public MilkTea(){
		description = "奶茶";
	}
	
	@Override
	public double getCost() {
		//返回奶茶价格
		return 3.0;
	}

}

  //创建珍珠配料,就是具体的装饰者,继承抽象配料,实现对奶茶的装饰

public class Pearl extends CondimentDecorator{
	//持有对所装饰对象的引用
	private Tea tea;
	
	public Pearl(Tea tea){
		this.tea = tea;
	}
	
	@Override
	public String getDescription() {
		return "珍珠," +  tea.getDescription() ;
	}

	@Override
	public double getCost() {
		//把茶的价格加上珍珠的价格,得到最后结果
		return 1.0 + tea.getCost();
	}

}

  //创建咖啡配料,就是具体的装饰者,继承抽象配料,实现对奶茶的装饰

public class Coffee extends CondimentDecorator{
	//持有对所装饰对象的引用
	private Tea tea;
	
	public Coffee(Tea tea){
		this.tea = tea;
	}
	
	@Override
	public String getDescription() {
		//加上咖啡后的描述
		return "咖啡," + tea.getDescription();
	}

	@Override
	public double getCost() {
		//茶的价格加上咖啡的价格,算出结果
		return 2.0 + tea.getCost();
	}
}

  //测试装饰者

public class TestDecorator {
	public static void main(String[] args){
		//创建一杯纯奶茶,不需要加调料,打印出描述和价格
		Tea tea = new MilkTea();
		System.out.println(tea.getDescription() + " 价格为:¥" + tea.getCost());
		
		//创建加调料的奶茶
		Tea tea2 = new MilkTea();
		//加上一份珍珠
		tea2 = new Pearl(tea2);
		//再加一份珍珠
		tea2 = new Pearl(tea2);
		//加上一份咖啡调料
		tea2 = new Coffee(tea2);
		
		//打印加了调料的奶茶
		System.out.println(tea2.getDescription() + " 价格为:¥" + tea2.getCost());
	}
}

  运行结果:

装饰者在Java I/O中的使用

  Java I/O 是使用装饰者模式最典型的例子,看下面InputStream类图可知

 

 六、优点和缺点

  1.优点

  (1)、利用组合和委托动态地扩展行为,而无须修改现有代码。

  (2)、可以用多个装饰者包装一个对象,得到不同的对象组合,从而使被包装的类的功能更加强大。

  (3)、装饰者和被装饰者都具有相同的超类,在需要被装饰者的场合,可以用装饰过的对象替代原被装饰者。

  (4)、装饰者和被装饰者是相互独立的,我们可以根据需要增加和改变具体组件和具体装饰者,最后通过组合方式使用它们,符合“开闭原则”。

  (5)、避免了通过继承方式扩展对象功能所带来的灵活性差,子类无限扩张的问题(类爆炸)。

  2.缺点

  (1)、导致设计中出现很多小对象,如果过度使用,会让程序变得复杂。

  (2)、装饰链过多会使程序效率低,也增加了使用和排错的难度。

七、使用场景

  1、当需要动态得扩展对象行为,或者说是动态地增加功能时使用。

  2、当使用继承方式对系统功能扩展受到限制时,或者继承不利于系统扩展和维护时。

八、总结

  1、装饰者和被装饰对象有相同的超类型。

  2、可以用一个或者多个装饰者包装一个对象。

  3、可以在任何时候,使用不同的装饰者组合来装饰对象。

  4、装饰者可以在被装饰者的行为前面、后面加上自己的行为,甚至取代被装饰者的行为,达到特定的目的。

posted @ 2016-07-20 22:33  jenkinschan  阅读(1217)  评论(0编辑  收藏  举报