当前位置:首页 > 随笔 > 正文内容

东北F4的设计模式情景剧——第一幕 装饰模式

访客 随笔 2026年8月14日 4

第一幕 面临难题

场景设定:铁岭,晴朗午后,微风拂过。一栋颇具规模的办公楼三层外墙悬挂着一块褪色的红布横幅,上面用潦草的毛笔字写着"东北F4软件外包工作室"。狂风中,横幅剧烈摇摆,发出沙沙声响。室内,东北F4团队正为大鹏科技公司开发一款名为"大侠"的游戏。

刘能(坐在椅子上,转头朝众人):好了……搞定!我创建了一个大侠基类,接着派生出三峡和张凤霞两个子类,每位大侠具备攻击和隐身两项基本能力。这是我绘制的类图:

刘能:还有这段代码:

 1 class DaXia   // 基类  
 2 {  
 3 public:  
 4     DaXia() {};  
 5     virtual ~DaXia() {};  
 6     virtual void GongJi() = 0;  
 7     virtual void YinShen() = 0;  
 8 };  
 9   
10 class SanXia :public DaXia   // 三峡版本  
11 {  
12 public:  
13     SanXia() {};  
14     virtual ~SanXia() {};  
15     virtual void GongJi()  
16     {  
17         cout << "来自三峡的攻击!" << '\n';  
18     }  
19     virtual void YinShen()  
20     {  
21         cout << "三峡已隐身!" << '\n';  
22     }  
23 };  
24   
25 class ZhangFengXia :public DaXia   // 张凤霞版本  
26 {  
27 public:  
28     ZhangFengXia() {};  
29     virtual ~ZhangFengXia() {};  
30     virtual void GongJi()  
31     {  
32         cout << "来自张凤霞的攻击!" << '\n';  
33     }  
34     virtual void YinShen()  
35     {  
36         cout << "张凤霞已隐身!" << '\n';  
37     }  
38 };

(众人围拢观看)

宋小宝:刚刚大鹏那边来电,说每个大侠除了基本的攻击和隐身,还可能添加一些额外的小功能,例如攻击前先跳一下,隐身前喊一声。因为某些大侠可能全部具备这些功能,也可能只有一部分,甚至没有。总之情况多变,你们怎么看?

赵四(自信满满):这有何难,让我来解决!

(片刻之后……)

赵四(得意地将一张纸拍在桌上):看!完美无缺!

赵四(摇头晃脑):我设计的新类聚合了DaXia类,每个新类实现自己的附加动作,原有功能则复用聚合类的代码。由于聚合的是大侠基类,因此所有具体大侠类均可被新类使用,从而减少新增类的数量。比如,要实现攻击前跳跃的三峡,只需将SanXia实例包装进TiaoDaXia类;再如,要实现攻击前跳跃、隐身前喊话的张凤霞,只需将ZhangFengXia实例包装进TiaoShuoDaXia类。这不就解决问题了吗?

小沈阳(打断赵四):好家伙!你这设计根本不合逻辑!你新设计的张凤霞类不是大侠的子类,客户端无法直接使用!看看我的接口:

void DaXiaDongZuo(DaXia *daxia)  
{  
        daxia->GongJi();  
        daxia->YinShen();  
}

你看这怎么用?我必须改动代码,而且一旦需求变更,还得改!你这是在挑战"开闭原则"!面向扩展开放,面向修改关闭!

赵四(捂脸,怒气冲冲):你凭什么打我!不行就不行嘛,你凭什么动手!(愤恨地)你等着,我十分钟后给你一个完美的方案!

(十分钟后)

赵四(颤颤巍巍地将纸拍在桌上):看,这次你满意了吧?这次你服了吧?

(众人无视赵四,纷纷围观)

赵四(神气活现):这次你总该服了吧?我用了继承方法,解决了你提到的所有问题。怎么样,我很聪明吧?

刘能(打断):聪明你个头!你解决的是他的问题,我的呢?如果现在要一个既跳又说的张凤霞,就得新写一个类,然后只跳不说、只说不跳,又得三个类,三峡也得三个。明天再来需求,我岂不是要疯了?你来写,你能维护吗?你这设计毫无复用性!跳功能每个类都重复写!

赵四(委屈地):宝啊,他们欺负我,你说扩展一个类,不就是继承和聚合吗?你们……(作委屈状)

