【设计模式与体系结构】结构型模式-装饰模式
简介
装饰模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。装饰模式是用组合的方式将装饰对象和被装饰对象组合在一起,当调用装饰后的对象的方法时,实际是先执行装饰器类添加的额外功能,再执行被装饰类原来的功能。
装饰器模式的角色
- Component抽象构件:抽象装饰类和具体构件的共同父类,声明了在具体构件中的业务方法,它的引入可以使得客户端可以一致地对待未被装饰的对象和已被装饰的对象,实现客户端的透明操作
- ConcreteComponent具体构件:它是抽象构件类的子类,用于定义具体的构件对象,实现了抽象构件中声明的方法,装饰器可以给它增加额外的方法(职责)
- Decorator抽象装饰类:它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的
- ConcreteDecorator具体装饰类:它是抽象装饰类的子类,负责向具体构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
装饰器模式的优点
- 灵活性高:可以动态地添加或删除对象的功能。与继承相比,装饰模式更加灵活,因为继承是在编译时就确定了类的功能扩展,而装饰模式可以在运行时根据需要组合功能。
- 可维护性好:每个装饰器类只关注一个功能的添加,功能模块划分清晰。如果要修改某个装饰功能,只需要修改对应的装饰器类,不会影响到其他部分。
- 符合开闭原则
装饰器模式的缺点
- 增加系统复杂度:若过度使用装饰器模式,会使得系统中有大量的装饰器类,会使得程序结构变得复杂,难以理解和维护
- 顺序敏感:装饰器的添加顺序可能会对最终的结果产生影响,当出错时,debug代价可能是昂贵的
装饰器模式的应用场景
- 动态增加功能:当对象在运行时需要根据不同的情况添加不同的功能时
- 避免继承的滥用
正文
各种游戏为了满足用户对游戏角色的装扮需求,可能会出各类装扮,比如:发型、帽子、鞋子、衣服和裤子等等。这就是一个很典型的在代码运行时,根据用户的需求动态的增加业务的需求。下面就以 cos 女巫作为案例,进行代码讲解。
首先定义一个抽象构件接口 Costume.java,也可以声明为抽象类,但是实现关系比继承关系耦合度更低,更推荐使用实现关系。
public interface Costume { void dressUp(); }
抽象构件接口 Costume.java 是具体构件和抽象装饰类的公共父类。对于透明型装饰器模式来说,客户端可以一致地对待具体构件和装饰器,安全性较高。
定义一个具体构件 SorceressCostume.java,代表最基本的女巫 cos 构件
public class SorceressCostume implements Costume { @Override public void dressUp() { System.out.println("穿上女巫cos服装"); } }
创建出女巫 cos 构件的对象时,相当于游戏角色已经装扮了女巫服装。但是对于具有个性化需求的用户来说,这可能并不能满足全部的用户需求。但是“装扮女巫服装”这件事情已经完成了,用户想装扮新的服饰,此时就需要引入装饰器模式中的另一个角色——装饰器类。
定义一个抽象装饰器类 CostumeDecorator.java。值得注意的是,抽象装饰器类,只能定义为抽象类而不能定义为接口。
public abstract class CostumeDecorator implements Costume { private Costume costume; public CostumeDecorator(Costume costume) {//注入一个 Costume 对象 this.costume = costume;//持有一个外部对象 } @Override public void dressUp() { costume.dressUp();//即便是抽象类,也必须实现接口的业务方法,否则子类中无法一致处理 } }
以依赖注入的方式,让一个装饰器持有外部的一个构件对象,这个构件对象既可以是具体构件对象,也可以是具体装饰器对象。原因是抽象装饰器和具体构件都是抽象构件的子类,所以用多态的方式,一致地对待依赖注入,从而达到不修改原功能的基础上实现功能的扩展。
不妨假设还有装扮帽子和装扮飞行器的功能。
定义女巫帽子装饰器类 CapCostumeDecorator.java
public class CapCostumeDecorator extends CostumeDecorator { public CapCostumeDecorator(Costume costume) { super(costume); } @Override public void dressUp() { super.dressUp(); dressUpCap(); } private void dressUpCap() { System.out.println("穿戴女巫帽子"); } }
定义女巫飞行器装饰器类 AircraftCostumeDecorator.java
public class AircraftCostumeDecorator extends CostumeDecorator { public AircraftCostumeDecorator(Costume costume) { super(costume); } @Override public void dressUp() { super.dressUp(); dressUpAircraft(); } private void dressUpAircraft() { System.out.println("穿戴女巫扫把飞行器"); } }
写一个调用的demo:
public class DancingParty { public static void main(String[] args) { Costume costume = new SorceressCostume(); CostumeDecorator aircraftCostumeDecorator = new AircraftCostumeDecorator(costume); CostumeDecorator capCostumeDecorator = new CapCostumeDecorator(aircraftCostumeDecorator); capCostumeDecorator.dressUp(); } }
运行效果截图如下:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 【.NET】调用本地 Deepseek 模型