写在前面的话
复习、总结23种设计模式
上一篇
装饰模式
记重点
如果你不想在修改原有类的基础上增强新的功能,那就使用装饰模式吧
定义
简单来说,就是用一系列包装类来增强原始类的功能,而不是直接修改原始类。
它是一种结构型设计模式。在该模式中,我们使用了一个装饰器类,该类将原始类包装在其内部,并提供与原始类相同的接口。由于该类遵循同样的接口,因此无论是使用原始类还是装饰器类,都可以正常工作。
装饰器模式通常用于以下两种情况:
- 当你需要透明地添加功能时,而不希望增加对象子类来实现所需的功能时,可以使用该模式。
- 当需要动态地为对象添加功能时,可以使用装饰器模式。
装饰器模式分为四个部分:接口、具体组件、装饰器、具体装饰器。其中,接口定义了组件和装饰器的公共行为,具体组件实现了组件接口,并承担了被装饰的原始类,装饰器实现了组件接口并包装了具体组件,具体装饰器继承了装饰器并添加了一些额外的行为。
装饰模式的使用场景
装饰器模式在以下三种情况下非常适用。
-
需要动态地添加对象功能
在不影响其他对象的情况下添加新功能。你可以使用装饰器来改变对象的行为,而不必修改底层对象。这种方式比继承更加灵活,因为它允许你在运行时添加或删除功能。
-
需要透明地添加对象功能
装饰器模式允许你以透明的方式为对象添加功能。这意味着你可以将多个装饰器一起使用,而不必担心它们如何相互作用。因为装饰器遵循相同的接口,因此你可以在不了解其内部实现的情况下使用它们。
-
需要保持类简单、干净
如果你的类有太多的责任,那么你可以使用装饰器模式来分离这些责任。这将使你的代码更加容易维护和测试。装饰器允许你将一些特定的责任委托给单独的类,以便你可以更好地关注原始类的核心功能。
装饰模式的通用类图
在类图中有四个角色需要说明
Component抽象构建
Component是一个接口或抽象类,就是定义我们最核心的对象,也就是最原始的对象
//抽象构件角色
public interface Component {
void operation();
}
ConcreteComponent具体构建
ConcreteComponent是最核心、最原始、最基本的接口或抽象类的实现,我们要装饰的就是它
//具体构件角色
public class ConcreteComponent implements Component {
public ConcreteComponent() {
System.out.println("创建具体构件角色");
}
@Override
public void operation() {
System.out.println("调用具体构件角色的方法operation()");
}
}
Decorator装饰角色
一般是一个抽象类,用什么用呢?实现接口或抽象方法,它里面不一定有抽象的方法,但在它的属性中必然有一个private变量指向Component抽象构建
//抽象装饰角色
public class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
ConcreteDecorator具体装饰角色
//具体装饰角色
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedFunction();
}
public void addedFunction() {
System.out.println("为具体构件角色增加额外的功能addedFunction()");
}
}
DEMO-1
需要被装饰的目标接口
package com.design.pattern.decorator.test02;
public interface Person {
void run();
}
// 一个很普通的人,没有经过装饰过的
class Man implements Person {
@Override
public void run() {
System.out.println("我终于跑起来了");
}
}
装饰器
package com.design.pattern.decorator.test02;
public abstract class PersonDecorator implements Person {
private Person person;
public PersonDecorator(Person person) {
this.person = person;
}
@Override
public void run() {
person.run();
}
}
// 使用飞机来装饰这个人
class PersonWithPlaneDecorator extends PersonDecorator {
public PersonWithPlaneDecorator(Person person) {
super(person);
}
@Override
public void run() {
System.out.println("在我成为飞行员后");
super.run();
}
}
测试类
package com.design.pattern.decorator.test02;
public class Client {
public static void main(String[] args) {
// 我们要装饰的目标对象
Person man = new Man();
// 1次装饰
PersonDecorator decorator1 = new PersonWithPlaneDecorator(man);
// 2次装饰
decorator1 = new PersonWithPlaneDecorator(decorator1);
decorator1.run();
}
}
最佳实践
装饰模式是对继承的有力补充。你要知道继承不是万能的,继承可以解决实际的问题,但是在项目中你要考虑诸如易维护、易扩展、易复用等,而且在一些情况下(比如上面那个成绩单例子)你要是用继承就会增加很多子类,而且灵活性非常差,那当然维护也不容易了,也就是说装饰模式可以替代继承,解决我们类膨胀的问题。同时,你还要知道继承是静态地给类增加功能,而装饰模式则是动态地增加功能,在上面的那个例子中,我不想要SortDecorator这层的封装也很简单,于是直接在Father中去掉就可以了,如果你用继承就必须修改程序。
装饰模式还有一个非常好的优点:扩展性非常好。在一个项目中,你会有非常多的因素考虑不到,特别是业务的变更,不时地冒出一个需求,尤其是提出一个令项目大量延迟的需求时,那种心情是相当的难受!装饰模式可以给我们很好的帮助,通过装饰模式重新封装一个类,而不是通过继承来完成,简单点说,三个继承关系Father、Son、GrandSon三个类,我要在Son类上增强一些功能怎么办?我想你会坚决地顶回去!不允许,对了,为什么呢?你增强的功能是修改Son类中的方法吗?增加方法吗?对GrandSon的影响呢?特别是GrandSon有多个的情况,你会怎么办?这个评估的工作量就够你受的,所以这是不允许的,那还是要解决问题的呀,怎么办?通过建立SonDecorator类来修饰Son,相当于创建了一个新的类,这个对原有程序没有变更,通过扩展很好地完成了这次变更。