设计模式之美-结构型模式-装饰器模式

news/2024/11/25 23:41:21/

        装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,装饰器模式提供了比继承更有弹性的替代方案将功能附加到对象上。因此,装饰器模式的核心功能是功能扩展,使用装饰器模式可以透明且动态的扩展类的功能。装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。

应用场景:

  1. 用于扩展一个类的功能,或者给一个类添加附加职责

  2. 动态的给一个对象添加功能,这些功能可以再动态的被撤销

  3. 需要为一批平行的兄弟类进行改装或者加装功能

UML类图

由上图可知,装饰器模式主要包含了4个角色;

  1. 抽象组建(Component):可以是一个接口或者抽象类,充当被装饰类的原始对象,规定了被装饰对象的行为

  2. 具体组件(ConcreteComponent):实现/继承Component的一个具体对象,即被装饰对象

  3. 抽象装饰器(Decorator):通用的装饰ConcreteComponent的装饰器,其内部必然有一个属性指向Component,其实现一般是一个抽象类,主要为了让其子类按照其构造形式传入一个Component,这是强制的通用行为。如果系统中装饰逻辑单一,则并不需要实现许多装饰器,可以直接省略该类,而直接实现一个具体装饰器即可

  4. 具体装饰器(ConcreteDecorator):Decorator的具体实现类,理论上,每个ConcreteDecorator都扩展了Component对象的一种功能

装饰器模式的实现原理就是,让装饰器实现与被装饰类相同的接口,使得装饰器与被扩展类类型一致,并在构造函数中传入该接口对象,然后再实现这个接口的被包装类属于同一类型,且构造函数的参数为其实现接口类,因此装饰器模式具备嵌套扩展功能,这样就能使用装饰器模式一层一层的对底层被包装类进行功能扩展了

通用写法

public class Client {public static void main(String[] args) {ConcreteComponent c1 = new ConcreteComponent();ConcreteDecoratorA decoratorA = new ConcreteDecoratorA(c1);decoratorA.operation();System.out.println("---------------------------");ConcreteDecoratorB decoratorB = new ConcreteDecoratorB(c1);decoratorB.operation();System.out.println("---------------------------");ConcreteDecoratorB decoratorB1 = new ConcreteDecoratorB(decoratorA);decoratorB1.operation();}static abstract class Component{public abstract void operation();}static class ConcreteComponent extends Component{@Overridepublic void operation() {System.out.println("处理业务逻辑!!");}}static abstract class Decorator extends Component{protected Component component;public Decorator(Component component){this.component = component;}public void operation(){//转发请求给组建对象,可以在转发前后执行一些附加动作component.operation();}}static class ConcreteDecoratorA extends Decorator{public ConcreteDecoratorA(Component component) {super(component);}private void  operationFirst(){System.out.println("ConcreteDecoratorA装饰operationFirst");}private void  operationLast(){System.out.println("ConcreteDecoratorA装饰operationLast");}public void operation(){operationFirst();super.operation();operationLast();}}static class ConcreteDecoratorB extends Decorator{public ConcreteDecoratorB(Component component) {super(component);}private void  operationFirst(){System.out.println("ConcreteDecoratorB装饰operationFirst");}private void  operationLast(){System.out.println("ConcreteDecoratorB装饰operationLast");}public void operation(){operationFirst();super.operation();operationLast();}}
}

示例:

使用装饰器模式解决煎饼加码问题

下面用代码来模拟给煎饼加码的业务场景,先来看不用装饰器模式的情况。首先创建一个煎饼Battercake类

public class Battercake {protected String getMsg(){return "煎饼";}public int getPrice(){return 5;}
}

