[设计模式-4] 外观模式

话三国~外观模式


五丈原诸葛禳星

诸葛亮六出祁山北伐曹魏,但始终无法解决粮草问题而无功而返。为了避免夜长梦多,诸葛亮必须迫使魏军决一死战,但司马懿老奸巨猾,也深知用兵之道,知道时间站在自己的这一边,因此始终坚守不出。诸葛亮心生一计,便派遣使者以送礼为名,赠送司马懿巾帼服装,嘲讽其不敢出战,乃一妇人也!岂知司马懿不但不动怒,还当众穿起,并问使者这样穿好不好看?司马懿的部将皆目露杀气,看主帅受辱,随时准备拔剑斩杀来使,唯独司马懿非常淡定并闲聊诸葛亮近况,使者据实以告,司马懿笑道:食少事烦,岂能长久。蜀使回归后,将情形告知诸葛亮,诸葛亮叹曰:彼深知我心也。

蜀汉主簿杨仪谏曰:我见丞相常常亲自处理很多琐事,我认为这非常不好!上下不可相侵,你身为丞相,只需交办属下做事即可,若凡事亲力亲为,最后将搞死自己而一事无成。孔明泣曰:我并不是不知道这个道理,但受到先帝托孤,我怕其他人不像我如此尽心尽力!众将听了皆暗自垂泪,按下不表。

诸葛亮一直是我非常喜欢的历史人物之一,每次看到这段历史故事,我都会认为刘备有部下如此,真的是死而无憾。但反过来讲,诸葛亮犯了什么错?以设计原则来看,我认为诸葛亮凡事亲力亲为,因此认识太多实践类,相依性太重,所以一旦诸葛亮这个类挂了,蜀汉这个系统就濒临崩坏的危险。

这样说可能大家还有点模糊,我们用案例来看就会比较容易了解。首先,我们先定义两个子系统,分别代表诸葛亮目前需要执行的内政工作和军事工作,内政包含了征收赋税以及土地开垦和防灾演练共三个类;军事包含了粮草补给以及训练士兵和调兵遣将也是三个类,而为了不复杂化,类的方法我都定义的很简单,大家可以直接往下看。

  class InternalAffairA
    {
        public void InternalMethoad()
        {
            Console.WriteLine("执内联政:征税赋税");
        }
    }

  class InternalAffairB
    {
        public void InternalMethoad()
        {
            Console.WriteLine("执内联政:土地开垦");
        }
    }

  class InternalAffairC
    {
        public void InternalMethoad()
        {
            Console.WriteLine("执内联政:防灾演练");
        }
    }

以上是三个内政子系统的类,都只有提供一个方法,分别输出:"征税赋税"和"土地开垦"以及"防灾演练"。

class MilitaryAffairA
    {
        public void InternalMethoad()
        {
            Console.WriteLine("执行军事:粮草补给");
        }
    }

class MilitaryAffairB
    {
        public void InternalMethoad()
        {
            Console.WriteLine("执行军事:训练士兵");
        }
    }

class MilitaryAffairC
    {
        public void InternalMethoad()
        {
            Console.WriteLine("执行军事:调兵遣将");
        }
    }

以上是三个军事子系统的类,也都只有提供一个方法,分别输出:"粮草补给"和"训练士兵"以及"调兵遣将"。

 class Program
    {
        ///Main相当于诸葛亮的类
        static void Main(string[] args)
        {
            var IaffairA = new InternalAffairA();
            var IaffairB = new InternalAffairB();
            var IaffairC = new InternalAffairC();
            var MaffairA = new MilitaryAffairA();
            var MaffairB = new MilitaryAffairB();
            var MaffairC = new MilitaryAffairC();

            IaffairA.InternalMethoad();
            IaffairB.InternalMethoad();
            IaffairC.InternalMethoad();
            MaffairA.InternalMethoad();
            MaffairB.InternalMethoad();
            MaffairC.InternalMethoad();

            Console.Read();
        }
    }

