概述
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。它是作为现有的类的一个包装。
若要扩展一个对象的功能,装饰器模式提供了比继承更有弹性的替代方案。组合优于继承。
何时使用:
- 需要扩展一个类的功能,或给一个类增加附加责任。
- 需要动态的给一个对象增加功能,这些功能可以再动态地撤销。
- 需要增加一些基本功能的排列组合而产生的非常大量的功能,从而使继承变得不现实。
UML 类图:
角色组成:
- 抽象构件角色(Component): 定义可以动态添加任务的对象的接口
- 具体构件角色(ConcreteComponent):定义一个要被装饰器装饰的对象,即 Component 的具体实现
- 抽象装饰器(Decorator): 持有一个构件(Conponent)对象的实例,并定义一个和抽象构件一致的接口。维护对组件对象和其子类组件的引用
- 具体装饰器角色(ConcreteDecorator):向组件添加新的职责
通用代码
抽象构件角色:
public interface Component {
void operation();
}
具体构件角色:
public class ConcreteComponent implements Component {
@Override
public void operation() {
// 具体业务代码
System.out.println("ConcreteComponent 通用基础逻辑");
}
}
抽象装饰角色:
public abstract class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
// 委派给构件
component.operation();
}
}
具体装饰角色:
public class ConcreteDecorationA extends Decorator {
public ConcreteDecorationA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
decorateMethod();
}
// 定义自己的修饰逻辑
private void decorateMethod() {
System.out.println("ConcreteDecorationA 的修饰逻辑");
}
}
public class ConcreteDecorationB extends Decorator {
public ConcreteDecorationB(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
decorateMethod();
}
// 定义自己的修饰逻辑
private void decorateMethod() {
System.out.println("ConcreteDecorationB 的修饰逻辑");
}
}
使用:
public class Test {
public static void main(String[] args) {
Component component = new ConcreteComponent();
Decorator decoratorA = new ConcreteDecorationA(component);
decoratorA.operation();
}
}
结果:
ConcreteComponent 通用基础逻辑
ConcreteDecorationA 的修饰逻辑
看到这里,相信大家对装饰器模式的通用代码结构清楚了许多,下面我们通过一个实际使用案例来进一步诠释。
实例
相信大家都喝过奶茶,奶茶的种类有很多种,比如有椰香奶茶、QQ奶茶,巧克力奶茶等,奶茶中又可以添加椰果、红豆、布丁等不同的甜品。奶茶店现在要卖各种口味的奶茶,如果不使用装饰模式,那么在销售系统中,各种不一样的奶茶都要产生一个类,如果有5种奶茶类,5种甜品,那么就会产生至少25个类(不包括混合口味),如果使用了装饰模式,那么几个类就可以搞定了。
奶茶接口类
public interface MilkTea {
// 返回奶茶描述
String getDescription();
// 返回价格
double getPrice();
}
具体的奶茶类:巧克力奶茶、QQ奶茶
public class ChocolateMT implements MilkTea{
private String description = "巧克力奶茶";
@Override
public String getDescription() {
return description;
}
@Override
public double getPrice() {
return 15;
}
}
public class QQMT implements MilkTea {
private String description = "QQ奶茶";
@Override
public String getDescription() {
return description;
}
@Override
public double getPrice() {
return 10;
}
}
抽象装饰类
public abstract class Decorator implements MilkTea {
protected MilkTea milkTea;
public Decorator(MilkTea milkTea) {
this.milkTea = milkTea;
}
@Override
public String getDescription() {
return milkTea.getDescription();
}
@Override
public double getPrice() {
return milkTea.getPrice(); // 价格由种类来决定
}
}
具体装饰类:给奶茶加入椰果
public class Coconut extends Decorator {
private String description = "加了椰果!";
public Coconut(MilkTea milkTea) {
super(milkTea);
}
@Override
public String getDescription() {
return milkTea.getDescription() + "\n" + description;
}
@Override
public double getPrice() {
return milkTea.getPrice() + 3; // 3 表示椰果的价格
}
}
具体装饰类:给奶茶加入布丁
public class Pudding extends Decorator {
private String description = "加了布丁!";
public Pudding(MilkTea milkTea) {
super(milkTea);
}
@Override
public String getDescription() {
return milkTea.getDescription() + "\n" + description;
}
@Override
public double getPrice() {
return milkTea.getPrice() + 5; // 5表示布丁的价格
}
}
具体装饰类:给奶茶加入珍珠
public class Pearl extends Decorator {
private String description = "加了珍珠!";
public Pearl(MilkTea milkTea) {
super(milkTea);
}
@Override
public String getDescription() {
return milkTea.getDescription() + "\n" + description;
}
@Override
public double getPrice() {
return milkTea.getPrice() + 10; // 10表示珍珠的价格
}
}
测试
// 第一种写法
public class Test {
public static void main(String[] args) {
// 选择巧克力奶茶
MilkTea milkTea = new ChocolateMT();
// 第一次修饰,为巧克力奶茶添加布丁
milkTea= new Pudding(milkTea);
// 第二次修饰,为巧克力奶茶添加椰果
milkTea = new Coconut(milkTea);
System.out.println(milkTea.getDescription() + "\n加了布丁的巧克力奶茶的价格:" + milkTea.getPrice());
}
}
// 第二种写法
public class Test {
public static void main(String[] args) {
// 选择巧克力奶茶
MilkTea milkTea = new ChocolateMT();
// 第一次修饰,为巧克力奶茶添加布丁
Pudding puddingMilkTea = new Pudding(milkTea);
// 第二次修饰,为巧克力奶茶添加椰果
Coconut coconutMilkTea = new Coconut(puddingMilkTea);
System.out.println(coconutMilkTea.getDescription() + "\n加了布丁的巧克力奶茶的价格:" + coconutMilkTea.getPrice());
}
}
测试结果:
巧克力奶茶
加了布丁!
加了椰果!
加了布丁的巧克力奶茶的价格:23.0
测试用例的两种写法结果都是一样的
总结
由上面的案例可以看出装饰器模式的代码结构,使用类的组合来代替继承,但其实装饰器模式相对于简单的组合关系,还有两个比较特殊的地方。
第一个比较特殊的地方是:装饰器类和原始类继承同样的父类,这样我们可以对原始类“嵌套”多个装饰器类。 比如上面测试类中的第二种写法,对巧克力奶茶嵌套了两个装饰器类,给它添加了椰果和布丁。
第二个比较特殊的地方是:装饰器类是对功能的增强,这也是装饰器模式应用场景的一个重要特点。 实际上,符合“组合关系”这种代码结构的设计模式有很多,比如之前讲过的代理模式、桥接模式,还有现在的装饰器模式。尽管它们的代码结构很相似,但是每种设计模式的意图是不同的。就拿比较相似的代理模式和装饰器模式来说吧,代理模式中,代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功能。
在 Java 中的 IO 类库是使用装饰器模式的经典场景。
InputStream in = new FileImputStream("/user/test.txt");
InputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
int data = din.readInt();
最后
一直想整理出一份完美的面试宝典,但是时间上一直腾不开,这套一千多道面试题宝典,结合今年金三银四各种大厂面试题,以及 GitHub 上 star 数超 30K+ 的文档整理出来的,我上传以后,毫无意外的短短半个小时点赞量就达到了 13k,说实话还是有点不可思议的。
需要完整版的小伙伴,可以一键三连后,点击这里!
一千道互联网 Java 工程师面试题
内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、SpringBoot、SpringCloud、RabbitMQ、Kafka、Linux等技术栈(485页)
《Java核心知识点合集(283页)》
内容涵盖:Java基础、JVM、高并发、多线程、分布式、设计模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat、数据库、云计算等
《Java中高级核心知识点合集(524页)》
《Java高级架构知识点整理》
由于篇幅限制,详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
需要完整版的小伙伴,可以一键三连后,点击这里!