装饰器模式也称为包装模式是指在不改变原有对象的基础上,将功能附加到对象上,提供比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式
装饰器模式的核心是功能扩展,使用装饰器模式可以透明且动态的扩展类的功能
装饰器模式通用UML类图
主要角色有:
- 抽象组件(Component):可以是一个接口或者一个抽象类,充当被修饰类的原始对象,规定了被装饰对象的行为
- 具体组件(ConcreteComponent):就是被装饰对象,实现或者集成抽象组件的一盒对象
- 抽象装饰器(Decorator):通用的装饰ConcreteComponent的装饰器,一般情况下是一个抽象类(如果装饰器只有一个的话可以直接省掉这个类)其内部必然有一个属性是指向Component抽象组件的
- 具体装饰器(ConcreteDecorator):Decorator的具体实现类,理论上每个ConcreteDecorator都扩展了Conponent一种功能(当然也可以不扩展,如果不扩展不就是写了一个没用的类吗)
装饰器模式的通用实现代码
public interface Component {
void doSomething();
}
复制代码
public class ConcreteComponent implements Component{
@Override
public void doSomething() {
System.out.println("原始类做一些事情");
}
}
复制代码
public abstract class Decorator implements Component{
public Component component;
public Decorator(Component component){
this.component = component;
}
}
复制代码
public class DecoratorA extends Decorator{
public DecoratorA(Component component) {
super(component);
}
@Override
public void doSomething() {
super.component.doSomething();
System.out.println("DecoratorA 再帮忙做点事情");
}
}
复制代码
public class DecoratorB extends Decorator{
public DecoratorB(Component component) {
super(component);
}
@Override
public void doSomething() {
super.component.doSomething();
System.out.println("DecoratorB 再帮忙做点事情");
}
}
复制代码
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
component.doSomething();
System.out.println("-----------");
Component decoratorA = new DecoratorA(component);
decoratorA.doSomething();
System.out.println("-----------");
Component decoratorB = new DecoratorB(decoratorA);
decoratorB.doSomething();
}
}
复制代码
装饰器模式的大致原理
让装饰器实现被包装类相同的接口(使得装饰器与被扩展类类型一样),并在构造函数中传入该接口对象,然后就可以在接口需要实现的方法中在被包装类对象的 现有功能上添加新功能,而且由于装饰器与被包装类属于同一类型,且构造函数的参数为其实现接口类,因此装饰器模式具备嵌套扩展功能,这样我们就能使用装饰器模式一层一层的对最底层被包装类进行功能拓展了
装饰器内部有个指针指向被包装的类,应为被包装类和包装类实现的都是同一个接口或者抽象类,所以这个指针可能指向的不是最原始的类可以是被包装好几层的类,所以就实现了一层一层的包装
代码示例(不使用装饰器模式)
举一个很常见的例子,比如课程购买,一个课程最初始的价格是100,但是到了618公司说要搞个活动打九折,没过两天又来个活动这个活动是8折,此时普通用户就只能参加618打九折,公司普通会员的用户公司给8折,超级会员用户公司给折上九折再打八折。
public class JavaCourse {
public Double getPrise() {
return 100.0;
}
}
复制代码
618活动
public class ActivityAJavaCourse extends JavaCourse{
public Double getActivityPrice() {
return 100.0 * 0.9;
}
}
复制代码
普通会员活动
public class ActivityBJavaCourse extends JavaCourse{
public Double getActivityPrice() {
return 100.0 * 0.8;
}
}
复制代码
超级会员活动
public class ActivityBAndAJavaCourse extends JavaCourse{
public Double getActivityPrice() {
return 100.0 * 0.9 * 0.8;
}
}
复制代码
客户端调用:
public class Client {
public static void main(String[] args) {
Integer vipType = 1;
boolean hasActivity = true;
if(hasActivity){
if(vipType == 0){
//普通用户
ActivityAJavaCourse javaCourse = new ActivityAJavaCourse();
System.out.println("课程价格:"+javaCourse.getActivityPrice());
}else if(vipType == 1){
//普通会员
ActivityBJavaCourse javaCourse = new ActivityBJavaCourse();
System.out.println("课程价格:"+javaCourse.getActivityPrice());
}else{
//超级会员
ActivityBAndAJavaCourse javaCourse = new ActivityBAndAJavaCourse();
System.out.println("课程价格:"+javaCourse.getActivityPrice());
}
}else{
JavaCourse javaCourse = new JavaCourse();
System.out.println("课程价格:"+javaCourse.getPrise());
}
}
}
复制代码
这里面有两个很尴尬的点
- 每个活动对应一个类这个可以理解已有活动组合还要创建一个类,很麻烦,类很多
- 我们可以看一下客户端调用,在支付的时候使用金额还要判断一下当前是否在活动中,如果是活动则用
getActivityPrice
来获取支付金额如果不在活动中则使用getPrise
获取支付金额,就写了很多这样的垃圾代码,当然有人可能质疑说不要实现getActivityPrice
而是覆盖父类的getPrise
方法,这样写的话前端是没那么多问题了,但是这个代码对于后端来说又不符合里式替换原则,很恶心
针对以上两个痛点我们可以使用装饰器模式稍微改进一下代码
代码示例(使用装饰器模式优化)
public interface ICourse {
Double getPrice();
}
复制代码
被装饰类
public class JavaCourse implements ICourse{
public Double getPrice() {
return 100.0;
}
}
复制代码
抽象装饰器
public abstract class ActivityJavaCourse implements ICourse{
protected ICourse course;
public ActivityJavaCourse(ICourse course){
this.course = course;
}
}
复制代码
具体618活动装饰类
public class ActivityJavaCourseA extends ActivityJavaCourse{
public ActivityJavaCourseA(ICourse course) {
super(course);
}
@Override
public Double getPrice() {
Double price = super.course.getPrice();
return price * 0.9;
}
}
复制代码
会员折扣装饰类
public class ActivityJavaCourseB extends ActivityJavaCourse{
public ActivityJavaCourseB(ICourse course) {
super(course);
}
@Override
public Double getPrice() {
Double price = super.course.getPrice();
return price * 0.8;
}
}
复制代码
客户端使用
public class Client {
public static void main(String[] args) {
Integer vipType = 3;
boolean hasActivity = true;
JavaCourse javaCourse = new JavaCourse();
if(hasActivity){
if(vipType == 0){
//普通用户
ActivityJavaCourseA javaCourseA = new ActivityJavaCourseA(javaCourse);
System.out.println("课程价格:"+javaCourseA.getPrice());
}else if(vipType == 1){
//普通会员
ActivityJavaCourseB javaCourseB = new ActivityJavaCourseB(javaCourse);
System.out.println("课程价格:"+javaCourseB.getPrice());
}else{
//超级会员
ActivityJavaCourseA javaCourseA = new ActivityJavaCourseA(javaCourse);
ActivityJavaCourseB javaCourseB = new ActivityJavaCourseB(javaCourseA);
System.out.println("课程价格:"+javaCourseB.getPrice());
}
}else{
System.out.println("课程价格:"+javaCourse.getPrice());
}
}
}
复制代码
可以发现很好的解决了上述的两个痛点,有人可能会说不满足里式替换原则,大家需要注意的是这里不再是类的继承而是很多类共同实现一个接口而已
装饰器模式的适用场景
通过上文的描述我们可以大概总结出装饰器模式的适用场景
- 用于扩展一个类的功能或者给一个类添加附加职责
- 动态的给一个对象添加功能,这些功能可以再动态的撤销
- 需要为一批兄弟类进行改装或者加装功能
装饰器模式的优缺点
优点:
- 可以在不改变原有对象情况下动态的给一个对象扩展功能
- 遵循开闭原则
- 不同装饰器可以随意排列组合
缺点:
- 类增多增加程序复杂性
- 动态装饰时多层装饰时会更复杂
装饰器模式、门面模式、代理模式区别
讲完装饰器模式看过前面几篇文章的人可能凌乱了,有点搞不清楚门面模式、静态代理模式、装饰器模式了,下面总结一下三者的区别
- 装饰器模式和门面模式一样都是特殊的静态代理模式(仅此点一样)
- 装饰器模式强调的代码的变化、门面模式强调的代码的封装,代理模式则是代码的增强
- 一般装饰器模式源对象是需要通过构造方法传入的,门面模式则是在内部初始化子系统的对象主要是对子系统的封装
- 静态代理模式和装饰器模式的差别就没有那么大主要是理念上的不同,比如使用装饰器层层装饰,如果改用静态代理层层代理也挺奇怪
作者:Potato_土豆
链接:https://juejin.cn/post/7166531323320860680
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。