您现在的位置是:首页 >学无止境 >设计模式学习、装饰器模式网站首页学无止境
设计模式学习、装饰器模式
一、简介
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许我们通过将对象放入包含行为的特殊包装中来为原对象增强功能,同时又不改变其结构。这种方式既灵活又可扩展,因此在实际开发中非常常见。
在装饰器模式中,通常会有一个抽象组件(Component)定义了一个基本的操作方法,然后有一个具体组件(ConcreteComponent)实现该操作方法。此外,还有一个抽象装饰器(Decorator)定义了一个与组件相同的接口,并维护了一个指向组件对象的指针,而具体装饰器(ConcreteDecorator)则对组件进行了装饰,增加了额外功能。
二、什么是装饰器模式
先看下装饰器的UML图:
具体组件介绍:
- 抽象组件(Component):定义一个抽象接口,用于规范核心组件(Concrete Component)和装饰器(Decorator)共同遵循的接口。
- 具体组件(Concrete Component):实现抽象组件接口,并定义了一个具有基本功能的对象。
- 抽象装饰器(Decorator):继承或实现抽象组件接口,并且持有一个抽象组件实例,在内部可以通过调用抽象组件接口方法来完成基础功能,并在此基础之上添加新的功能。
- 具体装饰器(Concrete Decorator):实现抽象装饰器接口,并且持有一个抽象组件实例。在内部不仅可以通过调用抽象组件接口方法来完成基础功能,还可以添加自己的新功能,或者修改原有的功能。
具体用代码展示下:
抽象组件(Component)
public interface Component{
void methodA();
void methodB();
}
具体组件(Concrete Component)
public class ConcreteComponent implements Component {
@Override
public void methodA() {
//do something...
}
@Override
public void methodB() {
//do something...
}
}
抽象装饰器(Decorator)
public abstract class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void methodA() {
//调用被装饰对象的方法
component.methodA();
}
@Override
public void methodB() {
//调用被装饰对象的方法
component.methodB();
}
}
具体装饰器(Concrete Decorator)
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Decorator decorator) {
super(decorator);
}
@Override
public void methodA() {
//调用被装饰对象的方法
super.methodA();
//do something...
}
@Override
public void methodB() {
//do something...
//调用被装饰对象的方法
super.methodB();
}
}
三、举个实际例子
就拿我经常吃的鸡蛋灌饼举个例子(得控制不能多吃,会长脂肪肝),我们去买鸡蛋灌饼的时候,有的人喜欢加烤肠,有的人喜欢加里脊肉,有的人喜欢都不加,而老板可以根据大家不同的需求,去添加不同的配菜,最后结算的价格也不一样。
那么如何用代码来设计这个鸡蛋灌饼呢,一般来说我们会定义一个类来表示鸡蛋灌饼,然后再定义一个加烤肠的鸡蛋灌饼,再定义一个加里脊的鸡蛋灌饼,如果有的人既要加烤肠又要加里脊,那就还需要再定义一个加烤肠加里脊的鸡蛋灌饼,看似满足了需求,但是如果以后老板推出新的配菜,比如韭菜、杭椒,那么排列组合方式就更多了,子类会爆发式增长。
这时候我们应该换一个思路,把鸡蛋灌饼当做主体,烤肠、里脊肉这些当做它的装饰,把它一层层包装起来。这就是装饰器模式。
先画一个UML图看下:
下面来写代码实现一下
Pancake:相当于抽象组件(Component),来定义鸡蛋灌饼和装饰器类的行为
public interface Pancake {
String description();
int cost();
}
EggFilledPancake:相当于具体组件(Concrete Component),表示鸡蛋灌饼
public class EggFilledPancake implements Pancake {
@Override
public String description() {
return "鸡蛋灌饼";
}
@Override
public int cost() {
return 6;
}
}
Decorator:抽象装饰器
public abstract class Decorator implements Pancake {
private Pancake pancake;
public Decorator(Pancake pancake) {
this.pancake = pancake;
}
@Override
public String description() {
return pancake.description();
}
@Override
public int cost() {
return pancake.cost();
}
}
GrilledSausage:具体装饰器(Concrete Decorator),表示加烤肠
public class GrilledSausage extends Decorator {
public GrilledSausage(Pancake pancake) {
super(pancake);
}
@Override
public String description() {
return super.description() + ",加烤肠";
}
@Override
public int cost() {
return super.cost() + 1;
}
}
Tenderloin:具体装饰器(Concrete Decorator),表示加里脊
public class Tenderloin extends Decorator {
public Tenderloin(Pancake pancake) {
super(pancake);
}
@Override
public String description() {
return super.description() + ",加里脊";
}
@Override
public int cost() {
return super.cost() + 2;
}
}
最后写个测试类调用下
public class DecoratorDemo {
public static void main(String[] args) {
Pancake pancake = new EggFilledPancake();
//第一次装饰,加烤肠,加1块
pancake = new GrilledSausage(pancake);
//第二次装饰,加里脊,加2块
pancake = new Tenderloin(pancake);
//最后结算
System.out.println(pancake.description());
System.out.println(pancake.cost() + "元");
}
}
运行后,控制台打印如下:
鸡蛋灌饼,加烤肠,加里脊
9元
四、应用
在Mybatis框架里创建 Executor 时就用到了装饰器模式
通过判断 cacheEnabled 参数判断是否需要在原本的 executor 上再用 CachingExecutor 装饰一下,这就是Mybatis的缓存机制,有兴趣的可以去了解下,下面放上 Executor 的类图。
五、总结
装饰器模式作为一种经典的设计模式,有优点的同时,也有很多缺点
优点:
- 装饰器模式可以动态地扩展对象的功能。通过给原始对象添加一个或多个装饰器,可以在不修改原始对象的情况下增加新的行为,从而满足各种业务需求。
- 装饰器模式实现了代码的松耦合。由于装饰器和被装饰者都是基于同一接口或抽象类的,因此它们之间的关系非常灵活,可以随时替换或升级。
- 装饰器模式遵循开闭原则。新增或删除装饰器对客户端代码的影响非常小,因此系统的可扩展性和可维护性较高。
缺点:
- 装饰器模式增加了系统的复杂度。由于装饰器会嵌套使用,因此难以逐级理解装饰器的逻辑。
- 装饰器模式可能会导致多个小对象的创建。每增加一个装饰器,就需要创建一个新的对象,这会导致系统的开销增大。
- 装饰器模式对高层模块的透明性较低。装饰器模式会在被装饰的对象外部增加一个包装器,从而增加了高层模块对对象的理解和使用难度。