宋小宝(推他):瞧你那德行!净想些找打的办法!

赵四(委屈):连你也欺负我!你来想个好办法!

宋小宝(心虚):我要是真有办法,还轮得到你挨打?

小沈阳(不耐烦):行了,下班时间到了,大家回去想想怎么处理。

众人(无奈):好吧,也只能这样了。

(众人离开,赵四嘟囔声渐远:这事没完,我不会白白挨打的,明天,我一定让你们心服口服!)

第二幕 赵四反杀

场景:一夜麻将后的宋小宝揉着眼睛走进办公室,见其他人正讨论着什么。

宋小宝(疑惑):咋回事?

赵四(兴奋地):宝啊,我彻夜思考,终于想到解决办法!怎么样,服不服?

宋小宝(冷笑):哈哈哈,你这笑话可真够逗的,你要是真解决了,我请大家吃饭!

刘能、小沈阳(做胜利手势):耶!

宋小宝(震惊):什么?你真想出来了?

刘能、小沈阳(点头):这次是真的!

宋小宝(犹豫):等等,让我好好看看。

(众人看向赵四的新方案)

(几分钟后)

宋小宝(困惑):你这设计啥意思,跟天书似的!

赵四(不屑):你这水平也看不懂!

宋小宝(严肃):别抢台词,你解释清楚。

(赵四望向刘能和小沈阳)

刘能(装作没看见):快说,宝都等不及了。

小沈阳:鼓掌!

(众人鼓掌)

赵四(清清嗓子):在我们的游戏中,攻击和隐身是核心功能,而攻击前跳、隐身前喊是辅助功能。它们是对核心功能的装饰。

众人(点头):有道理!

赵四(踱步):所以我从大侠类派生出装饰类,专门处理装饰后的行为。

刘能(打断):等等!这不是和昨天一样吗?还是得多写很多类。

赵四(摇头):不对,这次不同。我将每种装饰做成装饰类的子类。比如跳跃和喊话,我分别派生出TiaoDaXia和ShuoDaXia类。这些是具体装饰类。每个只负责自己的装饰功能,其余功能调用聚合对象的版本。以TiaoDaXia为例,其GongJi函数先调用Tiao函数,再调用daxia的GongJi函数。

刘能(问):daxia是啥?

赵四(神秘):重点来了。装饰类不仅继承DaXia,还聚合了DaXia指针。这意味着它可以指向所有DaXia类的子类,包括其他装饰类。这样,就可以形成嵌套结构,实现递归调用,从而自由组合功能。

小沈阳(困惑):这很重要吗?

赵四(激动):太重要了!比如TiaoDaXia类继承了daxia指针,它可指向ShuoDaXia类,ShuoDaXia类又可指向其他类……这样就能无限组合,变化无穷!

宋小宝(好奇):能举例说明吗?

