东北F4的设计模式情景剧——第一幕 装饰模式
第一幕 面临难题
场景设定:铁岭,晴朗午后,微风拂过。一栋颇具规模的办公楼三层外墙悬挂着一块褪色的红布横幅,上面用潦草的毛笔字写着"东北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盏灯全灭。从此踏上编程之路。我不像你们,是名校高材生,我是半路出家。但我有尊严!我要靠实力证明自己!
小沈阳(感动):太感人了!四儿,对不起,以后不再欺负你了!
刘能(擤鼻涕):太励志了!二哥以后再也不鄙视你了!
(宋小宝悄悄退后,不小心撞倒垃圾桶,被发现)
小沈阳、刘能(追赶):宋小宝,你站住!
(完)
