现在的位置: 首页 > 综合 > 正文

三国演义与设计模式之Decorator模式(1.虎牢关三英战吕布 2.云长降曹受封赏)

2012年10月25日 ⁄ 综合 ⁄ 共 5008字 ⁄ 字号 评论关闭
 写在前面:

以下三点,适用于本系列所有文章。

1、同一个故事,从不同的角度看,就可以引申出不同的模式。本系列中所举模式,未必是当前故事是匹配的,还望大家多提意见,一起讨论,一起提高。

2、类图部分只为突出要点,非重点处不必追究细节。如下面的例子人物应为SingleTon,在“吕布”和“武将”两个Class之间再加一层“董卓武将”似乎更合情理。但若面面俱到,则难免喧宾夺主之嫌,故略之。以后各篇也是如此,不便处敬请谅解。

3、文中所举三国故事,细节上多有虚构戏说成分,旨在说明问题,一笑了之,切莫当真。

 

Decorator模式(1.虎牢关三英战吕布    2.云长降曹受封赏)

第一个例子取材自三国演义第五回《发矫诏诸镇应曹公 破关兵三英战吕布》,说的是十八路诸候大军逼向虎牢关,吕布率部来迎,连胜盟军数员大将。此时张飞出马,原文精彩部分如下“飞抖擞精神,酣战吕布。连斗五十余合,不分胜负。云长见了,把马一拍,舞八十二斤青龙偃月刀,来夹攻吕布。三匹马丁字儿厮杀。战到三十合,战不倒吕布。刘玄德掣双股剑,骤黄鬃马,刺斜里也来助战。这三个围住吕布。转灯儿般厮杀。八路人马,都看得呆了。吕布架隔遮拦不定,看着玄德面上,虚刺一戟,玄德急闪。吕布荡开阵角,倒拖画戟,飞马便回。三个那里肯舍,拍马赶来。八路军兵,喊声大震,一齐掩杀。”

刘关张何以战败吕布?用的正是装饰者(Decorator)模式。

将故事抽象为如下类图

 

本例中,两员武将“单挑”,每回合本应各发一招,但通过Decorator模式,张飞聚合了关羽,关羽又聚合了刘备。也就是张飞的“单挑”方法,在对外接口不变(吕布不知情)的情况下,被另外添加了新的行为,导致吕布每回合实际受到了三个人的攻击,故而落败。

代码如下:

using System;

namespace ConsoleApplication1
{

    
public class Client
    
{
        [STAThread]
        
static void Main(string[] args)
        
{

            武将 吕 
= new 吕布();

            菜鸟武将 方 
= new 方悦();
            菜鸟武将 穆 
= new 穆顺();
            菜鸟武将 武 
= new 武安国();
            菜鸟武将 公孙 
= new 公孙瓒();

            桃园英雄 张 
= new 张飞(new 关羽(new 刘备(null)));

            PK(
1,吕,方);
            PK(
2,吕,穆);
            PK(
3,吕,武);
            PK(
4,吕,公孙);

            PK(
5,吕,张);
            Console.ReadLine();

        }


        
private static void PK(int num,武将 吕,武将 诸侯武将)
        
{
            Console.WriteLine(
"第{0}场 1回合:",num);
            吕.单挑();
            诸侯武将.单挑();
            Console.WriteLine(
"----------------------");
        }


    }



    
public abstract class 武将
    
{
        
public abstract void 单挑();
    }


    
public abstract class 十八路诸侯武将 : 武将
    
{
    }


    
public abstract class 菜鸟武将 : 十八路诸侯武将
    
{
    }


    
public abstract class 桃园英雄 : 十八路诸侯武将
    
{
        
protected 桃园英雄 m_兄弟 = null;
        
public 桃园英雄(桃园英雄 帮手)
        
{
            m_兄弟 
= 帮手;
        }

    }


    
public class 吕布 : 武将
    
{
        
public 吕布()
        
{}

        
public override void 单挑()
        
{
            Console.WriteLine(
"吕布出手!!!");
        }

    }


    
方悦、穆顺、武安国、公孙瓒

    
刘、关、张

}

 

执行效果图如下:

Decorator模式强调的是在不改变现有对象的基础上,动态修饰对象的行为。而且这种行为的添加,对客户端来说是透明(不知情,即客户端不用改动)的。并且,通过继承+聚合的这种结构关系,使得行为可以无限制地修饰下去。

当然Decorator的局限性也很明显。个人觉得,Decorator模式可能更适合于装饰整个新行为的过程中(新行为所在对象创建时和新行为执行时),不与原有行为发生数据冲突的情况。比如说简单的打印报表,报表的主体不变,页头和页尾都可以用不同的样式来修饰,这时用Decorator就比较合适。但如果主体行为是个update数据库的动作,装饰上的新行为也是一些update和delete的动作,这时用Decorator模式就要多加小心,因为前后几次的行为有数据上的共享,很容易出现逻辑不清,特别是装饰和层次再稍微多些的话,更要开发者必须非常清楚整个流程。

就把前面战吕布的例子作个引申:
张飞策马出阵的途中,在路上看见一瓶三鞭酒,张飞心想,“好啊,二哥斩了华雄,喝的也不过是军中的水酒。俺老张运气好,竟能喝到这喝了后一晚上能长一寸胡子的三鞭酒。战情紧急,待俺打发了吕布,再饮也不迟。”所以,张飞上阵,和吕布虚晃了一招,再回头取酒。“咦?找不到了,酒怎么没了,哪里去了?”

酒哪里去了?原来张飞在和吕布单挑时,将关羽聚合出来帮忙,偏偏关羽实例化时的构造函数有这样一段代码

              while(吕布未到())
               {
                     if (发现宝贝())
                            捡宝贝();
                     赶路++;
                }

结果就是那瓶三鞭酒自然被云长收入怀中了,而这一切张飞并不知情。

所以,对于有可能发生数据冲突的Decorator模式,开发时应当多考虑一下前因后果。

可能有人要问,在上面的UML图中,桃园英雄向自身聚合的那根线,如果不指向自身,而指向“十八路诸侯武将”会是什么样的效果?

其实两者都是Decorator模式,因适用场合不同,所以结构也略有不同。我们不必拘泥于细节,只要抓住Decorator的本质思想就可以了。

如果那根聚合线指向“十八路诸侯武将”,桃园英雄带的帮手就可以不仅仅是刘关张了,可以包括菜鸟武将,当然了,菜鸟武将是不能继续聚合新的武将的(怎么有点象马和驴交配生了骡子,骡子却不能再生育的感觉啊,呵呵)。这是两者的差别,可以体会一下。

再举个例子,出自《三国演义》第二十五回《屯土山关公约三事 救白马曹操解重围》看下图。

上面的类图,如果在使用时,不聚合新对象的话,相当于没有装饰,就是一个普通的多态形式。

且说关羽入了相府之后,官拜汉寿亭候,三日一小宴,五日一大宴,封赏不绝。

我们看在这里如何是使用Decorator的

第一日  
使者:“封关羽为汉寿亭侯~~~~”。
关羽:“云长受下,多谢丞相!!!”
代码:关羽.受赏(new 侯爵());

第二日  
使者:“送关羽锦袍~~~~”。
关羽:“云长受下,多谢丞相!!!”
代码:关羽.受赏(new 锦袍());

第三日  
使者:“送关羽赤兔马~~~~。”
关羽:“云长受下,多谢丞相!!!等等,马屁股上驮的什么?美女哎,知我者,曹丞相也 ^_^”
代码为:关羽.受赏(new 赤兔马(new 美女()));

封爵送袍都可以声张,送美女不能声张,要给云长留些面子。所以美女要装饰到赤兔马上,若二嫂问起,只答送马便是。

上图中,如果让关羽也实现“丞相封赏”这个接口行不行呢?这样不影响关羽受赏,好象功能还多了点?

这取决于系统实际的需求,不需要的功能最好不要加,不然可能会节外生枝,看下面这个例子。

某日,曹操召夏侯渊来见,“妙才啊,你平日出生忘死,屡立战功,今天终于有空,我可要好好奖励你一番,按单去领赏吧。”曹操抛下一张纸。

夏侯渊捡起来一看,“第一个,美女,我喜欢。第二个,金银,我喜欢。第三个,锦袍,我喜欢。第四个。。。关羽?哇靠,丞相,你当我是Gay啊!”

抱歉!评论已关闭.