赵四(自信):当然!我写了示例程序:

  1 #include<iostream>
  2 
  3 using namespace std;
  4 
  5 class DaXia   // 抽象基类
  6 {
  7 public:
  8     DaXia() {};
  9     virtual ~DaXia() 
 10     {};
 11     virtual void GongJi() = 0;
 12     virtual void YinShen() = 0;
 13 };
 14 
 15 class SanXia :public DaXia   // 三峡类
 16 {
 17 public:
 18     SanXia() {};
 19     virtual ~SanXia() 
 20     {};
 21     virtual void GongJi()
 22     {
 23         cout << "来自三峡的攻击!" << '\n';
 24     }
 25     virtual void YinShen()
 26     {
 27         cout << "三峡已隐身!" << '\n';
 28     }
 29 };
 30 
 31 class ZhangFengXia :public DaXia   // 张凤霞类
 32 {
 33 public:
 34     ZhangFengXia() {};
 35     virtual ~ZhangFengXia() 
 36     {};
 37     virtual void GongJi()
 38     {
 39         cout << "来自张凤霞的攻击!" << '\n';
 40     }
 41     virtual void YinShen()
 42     {
 43         cout << "张凤霞已隐身!" << '\n';
 44     }
 45 };
 46 
 47 class ZhuangShiDaXia :public DaXia   // 装饰大侠类
 48 {
 49 protected:
 50     DaXia *daxia;   // 聚合DaXia类的直接或间接子类
 51 public:
 52     ZhuangShiDaXia() {};
 53     ZhuangShiDaXia(DaXia *dx)
 54         :daxia(dx)
 55     {}
 56     virtual ~ZhuangShiDaXia()
 57     {
 58         delete daxia;
 59         daxia = NULL;
 60     }
 61     virtual void GongJi()   // 由所聚合的类实现功能
 62     {
 63         daxia->GongJi();
 64     }
 65     virtual void YinShen()
 66     {
 67         daxia->YinShen();
 68     }
 69 };
 70 
 71 class TiaoDaXia :public ZhuangShiDaXia   // 具有跳装饰的大侠类
 72 {
 73 protected:
 74     virtual void Tiao()   // 实现跳装饰
 75     {
 76         cout << "跳一下" << '\n';
 77     }
 78 public:
 79     TiaoDaXia() {};
 80     TiaoDaXia(DaXia *dx)
 81         :ZhuangShiDaXia(dx)
 82     {}
 83     virtual ~TiaoDaXia()
 84     {}
 85     virtual void GongJi()
 86     {
 87         Tiao();
 88         daxia->GongJi();   // 具体的攻击功能交由所聚合的类完成
 89     }
 90 
 91     // YinShen功能完全采用基类版本,即daxia->YinShen(),还是交由所聚合的类完成
 92 };
 93 
 94 class ShuoDaXia :public ZhuangShiDaXia   // 具有说装饰的大侠
 95 {
 96 protected:
 97     virtual void Shuo()   // 实现说装饰
 98     {
 99         cout << "你来找我呀?!" << '\n';
100     }
101 public:
 102     ShuoDaXia() {};
 103     ShuoDaXia(DaXia *dx)
104         :ZhuangShiDaXia(dx)
105     {}
 106     virtual ~ShuoDaXia()
107     {}
 108     virtual void YinShen()
 109     {
110         Shuo();
111         daxia->YinShen();   // 具体的隐身功能交由所聚合的类完成
112     }
113 
114     // GongJi功能完全采用基类版本,即daxia->GongJi(),还是交由所聚合的类完成
115 };
116 
117 int main()
 118 {
 119     ZhangFengXia *zfx = new ZhangFengXia();   // 张凤霞
 120     ShuoDaXia *sdx = new ShuoDaXia(zfx);   // 将张凤霞"嵌入"到具有说装饰的大侠实例中
 121     TiaoDaXia *tdx = new TiaoDaXia(sdx);// 将具有说装饰的张凤霞"嵌入"到具有跳装饰的大侠实例中
 122     tdx->GongJi();// 攻击
 123     tdx->YinShen();// 隐身
 124     delete tdx;
 125     tdx = NULL;
 126     sdx = NULL;
 127     zfx = NULL;
 128 
 129     return 0;
 130 }

赵四(得意):来,看效果!

赵四(自豪):看吧,我们构建的大侠具有了跳和说两种装饰。

刘能(困惑):还是不太明白……

赵四(不耐烦):好吧,我再解释一下。(拿出一张图)

赵四:如图所示,张凤霞实例被嵌入ShuoDaXia实例中,ShuoDaXia实例又被嵌入TiaoDaXia实例中。当调用tdx->GongJi()时,首先执行Tiao(),然后调用daxia->GongJi(),即调用ShuoDaXia的GongJi(),再调用ZhangFengXia的GongJi()。

众人(恍然大悟):哦,明白了!

小沈阳(思索):也就是说,装饰类只实现自己的装饰部分,其他功能交给聚合对象。

宋小宝(补充):关键是,如果聚合对象也包含daxia指针,就可以继续嵌套,直到没有daxia指针的类为止。

刘能(激动):我懂了!如果要一个攻击前跳两次的大侠,就这么写:

ZhangFengXia *zfx = new ZhangFengXia();   // 张凤霞  
TiaoDaXia *tdx1 = new TiaoDaXia(zfx);   // 跳一次  
TiaoDaXia *tdx = new TiaoDaXia(tdx1);   // 再跳一次  

赵四(拍拍刘能):孺子可教!

刘能(推开):别摸我头!

赵四(故作深沉):其实还有更简单的写法:

TiaoDaXia *tdx = new TiaoDaXia(new TiaoDaXia(new ZhangFengXia()));  

小沈阳(思考):这个装饰模式确实不错。它结合了继承和聚合的优点,既避免了类爆炸,又保持了灵活性。

