概述
装饰模式
是用来替代继承的一种设计模式。它通过一种无须定义子类的方式来给对象动态增加职责
,使用对象之间的关联关系取代类之间的继承关系。降低了系统的耦合,可以动态的增加或者删除对象的职责。
装饰模式:动态地给一个对象增加一些额外的职责。就扩张功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。
装饰器模式作用是针对目标方法进行增强,提供新的功能或者额外的功能。
不同于适配器模式和桥接模式,装饰器模式涉及的是单方,和代理模式相同,而且目标必须是抽象的!!。
而实际上,装饰器模式和代理模式的实现方式基本一致,只在目标的存在上有些差别。
装饰模式的UML图
不想画,借用一下
![](https://i-blog.csdnimg.cn/blog_migrate/d709fa91e654aab32e9350cb0d5259cb.png)
Component(抽象构建类)
:它是具体构件类和抽象装饰类的父类。声明规范具体构建类的业务方法。ConcreteComponent(具体构件类)
:它是抽象构建类
的子类,实现了抽象构建类
业务方法,具体装饰类
会给它增加额外的功能。Decorator(抽象装饰类)
:它也是抽象构建类
的子类,继承或实现抽象构件。它维护一个指向抽象构建对象的引用,通过该引用可以先调用装饰之前构建对象的方法,并通过其子类扩展该方法,已达到装饰的目的。ConcreteDecorator(具体装饰类)
:它是抽象装饰类
的子类,实现抽象装饰类
的相关方法,负责给具体构件类
添加新的功能。每一个具体的装饰类都定义了一个具体的行为。
代码演示
1、抽象构建类Component
package 装饰器;
public interface Component {
public void display();
}
2、具体构件类ConcreteComponent,实现接口Component
package 装饰器;
public class ConcreteComponent implements Component {
@Override
public void display() {
System.out.println("ConcreteComponent");
}
}
3、抽象装饰类Decorator,实现接口Component
package 装饰器;
public class Decorator implements Component{
/* 维护一个指向抽象构建对象的引用 */
public Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void display() {
/* 先调用传参装饰类对象的方法,再执行本具体装饰类的方法 */
component.display();
System.out.println(component + "+++" + "Decorator");
}
}
4.1、具体装饰类ConcreteDecoratorA,继承Decorator
package 装饰器;
public class ConcreteDecoratorA extends Decorator {
/* 具体装饰类先构造父类属性 */
public ConcreteDecoratorA(Component component) {
super(component);
}
public void display() {
/* 先调用传参装饰类对象的方法,再执行本具体装饰类的方法 */
super.display();
System.out.println("ConcreteDecoratorA");
}
}
4.2、具体装饰类ConcreteDecoratorB,继承Decorator
package 装饰器;
public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
public void display() {
super.display();
System.out.println("ConcreteDecoratorB");
}
}
5、调用类Client
package 装饰器;
public class Client {
public static void main(String[] args) {
/* 创建具体构件类ConcreteComponent */
Component component = new ConcreteComponent();
component.display();
System.out.println("-------");
/* 创建具体装饰类,给具体构件类装饰功能 */
Component component1 = new ConcreteDecoratorA(component);
component1.display();
System.out.println("-------");
Component component2 = new ConcreteDecoratorB(component1);
component2.display();
System.out.println("-------");
}
}
运行结果:
ConcreteComponent
-------
ConcreteComponent
装饰器.ConcreteComponent@60e53b93+++Decorator
ConcreteDecoratorA
-------
ConcreteComponent
装饰器.ConcreteComponent@60e53b93+++Decorator
ConcreteDecoratorA
装饰器.ConcreteDecoratorA@5e2de80c+++Decorator
ConcreteDecoratorB
-------
分析结果1:
- 构造过程:
Component component1 = new ConcreteDecoratorA(component)
先调用 ConcreteDecoratorA 构造方法,执行super(component)
调用父类 Decorator 的构造方法,执行this.component = component
,将传入参数 component 赋值给父类属性 component。 - 调用过程:执行
component1.display()
时,其方法第一步super.display()
执行父类Decorator::display()
。而父类 Decorator 的方法 display() 第一步component.display()
,执行传参的 component 的方法 display()。故依次打印ConcreteComponent --> 装饰器.ConcreteComponent@60e53b93+++Decorator --> ConcreteDecoratorA
。
分析结果2:
- 构造过程:
Component component2 = new ConcreteDecoratorB(component1)
先调用 ConcreteDecoratorB 构造方法,其构造方法super(component)
调用父类 Decorator 的构造方法this.component = component
,将传入参 component1 赋值给父类属性 component。 - 调用过程:执行
component2.display()
时,其方法第一步super.display()
执行父类Decorator::display()
。而父类 Decorator 的方法 display() 第一步component.display()
执行上述传参的 component1 的方法 display()。这一步流程就是上面 “执行component1.display(); ”的流程。 即打印ConcreteComponent --> 装饰器.ConcreteComponent@60e53b93+++Decorator --> ConcreteDecoratorA
。然后再打印装饰器 ConcreteDecoratorB 的流程:ConcreteDecoratorA@5e2de80c+++Decorator ,再打印ConcreteDecoratorB
。
注:装饰的调用有点类似递归,形成一个调用链
。
需求:如果想复用具体装饰类 ConcreteDecoratorA 的既有功能,怎么处理? (该需求可以参见《SpringMVC-快速入门(6.2)- 自定义过滤器》里复用HttpServletRequestWrapper原有功能,然后自定义修改逻辑)
解决方案:编写装饰类继承ConcreteDecoratorA
package 装饰器;
public class ConcreteDecoratorD extends ConcreteDecoratorA {
public ConcreteDecoratorD(Component component) {
super(component);
}
public void display() {
super.display();
System.out.println("ConcreteDecoratorD");
}
}
新增
ConcreteDecoratorD
继承ConcreteDecoratorA
。
调用类Client:
package 装饰器;
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
component.display();
System.out.println("-------");
Component component1 = new ConcreteDecoratorA(component);
component1.display();
System.out.println("-------");
/*Component component2 = new ConcreteDecoratorB(component1);
component2.display();
System.out.println("-------");*/
/* ConcreteDecoratorD 继承 ConcreteDecoratorA */
Component component3 = new ConcreteDecoratorD(component);
component3.display();
System.out.println("-------");
Component component4 = new ConcreteDecoratorD(component1);
component4.display();
System.out.println("-------");
}
}
运行结果:
ConcreteComponent
-------
ConcreteComponent
ConcreteDecoratorA
-------
ConcreteComponent
ConcreteDecoratorA
ConcreteDecoratorD
-------
ConcreteComponent
ConcreteDecoratorA
ConcreteDecoratorA
ConcreteDecoratorD
-------
结果可以看出,ConcreteDecoratorD.display()执行之前均会先执行其父类ConcreteDecoratorA.display()。
优点
- 可以通过一种动态的方式扩张一个类的功能。
- 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合可以创造不同的行为的组合。
- 对于扩张一个对象的功能,装饰模式比继承模式更加灵活,不会导致类的数量急剧增加。
缺点
- 因为增加装饰类,是必产生一些小对象,而这些小对象属性、方法差不多,会使系统复杂性增加。
- 装饰模式是一种比继承更加灵活的解决方案。但同时,也意味着比继承更加容易出错,更难排查问题。
对于多层装饰的对象,需要逐级排查,较为繁琐
。
使用场景:
- 在不影响其他对象的情况下以动态的,透明的方式给单个对象添加职责。
- 不能用继承进行扩张的时候。
与代理的区别
装饰类的代码编写和代理很类似,具体区别如下:
- 代理是全权代理,被代理类根本不对外,全部由代理类来完成。
- 装饰是增强,是辅助,被代理类仍然可以自行对外提供服务,装饰器只起增强作用。
- 代理的目标也可以是类,装饰目标必须是接口。
![](https://i-blog.csdnimg.cn/blog_migrate/54816424c86489ad2fbfdbfe77cb9c4a.png)
实际案例
快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,加配菜需要额外加钱,并且每个配菜的价钱不一样,计算快餐价格如何实现?
继承的方式:
![](https://i-blog.csdnimg.cn/blog_migrate/00e37b62cd72b9cdec241a18cebf58a9.png)
这是继承的方式,但是横向扩展性不好:如果要再加一种配料(火腿肠),我们就会发现需要给FriedRice和FriedNoodles分别定义一个子类。如果要新增一个快餐品类(炒河粉)的话,就需要定义更多的子类,会出现类爆炸的问题。
装饰者模式:
![](https://i-blog.csdnimg.cn/blog_migrate/625841a20f1beb12b21a96b2c6cf392b.png)
代码实现
1、抽象构件FastFood:
package 装饰器;
public interface FastFood {
public float getCountPrice();
public String getDesc();
}
2、具体构件RiceFried(炒饭):
package 装饰器;
public class RiceFried implements FastFood {
public float price = 10.0f;
public String desc = "炒饭";
@Override
public float getCountPrice() {
return price;
}
@Override
public String getDesc() {
return desc;
}
}
3、抽象装饰:
package 装饰器;
public class FoodDecorator implements FastFood {
public FastFood fastFood;
public FoodDecorator(FastFood fastFood) {
this.fastFood = fastFood;
}
@Override
public float getCountPrice() {
float countPrice = fastFood.getCountPrice();
return countPrice;
}
@Override
public String getDesc() {
String desc = fastFood.getDesc();
return desc;
}
}
4.1、具体装饰EggDecrator(鸡蛋)
package 装饰器;
public class EggDecrator extends FoodDecorator {
public float price = 2.0f;
public String desc = "鸡蛋";
public EggDecrator(FastFood fastFood) {
super(fastFood);
}
public float getCountPrice() {
/* 获取父类执行结果 */
float countPrice = super.getCountPrice();
/* 加上本身价格 */
countPrice += price;
return countPrice;
}
public String getDesc() {
String desc = super.getDesc();
desc = this.desc + desc;
return desc ;
}
}
4.2、具体装饰BaconDecrator(培根)
package 装饰器;
public class BaconDecrator extends FoodDecorator{
public float price = 3.0f;
public String desc = "培根";
public BaconDecrator(FastFood fastFood) {
super(fastFood);
}
public float getCountPrice() {
/* 获取父类执行结果 */
float countPrice = super.getCountPrice();
/* 加上本身价格 */
countPrice += price;
return countPrice;
}
@Override
public String getDesc() {
String desc = super.getDesc();
desc = this.desc +desc ;
return desc ;
}
}
5、调用:
package 装饰器;
public class Shopping {
public static void main(String[] args) {
FastFood RiceFried = new RiceFried();
System.out.println(RiceFried.getDesc() + ":" + RiceFried.getCountPrice());
FastFood eggDecrator = new EggDecrator(RiceFried);
System.out.println(eggDecrator.getDesc() + ":" + eggDecrator.getCountPrice());
BaconDecrator baconDecrator = new BaconDecrator(RiceFried);
System.out.println(baconDecrator.getDesc() + ":" + baconDecrator.getCountPrice());
BaconDecrator baconDecrator2 = new BaconDecrator(eggDecrator);
System.out.println(baconDecrator2.getDesc() + ":" + baconDecrator2.getCountPrice());
}
}
运行结果:
炒饭:10.0
鸡蛋炒饭:12.0
培根炒饭:13.0
培根鸡蛋炒饭:15.0
如果要再加一种配料(火腿肠),我们只需要定义一个修饰类。
如果要新增一个快餐品类(炒河粉)的话,我们只需要定义一个具体构件类。