装饰模式
一个简陋的房子,它可以让人在里面居住,为人遮风避雨,但如果给它进行装修,那么它的居住环境就更加宜人了。程序中的对象也与房子十分类似,首先有一个相当于“房子”的对象,然后经过不断装饰,不断对其增加功能,它就变成了使用功能更加强大的对象。像这样不断的给对象添加功能的模式称为装饰模式。
一、装饰模式概述
在软件设计中,装饰模式是一种用于替代继承的技术,通过无需定义子类的方式动态地增强对象的功能,使用对象间关联关系替代类之间的继承关系。在装饰模式中,引入了装饰类,装饰类不仅可以调用原有类中的方法,还可以添加新的方法以扩展原有类的功能。
装饰模式定义:动态地给一个对象增加一些职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。
二、装饰模式结构与实现
2.1、装饰模式结构
- 装饰模式UML类图
装饰模式包含以下四个角色:
- Component(抽象构件类):该角色通常是抽象类或者接口,它是具体构件类和抽象装饰类的共同父类,声明了具体构件类需要实现的方法,它的引入可以使客户端一致地处理未被装饰的对象以及装饰之后的对象,实现对客户端的透明操作。
- ConcreteComponent(具体构件类):该角色是抽象构件类的子类,实现了在抽象构件类中声明的方法,装饰类可以给该角色添加职责。
- Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件类添加新功能,但具体功能实现交由其子类完成。它还维护了一个指向抽象构件类的引用,通过这个引用可以调用装饰前对象的原有方法,通过子类扩展这个方法,来达到给具体构件类装饰的效果。
- ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的功能。具体装饰类可以调用抽象装饰类中定义的方法,还可以添加新的方法,用以扩展对象的行为。
2.2、装饰模式实现
抽象模式中各角色的一些典型代码如下:
-
抽象构件类
抽象构件类通常是抽象类或接口,在其中声明抽象业务方法,也可以具体实现一些在具体构件类中共有的业务方法。
public abstract class Component{ public abstract void operation(); }
-
具体构件类
具体构件类实现在抽象构建类中定义的抽象业务方法,通常只是提供最基本功能的实现,复杂的功能通过装饰类来实现。
public class ConcreteComponent extends Component{ public void operation(){ // 基本功能实现 } }
-
抽象装饰类
抽象装饰类是装饰模式中的核心设计,它是抽象构件类的子类,并将抽象构件类对象聚合进来,实现了在抽象构件类中定义的方法,只不过仅仅在重写方法中调用构件对象的原有业务方法,具体的装饰过程交由子类完成。
public abstract class Decorator extends Component{ private Component component; public Decorator(Component component){ this.component = component; } public void operation(){ component.operation(); } }
-
具体装饰类
具体装饰类是抽象装饰类的子类,将继承operation方法,可以根据需要对该方法进行扩展。典型代码如下:
public class ConcreteDecorator extends Decorator{ public ConcreteDecorator(Component component){ super(component); } public void operation(){ super.operation(); // 调用原有业务方法 this.addNewBehavior(); // 调用新增业务方法 } // 新增业务方法 public void addNewBehavior(){ // ... } }
因为在抽象装饰类中注入的抽象构件类对象,因此就还可以将一个已经装饰过的对象(即具体装饰类对象)注入其中进行多层装饰。
三、应用实例
-
应用案例说明:现在有一套图形界面构件库,这个构件库提供了大量的基本构件,如窗体(Window)、文本框(TextBox)、列表框(ListBox)等,但在使用过程中,用户经常会提出一些定制需求,比如一个带滚动条的窗体、一个带黑色边框的文本框或一个既带有滚动条又带有黑色边框的列表框等。现在通过装饰模式来实现这个案例。
-
UML类图
-
示例代码:
-
Component:抽象界面构件类,充当抽象构件类的角色
public interface Component { /** * 展示界面 */ void display(); }
-
Window:窗体类,充当具体构件类角色
public class Window implements Component { @Override public void display() { System.out.println("显示窗体!"); } }
-
TextBox:文本框类,充当具体构件类角色
public class TextBox implements Component { @Override public void display() { System.out.println("显示文本框!"); } }
-
ListBox:列表框类,充当具体构件类角色
public class ListBox implements Component { @Override public void display() { System.out.println("显示列表框!"); } }
-
ComponentDecorator:构件装饰类,充当抽象装饰类角色
public abstract class ComponentDecorator implements Component { private Component component; public ComponentDecorator(Component component) { this.component = component; } /** * 仅调用抽象构建类的display方法 */ @Override public void display() { component.display(); } }
-
ScrollBarDecorator:滚动条装饰类,充当具体装饰类角色
public class ScrollBarDecorator extends ComponentDecorator{ public ScrollBarDecorator(Component component) { super(component); } @Override public void display() { this.setScrollBar(); super.display(); } /** * 装饰方法 */ public void setScrollBar(){ System.out.println("为构件添加滚动条!"); } }
-
BlackBorderDecorator:黑色边框装饰类,充当具体装饰类角色
public class BlackBorderDecorator extends ComponentDecorator{ public BlackBorderDecorator(Component component) { super(component); } @Override public void display() { this.setBlackBorder(); super.display(); } /** * 装饰方法,设置黑色边框 */ public void setBlackBorder(){ System.out.println("为构件添加黑色边框!"); } }
-
App:客户端测试类
public class App { public static void main(String[] args) { Component component, componentSB, componentBB; component = new Window(); componentSB = new ScrollBarDecorator(component); // 增强原有构件功能-->添加滚动条 componentSB.display(); System.out.println("==================="); // 继续增强-->添加滚动条,添加黑色边框 componentBB = new BlackBorderDecorator(componentSB); componentBB.display(); } }
-
执行结果
-
如果需要添加新的具体构件类或者新的具体装饰类对象,只需要将它们作为抽象构件类或抽象装饰类的子类即可,不需要改变现有系统的任何代码,符合开闭原则!
四、装饰模式分类
在上述案例中,客户端可以完全面向抽象编程,不用关心任何具体构件类或者具体装饰类,也即具体构件类或者具体装饰类对于客户端来说是透明的,因此这种装饰模式被称为透明装饰模式。其好处就是客户端可以面向抽象编程,但是在客户端想要单独调用装饰类的新增功能的场景下,这种模式就无法满足需要。这样,客户端不能一致地对待装饰前和装饰后的对象。半透明装饰模式要求装饰类的引用必须是具体装饰类类型,但因为具体构件类对象的引用仍然可以是抽象构件类类型,因此这种装饰模式就被称为半透明装饰模式。
-
透明装饰模式示例代码
Component component, componentSB, componentBB; component = new Window(); componentSB = new ScrollBarDecorator(component); // 增强原有构件功能-->添加滚动条 componentSB.display(); System.out.println("==================="); // 继续增强-->添加滚动条,添加黑色边框 componentBB = new BlackBorderDecorator(componentSB); componentBB.display();
-
半透明装饰模式示例代码
// 半透明装饰模式 Component component = new Window(); ScrollBarDecorator decoratorSB = new ScrollBarDecorator(component); decoratorSB.display(); // 单独调用具体装饰类的新增方法 decoratorSB.setScrollBar(); System.out.println("==================="); BlackBorderDecorator decoratorBB = new BlackBorderDecorator(decoratorSB); decoratorBB.display();
五、装饰模式优缺点与适用环境
装饰模式降低了系统的耦合度,可以动态的添加或删除原有对象的职责,使具体装饰类和具体构件类可以独立变化、新增,减少类爆炸,提高系统的可扩展性和可维护性,装饰模式是取代继承复用的有效方式之一。
- 优点
- 可以动态的给对象添加新功能,通过运行时选择不同的具体装饰类给对象添加不同的新功能
- 使用对象关联方式替代继承复用方式,符合合成复用原则,大大减少了类爆炸
- 可以进行多次装饰,通过对具体装饰类进行排列组合,可以创造出很多种不同的新行为
- 具体构件类和具体装饰类可以独立变化,扩展新功能时,不需要修改系统现有代码,符合开闭原则。
- 缺点
- 装饰模式会生成大量的功能类似的小对象(具体装饰类)
- 装饰模式可以进行多次装饰达到对原有对象功能的增强,但在排查、定位问题时也会特别麻烦,需要逐级排查,提高问题排查成本。
- 适用环境
- 希望以透明、动态的方式给单个对象添加职责
- 不能通过继承的方式对现有系统的类进行功能扩展时,可以考虑使用装饰模式进行功能扩展。不能使用继承方式的原因有两大类:一是系统现有大量独立的类,如果通过继承方式进行扩展,可能会出现类爆炸;二是原有类被final关键字修饰,无法修改。
六、小结
-
装饰模可以动态地给一个对象增加一些职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案
-
装饰模式包含抽象构件、具体构件、抽象装饰类和具体装饰类四个角色
-
装饰模式分为透明装饰模式和半透明装饰模式。透明装饰模式下,客户端可以一致地对待装饰前和装饰后对象;半透明装饰模式下,客户端可以单独调用具体装饰类的新功能
-
装饰模式优点是可以动态给对象添加新功能、减少系统类爆炸、扩展功能符合开闭原则。缺点是会生成大量小对象,一定程度上会影响系统的性能,还会增加系统的排查问题成本
-
当希望可以动态、透明地给对象添加功能或不能通过继承方式扩展类功能时,可以考虑使用装饰模式。