• 设计模式之 面向对象的养猪厂的故事,C#演示(二)


    (三) 优先使用聚合,而不是继承

    有一段时间,养猪场的老板雇用了清洁工人来打扫猪舍。但有一天,老板忽然对自己说"不对啊,既然我有机器人,为什么还要雇人来做这件事情?应该让机器人来打扫宿舍!"
    于是,这个需求被提交到了机器人的研发小组。看到这个需求,我们敏感地意识到,这是一个潜藏了更多变化的需求,未来机器人的功能还可能会不断增加,于是,我们提取出了一个抽象的机器人接口,并实现了两个具体的机器人类一-喂猪机器人和清洁机器人。系统的结构如图V8-1所示。

                                  图V8-1

    这样一来,老板希望机器人工作时,可以调用机器人接口的"工作"方法。由于这也是针对接口编程,当老板需要新的机器人时,只要添加具体的机器人类即可,其他代码无需变化。这似乎己经解决了我们遇到的问题。
    但这里还存在另外一个问题:图v8-1 中的继承结构是在编译期间就确定了的,在运行期不能发生任何变化。因此,如果养猪场需要一个喂猪机器人和→个清洁机器人,那么我们必须在养猪场中放进这两个具体的机器人。依此类推,如果未来养猪场还需要兽医机器人、屠宰机器人等等,养猪场中不就挤满了机器人吗?更为重要的是,每添加一种机器人的类型,主是们就必须改动代码中的某一个地方,以便把这个机器人放进养猪场中,这就又会违反开闭原则了。在这种情况下,使用聚合的机制能很好地解决问题,因为基于聚合的结构可以在运行期间发生变化。
    使用聚合机制的养猪场如图v10-1 所示。我们把机器人接口改成了功能接口,而清洁功能和喂猪功能实现了这个功能接口。真正的机器人类中聚合了一个功能接口的引用,这样,我们只需要在养猪场中放进一个机器人,该机器人中聚合了一个喂猪功能,这时它是一个喂猪机器人。当我们需要打扫养猪场时,老板只需要调用机器人中的"变形"方法,并传递一个"清洁功能"对象给机器人,机器人就会像《变形金刚》中的"擎天柱"一样,大吼一声"汽车人,变形"就变成了-个清洁机器人了。

                                        图v10-1

    面向对象养猪厂V10版本实现代码如下:

      1 using System;
      2 using System.Collections.Generic;
      3 using System.ComponentModel;
      4 using System.Data;
      5 using System.Drawing;
      6 using System.Linq;
      7 using System.Text;
      8 using System.Windows.Forms;
      9 
     10 namespace PigFactoryV10
     11 {
     12     /*
     13      *  猪悟能的博客
     14      * http://www.cnblogs.com/hackpig/
     15      * 
     16      有一段时间,老板雇用清洁工人打扫猪厂,但有一天,老板对自己说“我有了机器人,为什么还要雇用人打扫猪场?”
     17      于是,软件团队必须开发新的清洁机器人品种
     18      这里,机器人的类型成了变化点
     19      
     20      下面的代码使用了聚合方式,把机器人功能变成接口,只要操作员调用“变形”功能,并传入一个“清洁功能”,机器人就会
     21      由喂猪机器人变身为清洁机器人。
     22      如果未来要增加屠宰机器人,原有代码也不用修改。
     23      
     24      这里反映出设计模式的第三个核心设计原则:
     25      
     26      优先使用聚合而不是继承。
     27      */
     28 
     29     public partial class Form1 : Form
     30     {
     31         public Form1()
     32         {
     33             InitializeComponent();
     34             btn_clean.Click += new EventHandler(btn_clean_Click);
     35             btn_feed.Click += new EventHandler(btn_feed_Click);
     36         }
     37 
     38         void btn_feed_Click(object sender, EventArgs e)
     39         {
     40             adminMan man1 = new adminMan(1, "大润发养猪场");
     41             this.rtbMsgInfo.Text = man1.Msg;
     42         }
     43 
     44         void btn_clean_Click(object sender, EventArgs e)
     45         {
     46             adminMan man1 = new adminMan(2, "大润发养猪场");
     47             this.rtbMsgInfo.Text = man1.Msg;
     48         }
     49     }
     50 
     51 
     52     public class adminMan:feedPigFactory
     53     {
     54         private string _msg;
     55 
     56         public string Msg
     57         {
     58             get { return _msg; }
     59             set { _msg = value; }
     60         }
     61 
     62         public adminMan(int funId,string factoryname)
     63         {
     64             base.FactoryName = factoryname;
     65             robot robot1 = null;
     66             switch (funId)
     67             {
     68                 case 1:     //喂食
     69                     robot1= new robot();
     70                     IList<Ipig> list1=new List<Ipig>();
     71                     list1.Add(new dbPig(1));
     72                     list1.Add(new dbPig(2));
     73                     list1.Add(new dbPig(3));
     74                     robot1.transformation(new feedPig(list1));
     75                     this._msg = robot1.work();
     76                     break;
     77                 case 2:     //清洁
     78                     robot1= new robot();
     79                     robot1.transformation(new clean());
     80                     this._msg= robot1.work();
     81                     break;
     82                 default:
     83                     break;
     84             }
     85         }
     86 
     87 
     88     }
     89 
     90 
     91     public interface IfunInterface
     92     {
     93         string work();
     94     }
     95 
     96     public class robot
     97     {
     98         private IfunInterface _robotFun;
     99 
    100         public IfunInterface RobotFun
    101         {
    102             get { return _robotFun; }
    103             set { _robotFun = value; }
    104         }
    105 
    106         public void transformation(IfunInterface robotFun)
    107         {
    108             this._robotFun = robotFun;
    109         }
    110         public string work()
    111         {
    112            return this._robotFun.work();
    113         }
    114     }
    115 
    116 
    117 
    118 
    119 
    120     public class clean : IfunInterface
    121     {
    122         public string work()
    123         {
    124             return "正在打扫清洁..."+Environment.NewLine;
    125         }
    126     }
    127 
    128     public class feedPig : IfunInterface
    129     {
    130         IList<Ipig> pigList = new List<Ipig>();
    131 
    132         public feedPig(IList<Ipig> plist)
    133         {
    134             foreach (Ipig m in plist)
    135                 pigList.Add(m);
    136         }
    137 
    138         public feedPig()
    139         {
    140         }
    141 
    142         public void Attack(Ipig pig)
    143         {
    144             pigList.Add(pig);
    145         }
    146 
    147         public string work()
    148         {
    149             string msgstr = string.Empty;
    150             foreach (Ipig m in pigList)
    151             {
    152                 msgstr += m.eat() + Environment.NewLine;
    153             }
    154 
    155             return string.Format("{0}{1}{2}",
    156                 "大润发养猪场" + Environment.NewLine,
    157                 "喂猪机器人开始工作...." + Environment.NewLine + Environment.NewLine,
    158                 msgstr);
    159         }
    160     }
    161 
    162 
    163 
    164     public abstract class feedPigFactory
    165     {
    166         private string _factoryName;
    167 
    168         public string FactoryName
    169         {
    170             get { return _factoryName; }
    171             set { _factoryName = value; }
    172         }
    173 
    174     }
    175 
    176 
    177 
    178     public interface Ipig
    179     {
    180 
    181         int PigIndex
    182         {
    183             get;
    184             set;
    185         }
    186 
    187         string eat();
    188 
    189     }
    190 
    191 
    192     public class cbPig : Ipig
    193     {
    194         private int _pigIndex;
    195 
    196         public int PigIndex
    197         {
    198             get { return _pigIndex; }
    199             set { _pigIndex = value; }
    200         }
    201         public cbPig(int pignum)
    202         {
    203             this._pigIndex = pignum;
    204         }
    205 
    206         public string eat()
    207         {
    208             return string.Format("{0}[{1}]开始吃.", "长白猪", _pigIndex);
    209         }
    210     }
    211 
    212 
    213 
    214     public class dbPig : Ipig
    215     {
    216         private int _pigIndex;
    217 
    218         public int PigIndex
    219         {
    220             get { return _pigIndex; }
    221             set { _pigIndex = value; }
    222         }
    223         public dbPig(int pignum)
    224         {
    225             this._pigIndex = pignum;
    226         }
    227 
    228         public string eat()
    229         {
    230             return string.Format("{0}[{1}]开始吃.", "大白猪", _pigIndex);
    231         }
    232     }
    233 
    234 
    235 
    236 }

    运行结果如上图所示, 老板可以下达指令在喂食和清洁机器人之间切换了.

    代码说明:

     在这里,喂猪机器人类是把原来直接调用Work() 喂食方法, 变成了先由transformation()指定功能类型, 再来执行Work().

    public class robot
        {
            private IfunInterface _robotFun;
    
            public IfunInterface RobotFun
            {
                get { return _robotFun; }
                set { _robotFun = value; }
            }
    
            public void transformation(IfunInterface robotFun)
            {
                this._robotFun = robotFun;
            }
            public string work()
            {
               return this._robotFun.work();
            }
        }

    而功能类型就是个IfunInterface接口, 而clearn(打扫清洁功能), feedPig(喂猪功能), 都是承继这个接口的.

    public interface IfunInterface
        {
            string work();
        }
    public class clean : IfunInterface
    public class feedPig : IfunInterface

    最后利用工厂方法, 决定了机器人在喂食,还是清洁两种功能之间切换.

     public adminMan(int funId,string factoryname)
            {
                base.FactoryName = factoryname;
                robot robot1 = null;
                switch (funId)
                {
                    case 1:     //喂食
                        robot1= new robot();
                        IList<Ipig> list1=new List<Ipig>();
                        list1.Add(new dbPig(1));
                        list1.Add(new dbPig(2));
                        list1.Add(new dbPig(3));
                        robot1.transformation(new feedPig(list1));
                        this._msg = robot1.work();
                        break;
                    case 2:     //清洁
                        robot1= new robot();
                        robot1.transformation(new clean());
                        this._msg= robot1.work();
                        break;
                    default:
                        break;
                }
            }

    实际上我们是聚合IfunInterface这个抽象接口,即通过指向接口类的引用来访问对象, 这种实现方法其实是综合了聚合与继承两种机制的方式

    此后,当我们添加一个新的机器人种类(如兽医机器人)时,只需要添加一个兽医功能的派生类,老板就可以根据自己的需要,在任何时刻命令机器人在三个种类之间随意变形。可以看出,添加一个机器人类型时,需要改动的代码都在系统外部,系统内已有的代码不需要发生变化。这里的聚合机制使我们很好地满足了开闭原则。

    总之,继承和聚合是两种各不相同也各有优缺点的机制:

    • 继承反映的是类之间"……是一个……"这样的关系,它在编译期间静态定义。继承的优点是使用起来比较简单(因为面向对象的语言直接支持继承机制),对设计

    人员来说比较容易理解。但继承也有缺点:

    首先,你不能在运行期间改变继承树的结构,因为继承是在编译期间定义的:

    其次,基类中往往定义了部分的实现,基类的实现暴露给派生类后,继承机制就会破坏数据和操作的封装,使派生类对基类产生较强的依赖O

    • 聚合反映的是类之间"有-个……"或"……包含一个……"的关系,它是在运行期间动态定义的,因此,被聚合对象的类型可以很容易地在运行期间发生变化,只要我们保证它们的接口相同,满足完全替换原则即可。而且,使用聚合可以更好地封装对象,使每一个类集中在单个职能上,类的继承层次也会保持较小的规模,不会造成类数量的爆炸。聚合的缺点是它并不是面向对象语言直接支持的一个特性,用户必须编写一些代码来完成聚合功能。例如,上面机器人类中的"工作"方法就必须把消息转发给内部聚合的功能对象,即调用功能对象的"工作"方法。被聚合对象的接口必须遵从聚合类的要求,这种消息转发的方式又被称为"委托( Delegation ) "。一般来说,聚合的结构比继承更难理解一些。

    从上面的分析可以看出,聚合在某些方面比继承更为优越。但我们强调聚合的作用绝不是否定继承的优点。使用聚合时,我们必须遵循针对接口编程的设计原则,不能聚合某一个具体的派生类对象,而应该聚合该类的抽象接口,即通过指向接口类的引用或指针来访问对象----这种实现方法其实是综合了聚合与继承两种机制的方式。


    由此,我们可以总结出设计模式的第三个核心设计原则
    继承反映的是类之间的"……是一个…"的关系,聚合反映的是类之间"…有一个……"或包含一个……"的关系。在不违反这个关系前提下,应该
    优先使用聚合而不是继承, 同时,聚合也必须和接口及相关的继承结构协同使用。

    全文完.

    本文源代码下载

    包括面向对象养猪厂的各种版本实现代码(C#示例), 和VS2010绘制的UML类图.

    原创文章,出处 : http://www.cnblogs.com/hackpig/

  • 相关阅读:
    算法15 《啊哈算法》第四章 盒子装扑克-DFS深度优先搜索 递归
    算法14 leetcode28 实现 strStr() kmp
    markdown一些有用笔记
    算法11 leetcode274 有效的字母异位词
    Quantity? Quality!
    算法 10 leetcode344. 反转字符串
    JavaWeb —— JDBC Driver驱动及连接问题
    Python —— 4 习题练习
    Python —— 变量的作用域
    JavaWeb —— 文件上传
  • 原文地址:https://www.cnblogs.com/hackpig/p/5760664.html
Copyright © 2020-2023  润新知