主角诸葛亮出现了,大家可以看到,诸葛亮必须认识以上两个子系统包含的六个类,而且必须直接依赖实践来调用它们的方法,换句话说,只要任何一个类的调用方式有异动,就代表主程序(诸葛亮)的类需要做更动。也难怪诸葛亮会累死了。那要如何改善这样的设计呢?其实内政和军事,是两个不着边的系统,否则吴国太也不会说,内事不决问张昭;外事不决问周瑜。而我们的主系统要跟子系统交互,却相依子系统的个别功能上,就显得太隅和了。这里要跟大家介绍的外观设计模式将会是一个很好的解决方案。

定义 

外观模式:可以定义一个高层的界面,来负责管理与子系统相依的琐事,让子系统更加适合使用。

回到刚刚的故事,诸葛亮要如何实做这个模式呢?其实,他真的可以不用每件事都自己管,但事情还是要做呀,一个很好的办法就是委派政务官,蜀汉在这个时期,其实人才还是很多的,比如在军事方面,蜀汉的魏延便是头号猛将;在内政方面,蒋婉也是一个不可多得的人才。转换成程序语言,便是我们要声明一个政务官的类,并提供对应的界面方法(蜀汉人才)来帮助操作子系统(内政和军事子系统),可参考下面的程序。

   /// 
    /// 委派政务官
    /// 
    class FacadeManagers
    {
        InternalAffairA IaffairA;
        InternalAffairB IaffairB;
        InternalAffairC IaffairC;
        MilitaryAffairA MaffairA;
        MilitaryAffairB MaffairB;
        MilitaryAffairC MaffairC;

        public FacadeManagers()
        {
            IaffairA = new InternalAffairA();
            IaffairB = new InternalAffairB();
            IaffairC = new InternalAffairC();
            MaffairA = new MilitaryAffairA();
            MaffairB = new MilitaryAffairB();
            MaffairC = new MilitaryAffairC();
        }

        /// 
        /// 可派魏延执行军事
        /// 
        public void MilitaryOperate()
        {
            MaffairA.InternalMethoad();
            MaffairB.InternalMethoad();
            MaffairC.InternalMethoad();
        }

        /// 
        /// 可派蒋婉管理内政
        /// 
        public void InternalAffairOperate()
        {
            IaffairA.InternalMethoad();
            IaffairB.InternalMethoad();
            IaffairC.InternalMethoad();
        }
    }

我们定义了FacadeManagers(政务官)的类,由这个类来认识所有子系统的类和方法,并声明了MilitaryOperate方法(代表魏延)负责军事子系统的相关功能执行;再声明InternalAffairOperate(代表蒋婉)负责内政子系统的相关功能执行。

 class Program
    {
        static void Main(string[] args)
        {
            var Managers = new FacadeManagers();
            Managers.InternalAffairOperate();
            Managers.MilitaryOperate();

            Console.Read();
        }
    }

接下来我们再来看Main(诸葛亮),我们可以发现Main只需要认识FacadeManager(政务官)就可以了,所以如果子系统有异动,只有FacadeManager需要配合修正,而完全不需要更动到主程序的类。

大家看到这,应该可以知道外观模式的威力了,但可能还会有一点疑问,比如说何时可以使用这个设计模式呢?最常见的就是当子系统因不断的重构或者新增功能,可能会产生很多方法,以本篇的例子,军事的子系统除了"运粮补给"和"训练士兵"跟"调兵遣将"外,之后会不会又有"招募士兵","拜访武将","防守城池"等等方法出现,如果能定义一个Facade类,将可以大大减少隅和,并更方便地使用;另外一个时机点便是新系统要跟旧系统交互,但你可能1.不想花很多时间更改旧系统,也有可能2.不想让新系统依赖旧系统那堆恶心的架构或者方法的实践。PS.如果你能忍受第二点,我的建议是也不需要写新系统了。在这个时机点,Facade就会非常好用,把那些恶心的依赖封装在Facade而让新系统可以透过Facade简洁的界面来操作,可以大大提升新系统未来的可维护性,而不会被旧系统牵着鼻子走。

好了,设计模式说完了,那大家觉得诸葛亮会接受这个建议吗?我觉得还是不会,因为诸葛亮表示:唯恐他人不似我尽心阿。其实,好的团队应该先从传球做起,但诸葛亮并不是为了自己的利益才这样做,而是有更崇高的理想,因此这里也就不忍苛责了。