刘能:没错!当需要对核心功能进行装饰时,这种模式非常适用。新增功能只需添加新类,无需改动现有代码。

宋小宝(不屑):得了,这也不是你发明的。

赵四(深沉):任何事物都有利弊。装饰模式虽好,但也存在缺点。它减少了类的数量,但增加了对象创建数量。比如要实现跳和说的张凤霞,需创建三个对象。如果类复杂,影响性能。此外,嵌套结构复杂,调试困难,代码可读性差。

小沈阳(感慨):赵四,你这分析还真挺深刻。

(背景音乐响起)

赵四(深情):记得2010年,我参加《非诚勿扰》,24盏灯全灭。从此踏上编程之路。我不像你们,是名校高材生,我是半路出家。但我有尊严!我要靠实力证明自己!

小沈阳(感动):太感人了!四儿,对不起,以后不再欺负你了!

刘能(擤鼻涕):太励志了!二哥以后再也不鄙视你了!

(宋小宝悄悄退后,不小心撞倒垃圾桶,被发现)

小沈阳、刘能(追赶):宋小宝,你站住!

(完)

标签: 设计模式

相关文章

可以按小时收费的VPS

很多 VPS 提供商都支持 按小时计费(hourly billing),想短期试用 / 临时搭建节点、测试网络、短期项目等场景非常合适。下面是当前最主流且靠谱的按小时 VPS 选项,分别按不同需求场景整理: 1. Vultr(全球节点,包括日本) 按小时计费 可选机房:东京 / 大阪 / 洛杉矶 / 法兰克福 / 伦敦 … 支持 PayPal(部分情况),但更常用信用卡/PayPal+卡价格参考$...

在 iPhone 上下载国外App

地区/国家限制App Store 会根据 Apple ID 的国家或地区限制应用下载。如果你的 Apple ID 绑定的是中国大陆,就可能无法下载 OpenAI 官方的 ChatGPT 应用,因为它在大陆 App Store 不上架。解决办法:换成美国、加拿大、香港等地区的 Apple ID。或者在现有 Apple ID 上更改地区。注册一个国外 Apple ID(推荐)比如注册 美国区 Appl...

Node.js 中的异步编程:回调与 Promise

Node.js 是一个基于 JavaScript 构建的单线程、非阻塞运行环境,它通过异步编程机制来高效处理多个操作。在执行如文件读取、API 请求或数据库查询等任务时,Node.js 不会等待这些操作完成,而是使用回调函数和 Promise 来避免阻塞主线程。 回调方式实现异步 那么当异步操作完成后,Node.js 如何知道接下来要做什么呢?这就要用到 回调函数(callback)。 回调本质上...

Selenium自动化测试入门指南

Selenium自动化测试入门指南

什么是自动化测试? 自动化测试是指利用软件工具自动执行测试用例,模拟用户操作,如打开网页、点击链接、输入文本等,并验证结果是否符合预期。 其主要优点包括: 大幅减少人工成本 测试速度快 可以在非工作时间运行 支持持续集成和交付 然而,它也存在一些局限性,例如开发成本较高、不适合快速变化的项目、依赖稳定的UI界面等。 自动化测试的应用条件 适合引入自动化测试的情况包括: 手动测试耗时且需要大量...

MariaDB Galera集群故障快速恢复指南

OpenStack控制节点采用三节点MariaDB Galera集群架构。当数据库集群因故障重启时,有时会出现Galera集群无法正常启动的问题。虽然有多种方法可以恢复数据库服务,但如何实现快速启动同时确保数据完整性呢? 通过分析日志发现,MariaDB Galera集群节点宕机时会在日志中输出以下信息: [Note] WSREP: 新集群视图:全局状态: 874d8e7e-5980-11e8-8...

Android 中 EventBus 的通信机制与实现原理深度解析

EventBus 核心设计思想 EventBus 是一个基于观察者模式的事件总线框架,广泛应用于 Android 平台以实现组件解耦。它通过中心化的消息分发机制,使不同层级、不同线程的对象能够以"发布-订阅"方式通信,避免了传统接口回调或广播带来的强依赖问题。 核心角色说明 事件(Event):任意 Java 对象,作为数据载体,如网络状态变更通知、用户登录信息等。 发布者(Publi...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。