设计模式之 面向对象的养猪厂的故事,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类图.

时间: 2024-07-31 08:26:30

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

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

对于设计模式, 从本质上说, 其最大的用途就是适应需求的变化. 因为有了设计模式,我们可以在设计阶段就为未来可能发生的变化留下足够的空间. 我们通过一个建造现代化养猪场的故事, 来讨论一下设计模式与需要变化之间的关系. (一)设计模式最根本的意图是适应需求的变化 一个机器人研发小组研制了一种能自动喂猪的机器人, 于是有人投资兴建了一座由机器人管理的养猪场. 这个养猪场要饲养的猪的品种包括: 大白猪: 又叫"大约克郡猪", 原产于英国. 全身白色,耳向前挺立,体长大,成年公猪体重三百到五

【设计模式】面向对象基础

最近一直在学习<大话设计模式>根据师傅对我的指导,让我先学习这本书的附录--面向对象基础.说实在话,面向对象技术从很早就接触了,从刚开始的C++,其次是软工视频,然后是UML,紧接着是C#,直到现在的设计模式.. 总的来说,在设计模式之前,我对面向对象的了解还只是停留在封装.继承.多态的层次上,虽然在C#视频中,有全面的讲解了一下,但是自己还是晕晕乎乎的,脑子中还是有很多疑云..直到这次的设计模式,通过通过幽默生动.而又通俗易懂的故事,对面向对象技术深入浅出,我才算是真正的入门了.. 这篇博客

【小话设计模式】面向对象设计原则

1.单一职责原则 单一职责原则的核心思想就是:系统中的每一个对象都应该只有一个单独的职责,而所有对象所关注的就是自身职责的完成.英文缩写SRP  Single Responsibility Principle 单一职责原则-->"高内聚,低耦合",每个类应该只有一个职责,此外只能提供一种功能,而引起类变化的原因应该只有一个.在设计模式中,所有的设计模式都遵循这一原则. 优点: 可以降低类的复杂度: 提高类的可读性,提高系统的可维护性: 变更引起的风险降低. 2.里氏替换原则 里氏

【设计模式】面向对象小结——重写与重载

通过对<大话设计模式>附录的学习,让我对面向对象技术有了更深一层的理解!下面是我画的一张导图,是我对面向对象技术的总体概括.这篇文章的主要内容,就是围绕这张图,来讲述我的理解.. 由图得知,我将面向对象分为三个部分:基本.特点及其功能.它们之间像是一种层层递进的关系. 如果没有基本的抽象思想,将具有相同属性和功能的对象进行整合,那么也就没有面向对象技术,更不可能产生面向对象特点.所以,抽象是核心,是基本:如果没有封装.继承和多态,那么类与类.对象与对象.类与对象之间,就没有了联系,就不能实现对

跟我学设计模式视频教程——面向对象理论,模板方法

课程视频 模板方法 面向对象理论 唠嗑 课程笔记 课程笔记 课程代码 课程代码 新课程火热报名中 课程介绍 跟我学设计模式视频教程--面向对象理论,模板方法

C++ Primer 学习笔记_34_面向对象编程(5)--虚函数与多态(二):纯虚函数、抽象类、虚析构函数、动态创建对象

C++ Primer 学习笔记_34_面向对象编程(5)--虚函数与多态(二):纯虚函数.抽象类.虚析构函数.动态创建对象 一.纯虚函数 1.虚函数是实现多态性的前提 需要在基类中定义共同的接口 接口要定义为虚函数 2.如果基类的接口没办法实现怎么办? 如形状类Shape 解决方法 将这些接口定义为纯虚函数 3.在基类中不能给出有意义的虚函数定义,这时可以把它声明成纯虚函数,把它的定义留给派生类来做 4.定义纯虚函数: class <类名> { virtual <类型> <函

[.net 面向对象程序设计进阶] (23) 团队开发利器(二)优秀的版本控制工具SVN(上)

[.net 面向对象程序设计进阶] (23) 团队开发利器(二)优秀的版本控制工具SVN(上) 本篇导读: 上篇介绍了常用的代码管理工具VSS,看了一下评论,很多同学深恶痛绝,有的甚至因为公司使用VSS离职的.其实使用什么代码管理工具要看项目而定.毕竟使用何用代码管理工具,是项目管理者根据需要来决定的,如果你是一个开发人员,首先要让自己的技术精进一点.下面根据我个人理解,把这几种常见的代码管理工具使用场景简单介绍一下. 1.几种代理管理工具的适用场景 A.如果你的项目是5-6人的小团队,那么使用

张季跃 201771010139《面向对象程序设计(java)》第十二周学习总结

张季跃 201771010139<面向对象程序设计(java)>第十二周学习总结 第二部分:实验部分 1.实验目的与要求 (1) 掌握Java GUI中框架创建及属性设置中常用类的API: (2) 掌握Java GUI中2D图形绘制常用类的API: (3) 了解Java GUI中2D图形中字体与颜色的设置方法: (4) 了解Java GUI中2D图像的载入方法. 2.实验内容和步骤 实验1: 导入第9章示例程序,测试程序并进行代码注释. 2.实验内容和步骤 实验1: 导入第10章示例程序,测试

设计模式2 面向对象设计原则

面向对象设计原则  原则的目的 面向对象设计原创表  单一职责原则案例 开闭原则 案例 依赖倒转原则 案例 面向对象设计原则  对于面向对象软件系统的设计而言,在支持可维护性的同时,提高系统的可复用性是一个至关重要的问题,如何同时提高一个软件系统的可维护性和可复用性是面向对象设计需要解决的核心问题之一.在面向对象设计中,可维护性的复用是以设计原则为基础的.每一个原则都蕴含一些面向对象设计的思想,可以从不同的角度提升一个软件结构的设计水平.  面向对象设计原则为支持可维护性复用而诞生,这些原则蕴含