装饰者模式
定义
装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种结构型设计模式。
从其定义可以看出,装饰者模式起到扩展功能的作用,也是一种替代继承的技术,是合成复用原则的一种体现。
以按钮举例:可以利用装饰者装修其大小,文本设置,背景等等。
举例说明
某公司要开发一套图形界面构件库,提供大量基本控件,如窗体、文本框、列表框等等。但是在使用该控件库时,用户经常要求定制一些特效显示效果,如滚动条,带黑边框的文本框等等,我们先看版本1实现方式。
版本1-使用继承实现
我们在这种实现方案上:实现带滚动条的方式是继承Window窗口类,然后在子类中实现滚动条相关的方法,其他的控件以及功能也是通过继承实现的。
上述方法的缺点很明显:子类数量过多,试想要扩充新功能,比如更换一个背景颜色呢?每个控件还得再继承一个控件,很不雅观,因此使用装饰者模式产生版本2
版本2-使用装饰者模式实现
先看下装饰者模式的原理图:
我们理解下上图:
- 首先Component是顶层抽象类,是一切类的父类
- 具体控件,比如版本1中的Window对应本图中的ConcreteComponent。
- Decorator继承Component,并且其中有Component类型的成员变量
- ConcreteDecoratorA, ConcreteDecoratorB继承了Decorator,为具体装饰者对象,实现为Component对象增加额外的职责。
结合装饰者模式,我们给出上述问题的更好的解决方案
根据上面的讲解,ScrollBarDecorator是实现滚动条功能的关键,下面看下代码,如何实现将一个窗体加上滚动条:
class ScrollBarDecorator extends ComponentDecorator
{
public ScrollBarDecorator(Component component)
{
super(component);
}
public void display()
{
this.setScrollBar();
super.display();
}
public void setScrollBar()
{
System.out.println("为构件增加滚动条!");
}
}
两类装饰者模式
透明装饰模式
在透明装饰模式中,要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该将对象声明为具体构件类型或具体装饰类型,而应该全部声明为抽象构件类型。对于客户端而言,具体构件对象和具体装饰对象没有任何区别。也就是应该使用如下代码:
Component c, c1;
c=new ConcreteComponent();
c1=new ConcreteDecorator(c);
c1.display();
非透明装饰模式
透明装饰模式的设计难度较大,而且有时我们需要单独调用新增的业务方法。为了能够调用到新增方法,我们不得不用具体装饰类型来定义装饰之后的对象,而具体构件类型还是可以使用抽象构件类型来定义,这种装饰模式即为半透明装饰模式,也就是说,对于客户端而言,具体构件类型无须关心,是透明的;但是具体装饰类型必须指定,这是不透明的。
非透明模式不能实现对同一对象的多次装饰,看个例子。
Component c;
c=new ConcreteComponent();
ConcreteDecorater c1;
c1=new ConcreteDecorater(c);
//c1中实现了新的方法
思考为什么非透明装饰模式不能实现一个对象的多次装饰呢?
- 上述代码中我标注出了 c1中实现了新的方法,也即上述定义中粗体部分—为了能够调用到新增的方法。
- c1中具有Component所没有的方法。如果想要实现多次装饰的话,那么其他装饰类中需要知道c1中实现的新方法是什么。
- 问题是所有装饰类都是相对独立的,如果让装饰类都互相知道各自实现了哪些新方法,那么装饰者将失去了通用性。
- 即一个对象装饰之后,我们要对他区别对待了,因为加上了新方法。
- 所以不能实现多次装饰,是因为我们要保证装饰类的通用性和独立性。
总结
注意事项
- 尽量保持装饰类的接口和被装饰类的接口保持相同,这样可以将修饰之前的对象和修饰之后的对象一致对待,即尽可能使用透明装饰模式。
- 尽量保持具体构建类是一个小类,不要把太多职责放进去,我们尽可能依靠装饰者对其功能进行扩展,这样能提高具体类的可复用性。
优点
- 对于扩展一个对象的功能,装饰者模式比继承更加灵活,不会导致类的个人急剧增加。
- 可以通过一种动态的方式来扩展一个对象的功能,再配合配置文件可以在运行时选择不同的装饰类,从而实现不同的行为。
- 可以实现对一个对象的多次装饰。
- 具体构建类和具体装饰类可以独立变化,用户可以根据需要增加新的具体构建类和具体装饰类,原有代码无需改变。
缺点
- 会产生很多小对象,大量小对象会影响程序的性能。
- 提供了比继承更灵活的解决方案,但是比继承更加容易出错,排错也很困难。
适用场景
- 在不影响其他对象的情况下,动态,透明的给单个对象添加职责。
- 不能采用继承来进行功能扩展,为何不能采用继承呢?一是因为组合关系导致产生大量子类情况。二是Java中的final类不可被继承。