装饰器模式主要用于对象的功能扩展。我们使用装饰类动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性。装饰器的核心就是再不改原有类的基础上给类新增功能,并且不改变原有类。
一般我们提到对象功能扩展,很大一部分都会联想到使用继承,或者AOP的方式。我们使用装饰器模式来设计程序,不仅能避免AOP的复杂,还能解决继承不断横向扩展导致子类膨胀的问题,我们不在考虑大量子类的维护了。
装饰模式一共有四部分组成:
- 抽象组件角色(Component):定义一个对象接口,以规范准备接受附加责任的对象,即可以给这些对象动态地添加职责。
- 具体组件角色(ConcreteComponent) :被装饰者,定义一个将要被装饰增加功能的类。可以给这个类的对象添加一些职责。
- 抽象装饰器(Decorator):维持一个指向构件Component对象的实例,并定义一个与抽象组件角色Component接口一致的接口。
- 具体装饰器角色(ConcreteDecorator):向组件添加职责。
=========================================================================
我们模拟一个往主板装硬件的过程:
我们再一个主板上装载内存,显卡等硬件,并且打印总价以及配件单。
首先定义一个硬件的超类,所有的不论是主板还是显卡,内存都属于硬件。也就是抽象组件角色:
package DecoratorPattern;
import java.math.BigDecimal;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Computer.java
* @Description 电脑类,超类
* @createTime 2022年03月01日 17:28:00
*/
public abstract class HardWare {
protected String name;
private BigDecimal price;
public String getName() {
return name;
}
public void setHardWareList(String name) {
this.name = name;
}
public BigDecimal getPrice()
{
return price;
}
public void setPrice(BigDecimal price)
{
this.price=price;
}
/**
* 获取总价
* @return 价格
*/
public abstract BigDecimal cost();
/**
* 获取配置单
* @return 配置单
*/
public abstract String hardWareList();
}
然后定义一个主板类,将会被其他硬件修饰的主体,也就是具体组件角色:
package DecoratorPattern;
import java.math.BigDecimal;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName DellComputer.java
* @Description 主板类
* @createTime 2022年03月02日 08:38:00
*/
public class MotherBoard extends HardWare {
@Override
public BigDecimal cost() {
return super.getPrice();
}
@Override
public String hardWareList() {
return super.getName();
}
}
然后构建一个抽象装饰器,所有的装饰类都要被这个类所规范
package DecoratorPattern;
import java.math.BigDecimal;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Decorator.java
* @Description 硬件装饰类
* @createTime 2022年03月02日 09:36:00
*/
public class Decorator extends HardWare{
private HardWare obj;
public Decorator( HardWare obj){
this.obj = obj;
}
/**
* 生成总价
* @return 总价
*/
@Override
public BigDecimal cost() {
return super.getPrice().add(obj.cost());
}
/**
* 生成配件表
* @return 配件表
*/
@Override
public String hardWareList() {
return super.getName()+"\n"+obj.name;
}
}
然后添加两个具体装饰器角色,也就是我们即将放上主板的内存和显卡:
显卡:
package DecoratorPattern;
import java.math.BigDecimal;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName GraphicCard.java
* @Description 显卡装饰
* @createTime 2022年03月02日 10:07:00
*/
public class GraphicCard extends Decorator{
public GraphicCard(HardWare obj) {
super(obj);
super.setHardWareList("ROC 3090");
super.setPrice(BigDecimal.valueOf(12999));
}
}
内存:
package DecoratorPattern;
import java.math.BigDecimal;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Memory.java
* @Description 内存装饰
* @createTime 2022年03月02日 09:46:00
*/
public class Memory extends Decorator{
public Memory(HardWare obj) {
super(obj);
super.setHardWareList("TridentZ Royal");;
super.setPrice(BigDecimal.valueOf(3200));
}
}
测试一下:
package DecoratorPattern;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Computer.java
* @Description 打印配置单
* @createTime 2022年03月02日 09:47:00
*/
public class Computer {
public static void main(String[] args) {
HardWare list;
list = new ROGSTRIXZ690();
System.out.println("主板价格: "+list.getPrice());
list = new Memory(list);
System.out.println("内存价格: "+list.getPrice() );
list = new GraphicCard(list);
System.out.println("显卡价格: "+list.getPrice());
System.out.println("总价:"+list.cost());
System.out.println("配件单: \n"+list.hardWareList());
}
}
优点:
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”
缺点:
- 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
- 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。