 然后创建一个加鸡蛋的煎饼BattercakeWithEgg类

public class BattercakeWithEgg extends Battercake{protected String getMsg(){return super.getMsg() + "+ 1个鸡蛋";}public int getPrice(){return super.getPrice() + 1;}
}

在创建一个既加鸡蛋又加香肠的BattercakeWithEggAndSausage类

public class BattercakeWithEggAndSausage extends BattercakeWithEgg{protected String getMsg(){return super.getMsg() + "+ 1根香肠";}public int getPrice(){return super.getPrice() + 2;}
}

最后编写客户端测试代码

public class ClientTest {public static void main(String[] args) {Battercake battercake = new Battercake();System.out.println(battercake.getMsg() + ",总价格" + battercake.getPrice());BattercakeWithEgg battercakeWithEgg = new BattercakeWithEgg();System.out.println(battercakeWithEgg.getMsg() + ",总价格" + battercakeWithEgg.getPrice());BattercakeWithEggAndSausage battercakeWithEggAndSausage = new BattercakeWithEggAndSausage();System.out.println(battercakeWithEggAndSausage.getMsg() + ",总价格" + battercakeWithEggAndSausage.getPrice());}
}

运行结果如下:

煎饼,总价格5
煎饼+ 1个鸡蛋,总价格6
煎饼+ 1个鸡蛋+ 1根香肠,总价格8

运行结果没有问题。但是,如果用户需要一个加2个鸡蛋和1根香肠的煎饼,则用现在的类结构是创建不出来的,也无法自动计算出价格,除非再创建一个类做定制。如果需求在变,那么一直加定制显然是不科学的

下面用装饰器模式来解决上面的问题。首先创建一个煎饼的抽象Battercake类。

public abstract class Battercake {protected abstract String getMsg();protected abstract int getPrice();
}

创建一个基本的煎饼(或者叫基础套餐)BaseBattercake.

public class BaseBattercake extends Battercake{@Overrideprotected String getMsg() {return "煎饼";}@Overrideprotected int getPrice() {return 5;}
}

然后创建一个扩张套餐的抽象装饰器BattercakeDecotator类

public abstract class BattercakeDecotator extends Battercake{private Battercake battercake;public BattercakeDecotator(Battercake battercake){this.battercake = battercake;}protected abstract void doSomething();protected String getMsg(){return this.battercake.getMsg();}protected int getPrice(){return this.battercake.getPrice();}
}

接着创建鸡蛋装饰器EggDecorator

public class EggDecorator extends BattercakeDecotator{public EggDecorator(Battercake battercake) {super(battercake);}@Overrideprotected void doSomething() {}protected String getMsg(){return super.getMsg() + "+1个鸡蛋";}protected int getPrice(){return super.getPrice() + 1;}
}

创建香肠装饰器SausageDecorator类

public class SausageDecorator extends BattercakeDecotator{public SausageDecorator(Battercake battercake) {super(battercake);}@Overrideprotected void doSomething() {}protected String getMsg(){return super.getMsg() + "+1根香肠";}protected int getPrice(){return super.getPrice() + 2;}
}

在编写客户端测试代码

public class Client {public static void main(String[] args) {//买一个煎饼Battercake battercake;battercake = new BaseBattercake();//煎饼有点小,想再加1个鸡蛋battercake = new EggDecorator(battercake);battercake = new EggDecorator(battercake);battercake = new SausageDecorator(battercake);System.out.println(battercake.getMsg() + ",总价: " + battercake.getPrice());}
}

运行结果如下图所示:

煎饼+1个鸡蛋+1个鸡蛋+1根香肠,总价: 9

装饰器模式与代理模式区别

从代理模式的UML类图和通用代码实现上看,代理模式与装饰器模式几乎一摸一样。代理模式的Subject对应装饰器模式的Component,代理模式的RealSubject对应装饰器模式的Concrete Component,代理模式的Proxy对应的装饰器模式的Decorator。确实,从代码实现上看,代理模式的确与装饰器模式是一样的,但是这两种设计模式多面向的功能扩展面是不一样的。

装饰器模式强调自身功能的扩展。Decorator所做的就是增强ConcreteComponent的功能,主体对象为ConcreteComponent,着重类功能的变化。

代理模式强调对代理过程的控制。Proxy完全掌握对RealSubject的访问控制,因此,Proxy可以决定对RealSubject进行功能扩展,功能缩减甚至功能散失,主体对象为Proxy。、

装饰器模式优缺点

优点:

  1. 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态地给一个对象扩展功能,即插即用。

  2. 通过使用不同装饰类及这些装饰类的排列组合,可以实现不同效果

  3. 装饰器模式完全遵守开闭原则

缺点:

  1. 会出现更多的代码,更多的类,增加程序的复杂性

  2. 动态装饰在多层装饰时会更复杂


http://www.ppmy.cn/news/43159.html

相关文章

MySQL-事务处理

MySQL事务 什么是事务 数据中的事务是指数据库执行的一些操作,这些操作最终要么全部执行成功,要么全部失败,不会存在部分成功,部分失败的情况 如果在事务的支持下,最终有两种结果: 操作成功:…

vite+vue3+pinia+vuex4动态路由解决刷新页面丢失

目录 了解如何添加动态路由 使用pinia持久化数据 解决方案 404找不页面问题 目前解决思路 完整路由配置 动态路由使用的数据 了解如何添加动态路由 vue官网-动态添加路由 使用pinia持久化数据 pinia的使用 解决方案 asyncRoutes()方法从pinia获取到动态菜单数据登陆成…

python调用matlab源码函数

Background 关于在python中调用matlab函数,我之前已经写过两篇文章了,非常详细,且之前的方法可以不用安装matlab程序,只需要按照mcr运行环境就行了。具体可以参考:【java和python调用matlab程序详细记录】【Python 高效…

【CSS】文字溢出问题 ( 强制文本在一行中显示 | 隐藏文本的超出部分 | 使用省略号代替文本超出部分 )

文章目录一、文字溢出问题二、文字溢出处理方案三、代码示例一、文字溢出问题 在元素对象内部显示文字 , 如果文本过长 , 则会出现文本溢出的问题 ; 下面的示例中 , 在 150x25 像素的盒子中 , 显示 骐骥一跃,不能十步;驽马十驾,功在不舍; 一段话 , 明显…

做一个内心强大的人

想想类似如下这种心灵鸡汤,本不太愿意在这个平台发布,但是偶尔喝点又何尝不可! 语录摘抄/分享: 1、你开始炫耀自己,往往都是灾难的开始,就像老子在《道德经》里写到: 光而不耀,静水流深。 2、…

数据库三大范式学习笔记(非常详细)

首先范式是什么: 是对设计数据库提出的一些规范,设计数据库时遵循一定的规则,建立冗余较小、结构合理的数据库更便于查找数据。 第一范式(1NF): 强调列的原子性,原子性即最小单位,不可再拆分…

JavaSE异常

文章目录JavaSE异常一、异常的概念二、异常的体系结构三、异常的分类四、异常的处理五、自定义异常类JavaSE异常 一、异常的概念 在Java中,将程序执行过程中发生的不正常行为称为异常 常见逻辑异常: 算数异常 System.out.println(10 / 0); // 执行结…

详解FreeRTOS中的软件定时器

软件定时器用于让某个任务定时执行,或者周期性执行。比如设定某个时间后执行某个函数,或者每隔一段时间执行某个函数。由软件定时器执行的函数称为软件定时器的回调函数。 参考资料: 《Mastering the FreeRTOS™ Real Time Kernel》——Cha…