1. 概述
装饰模式是一种结构型设计模式, 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。
注意,装饰模式和桥接模式很相似,没学过桥接模式的小伙伴,可以看这篇博客:22种设计模式——桥接模式。装饰者模式和桥接模式的出发点都是一样的,都是为了解决类爆炸的问题,先讲下这个问题:
举个例子,比如星巴克卖咖啡,咖啡种类有:美式、拿铁、摩卡,同时咖啡还可以加料:牛奶、巧克力、糖。这时如果按普通的方式,那是不是就要同时存在9(3 X 3)个子类,如果新增一种调料,就同时要多出三个类,这种设计显然是不合理的。
所以就有了桥接模式和装饰者模式解决这种情况,虽然他们两者使用场景很相似,但还是有所不同的,两个模式都是为了解决子类过多问题, 但他们的诱因不同:
- 桥接模式:桥接模式对象自身有沿着多个维度变化的趋势 , 本身不稳定
- 装饰者模式:装饰者模式对象自身非常稳定, 只是为了增加新功能/增强原功能
这样说可能有点抽象,以上面的星巴克咖啡例子来说,桥接模式和装饰者模式都是分维度处理,比如可以分成咖啡种类、咖啡调料两种维度,然后通过各自的方式连接起两个维度。
但这里不同的是桥接模式的对象是不稳定的,即有可能会产生新的维度,比如这里的星巴克咖啡可能会出现一种新的维度:尺寸(超大杯、大杯、中杯);装饰者模式的对象就是稳定的,它只关心两个维度的问题,即它的目的很纯粹,某一个维度用于增强另一个维度,应用在咖啡的例子就是星巴克的咖啡只有调料、种类两个维度,不考虑尺寸的维度。
但此时你或许会产生一个新的问题:桥接模式和装饰者模式都可以用于解决类爆炸问题,但桥接模式可以关注更多个维度的变化,装饰者模式只能两个,那直接用桥接模式就好了,还要装饰者模式干嘛?
问得好,既然存在装饰者模式,那就有它的道理,它肯定有一些桥接模式不具备的特性,这个特性就是装饰者模式的某个维度对于另一个维度的强化是可以递归添加的,正如开头的图片套娃一样,是可以不停的套的。应用到咖啡的例子,就是用户可以点一杯美式咖啡,同时加糖和加牛奶。而桥接模式就做不到这一点,它只能组成点一杯大杯的加糖美式,无法添加另一种调料。
- 总结
- 装饰者模式目的是为了解决类爆炸问题
- 装饰者模式和桥接模式很相似,但出发点不同,桥接模式的对象不稳定,可以有多个维度;装饰者模式的对象很稳定,只有两个维度
- 装饰者模式的某个维度对另一个维度的强化(装饰)是可以递归添加的
2. 特点
-
优点
- 无需创建新子类即可扩展对象的行为。
- 可以在运行时添加或删除对象的功能。
- 可以用多个装饰封装对象来组合几种行为。
- 单一职责原则。 你可以将实现了许多不同行为的一个大类拆分为多个较小的类。
-
缺点
- 在封装器栈中删除特定封装器比较困难。
- 实现行为不受装饰栈顺序影响的装饰比较困难。
- 各层的初始化配置代码看上去可能会很糟糕
-
使用场景
-
如果你希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为, 可以使用装饰模式。
装饰能将业务逻辑组织为层次结构, 你可为各层创建一个装饰, 在运行时将各种不同逻辑组合成对象。 由于这些对象都遵循通用接口, 客户端代码能以相同的方式使用这些对象。
-
如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以使用该模式。
许多编程语言使用 final最终关键字来限制对某个类的进一步扩展。 复用最终类已有行为的唯一方法是使用装饰模式: 用封装器对其进行封装。
-
3. 实现
-
UML类图
-
角色说明
- 抽象类或接口:这个就是上面一直说的对象(稳定或不稳定),所有功能都由它抽象出来,这个例子里面指的是
Coffee
这个基类 - 被装饰者:这个指的是被装饰的维度,在这个例子里面是
Type
咖啡种类 - 具体被修饰者:被修饰者的子类
- 装饰者:这个指的是修饰另一个维度的维度,这个例子里是
Decorator
调料品 - 具体修饰者:装饰者子类
- 客户端:调用装饰者模式的角色
- 抽象类或接口:这个就是上面一直说的对象(稳定或不稳定),所有功能都由它抽象出来,这个例子里面指的是
-
Java实现
- 抽象类或接口
/** * @Author: chy * @Description: 抽象类或者接口:这里表示咖啡 * @Date: Create in 20:15 2021/3/10 */ public abstract class Coffee { int price; String des; // 计算费用,抽象方法 abstract int cost(); // 获取描述,抽象方法 abstract String getDes(); }
- 被装饰者
/** * @Author: chy * @Description: 被装饰者:这里是咖啡种类 * @Date: Create in 20:00 2021/3/10 */ public class Type extends Coffee { @Override int cost() { return this.price; } @Override String getDes() { return this.des; } }
- 具体被修饰者
/** * @Author: chy * @Description: 被装饰的子类:美式咖啡 * @Date: Create in 20:02 2021/3/10 */ public class Americano extends Type { public Americano() { this.price = 5; this.des = "美式咖啡"; } }
/** * @Author: chy * @Description: 被装饰的子类:摩卡咖啡 * @Date: Create in 20:04 2021/3/10 */ public class Mocha extends Type { public Mocha() { this.price = 10; this.des = "摩卡咖啡"; } }
- 装饰者
/** * @Author: chy * @Description: 装饰者:调料类 * @Date: Create in 20:06 2021/3/10 */ public class Decorator extends Coffee { // 内置抽象类 Coffee coffee; // 组合 public Decorator(Coffee coffee) { this.coffee = coffee; } @Override int cost() { // 小细节,这里不要用price,否则会出错 return this.price + coffee.cost(); } @Override String getDes() { // 小细节,这里不要用des,否则会出错 return this.des + coffee.getDes(); } }
- 具体修饰者
/** * @Author: chy * @Description: 具体装饰者:这里是牛奶 * @Date: Create in 20:30 2021/3/10 */ public class Milk extends Decorator{ public Milk(Coffee coffee) { super(coffee); this.des = "牛奶"; this.price = 1; } }
/** * @Author: chy * @Description: 具体修饰者:糖 * @Date: Create in 20:33 2021/3/10 */ public class Sugar extends Decorator{ public Sugar(Coffee coffee) { super(coffee); this.des = "糖"; this.price = 3; } }
- 客户端
/** * @Author: chy * @Description: 客户端 * @Date: Create in 20:34 2021/3/10 */ public class Client { public static void main(String[] args) { // 点一杯普通美式 Coffee order1 = new Americano(); System.out.println("描述:"+order1.getDes()); System.out.println("价格:"+order1.cost()); // 点一杯加牛奶美式 Coffee order2 = new Milk(new Americano()); System.out.println("描述:"+order2.getDes()); System.out.println("价格:"+order2.cost()); // 点一杯加糖美式 Coffee order3 = new Sugar(new Americano()); System.out.println("描述:"+order3.getDes()); System.out.println("价格:"+order3.cost()); // 点一杯加糖加牛奶美式 Coffee order4 = new Sugar(order2); System.out.println("描述:"+order4.getDes()); System.out.println("价格:"+order4.cost()); } }
- 抽象类或接口