一、定义
装饰模式(Decorator):动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
二、为什么使用这个模式
以对客户透明的方式动态地给一个对象附加上更多的责任,装饰模式可以在不需要创造更多的子类的情况下,将对象的功能加以扩展。
1. 需要扩展一个类的功能,或给一个类添加附加职责。
2. 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
4. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
三、模式结构
Component(抽象构件):抽象构件定义了对象的接口,可以给这些对象动态地增加职责(方法)。抽象构件是具体构件和抽象装饰类的共同父类,它声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
ConcreteComponent(具体构件):具体构件定义了具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
Decorator(抽象装饰类):抽象装饰类是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类拓展该方法,以达到装饰的目的。
ConcreteDecorator(具体装饰类):具体装饰类是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法以便扩充对象的行为。
四、例子
(1)
// Component
public abstract class Component {
public abstract void operation();
}
// ConcreteComponent
public class ConcreteComponent extends Component {
@Override
public void operation() {
System.out.println("具体对象的操作");
}
}
// Decorator
public abstract class Decorator extends Component {
protected Component component;
public void setComponent(Component component) {
this.component = component;
}
//重写operation,实际上执行的是Component类的operation()方法
public void operation() {
if(component!=null) {
component.operation();
}
}
}
// ConcreteDecoratorA
public class ConcreteDecoratorA extends Decorator {
private void AFunction() {
System.out.println("组合装饰A独有方法");
}
public void operation() {
//运行原Component的operation方法
super.operation();
//执行本类的功能,相当于对原Component方法进行了装饰
AFunction();
}
}
// ConcreteDecoratorB
public class ConcreteDecoratorB extends Decorator {
private void BFunction() {
System.out.println("组合装饰B独有方法");
}
public void operation() {
//运行原Component的operation方法
super.operation();
//执行本类的功能,相当于对原Component方法进行了装饰
BFunction();
}
}
// DemoDecorator
public class DemoDecorator {
public static void main(String[] args) {
ConcreteComponent cc=new ConcreteComponent();
ConcreteDecoratorA d1=new ConcreteDecoratorA();
ConcreteDecoratorB d2 = new ConcreteDecoratorB();
//用d1包装cc
d1.setComponent(cc);
d2.setComponent(d1);
d2.operation();
}
}
输出结果:
具体对象的操作
组合装饰A独有方法
组合装饰B独有方法
装饰模式是利用setComponent来对对象进行包装的,这样每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只需要关心自己的功能,不需要关心如何被添加到关系链中。
(2)需求:写一个可以换各种各样的衣服裤子的个人形象系统
分析:人类是Component还是ConcreteComponent呢?对于设计模式的使用要善于变通,如果只有一个ConcreteComponent类而没有抽象的Component类,那么Decorator类可以是ConcreteComponent的一个子类.同样道理,如果只有一个ConcreteDecorator类,就没必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类.在这里我们就没有必要有Component类了,直接让服饰类Decorator继承人类Person(ConcreteComponent)就可以了。
// Person
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public void show() {
System.out.println("装扮的"+name);
}
}
// Decorator
public class Decorator extends Person {
protected Person person;
public void decorate(Person person) {
this.person=person;
}
public void show() {
if(person!=null) {
person.show();
}
}
}
// TShirts
public class TShirts extends Decorator {
public void show() {
System.out.println("T恤");
super.show();
}
}
// Overcoat
public class Overcoat extends Decorator{
public void show() {
System.out.println("外套");
super.show();
}
}
// Demo
public class Demo {
public static void main(String[] args) {
Person person=new Person("蒙蒙");
TShirts tShirts=new TShirts();
Overcoat overcoat=new Overcoat();
tShirts.decorate(person);
overcoat.decorate(tShirts);
overcoat.show();
}
}
五、总结
装饰模式是为已有功能添加更多功能的一种方式。当系统需要新功能的时候,是向旧的功能添加新的代码。这些新加的的代码通常装饰了原有类的核心职责和主要行为,问题来了,这样做在原有类中加入了新的字段,方法和逻辑,从而增加了原有类的复杂性,而这些新加入的东西仅仅是为了满足一些只在某些特定情况下才会执行的特殊行为的需要。而装饰模式提供了一个很好的解决方案,它把每个需要装饰的功能放在单独的类中,并让这个类包装它所装饰的对象,因此,当需要执行特殊行为时,测试代码就可以根据需要有选择的,按顺序地使用装饰功能包装对象。
六、装饰模式的优缺点
装饰模式的优点:
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
装饰模式的缺点:
- 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
- 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
注意事项:可代替继承
扩展:当心,装饰模式的装饰顺序很重要,比如说先穿外套,再穿T恤是不行的。
另:
JAVA中IO流的设计就大量运用了装饰模式,如:
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("..")));