设计模式之 面向对象的养猪厂的故事,C#演示(二)
(三) 优先使用聚合,而不是继承
有一段时间,养猪场的老板雇用了清洁工人来打扫猪舍。但有一天,老板忽然对自己说"不对啊,既然我有机器人,为什么还要雇人来做这件事情?应该让机器人来打扫宿舍!"
于是,这个需求被提交到了机器人的研发小组。看到这个需求,我们敏感地意识到,这是一个潜藏了更多变化的需求,未来机器人的功能还可能会不断增加,于是,我们提取出了一个抽象的机器人接口,并实现了两个具体的机器人类一-喂猪机器人和清洁机器人。系统的结构如图V8-1所示。
图V8-1
这样一来,老板希望机器人工作时,可以调用机器人接口的"工作"方法。由于这也是针对接口编程,当老板需要新的机器人时,只要添加具体的机器人类即可,其他代码无需变化。这似乎己经解决了我们遇到的问题。
但这里还存在另外一个问题:图v8-1 中的继承结构是在编译期间就确定了的,在运行期不能发生任何变化。因此,如果养猪场需要一个喂猪机器人和→个清洁机器人,那么我们必须在养猪场中放进这两个具体的机器人。依此类推,如果未来养猪场还需要兽医机器人、屠宰机器人等等,养猪场中不就挤满了机器人吗?更为重要的是,每添加一种机器人的类型,主是们就必须改动代码中的某一个地方,以便把这个机器人放进养猪场中,这就又会违反开闭原则了。在这种情况下,使用聚合的机制能很好地解决问题,因为基于聚合的结构可以在运行期间发生变化。
使用聚合机制的养猪场如图v10-1 所示。我们把机器人接口改成了功能接口,而清洁功能和喂猪功能实现了这个功能接口。真正的机器人类中聚合了一个功能接口的引用,这样,我们只需要在养猪场中放进一个机器人,该机器人中聚合了一个喂猪功能,这时它是一个喂猪机器人。当我们需要打扫养猪场时,老板只需要调用机器人中的"变形"方法,并传递一个"清洁功能"对象给机器人,机器人就会像《变形金刚》中的"擎天柱"一样,大吼一声"汽车人,变形"就变成了-个清洁机器人了。
图v10-1
面向对象养猪厂V10版本实现代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms; namespace PigFactoryV10
{
/*
* 猪悟能的博客
* http://www.cnblogs.com/hackpig/
*
有一段时间,老板雇用清洁工人打扫猪厂,但有一天,老板对自己说“我有了机器人,为什么还要雇用人打扫猪场?”
于是,软件团队必须开发新的清洁机器人品种
这里,机器人的类型成了变化点 下面的代码使用了聚合方式,把机器人功能变成接口,只要操作员调用“变形”功能,并传入一个“清洁功能”,机器人就会
由喂猪机器人变身为清洁机器人。
如果未来要增加屠宰机器人,原有代码也不用修改。 这里反映出设计模式的第三个核心设计原则: 优先使用聚合而不是继承。
*/ public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
btn_clean.Click += new EventHandler(btn_clean_Click);
btn_feed.Click += new EventHandler(btn_feed_Click);
} void btn_feed_Click(object sender, EventArgs e)
{
adminMan man1 = new adminMan(, "大润发养猪场");
this.rtbMsgInfo.Text = man1.Msg;
} void btn_clean_Click(object sender, EventArgs e)
{
adminMan man1 = new adminMan(, "大润发养猪场");
this.rtbMsgInfo.Text = man1.Msg;
}
} public class adminMan:feedPigFactory
{
private string _msg; public string Msg
{
get { return _msg; }
set { _msg = value; }
} public adminMan(int funId,string factoryname)
{
base.FactoryName = factoryname;
robot robot1 = null;
switch (funId)
{
case : //喂食
robot1= new robot();
IList<Ipig> list1=new List<Ipig>();
list1.Add(new dbPig());
list1.Add(new dbPig());
list1.Add(new dbPig());
robot1.transformation(new feedPig(list1));
this._msg = robot1.work();
break;
case : //清洁
robot1= new robot();
robot1.transformation(new clean());
this._msg= robot1.work();
break;
default:
break;
}
} } public interface IfunInterface
{
string 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();
}
} public class clean : IfunInterface
{
public string work()
{
return "正在打扫清洁..."+Environment.NewLine;
}
} public class feedPig : IfunInterface
{
IList<Ipig> pigList = new List<Ipig>(); public feedPig(IList<Ipig> plist)
{
foreach (Ipig m in plist)
pigList.Add(m);
} public feedPig()
{
} public void Attack(Ipig pig)
{
pigList.Add(pig);
} public string work()
{
string msgstr = string.Empty;
foreach (Ipig m in pigList)
{
msgstr += m.eat() + Environment.NewLine;
} return string.Format("{0}{1}{2}",
"大润发养猪场" + Environment.NewLine,
"喂猪机器人开始工作...." + Environment.NewLine + Environment.NewLine,
msgstr);
}
} public abstract class feedPigFactory
{
private string _factoryName; public string FactoryName
{
get { return _factoryName; }
set { _factoryName = value; }
} } public interface Ipig
{ int PigIndex
{
get;
set;
} string eat(); } public class cbPig : Ipig
{
private int _pigIndex; public int PigIndex
{
get { return _pigIndex; }
set { _pigIndex = value; }
}
public cbPig(int pignum)
{
this._pigIndex = pignum;
} public string eat()
{
return string.Format("{0}[{1}]开始吃.", "长白猪", _pigIndex);
}
} public class dbPig : Ipig
{
private int _pigIndex; public int PigIndex
{
get { return _pigIndex; }
set { _pigIndex = value; }
}
public dbPig(int pignum)
{
this._pigIndex = pignum;
} public string eat()
{
return string.Format("{0}[{1}]开始吃.", "大白猪", _pigIndex);
}
} }
运行结果如上图所示, 老板可以下达指令在喂食和清洁机器人之间切换了.
代码说明:
在这里,喂猪机器人类是把原来直接调用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 : //喂食
robot1= new robot();
IList<Ipig> list1=new List<Ipig>();
list1.Add(new dbPig());
list1.Add(new dbPig());
list1.Add(new dbPig());
robot1.transformation(new feedPig(list1));
this._msg = robot1.work();
break;
case : //清洁
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/
设计模式之 面向对象的养猪厂的故事,C#演示(二)的更多相关文章
- 设计模式之 面向对象的养猪厂的故事,C#演示(一)
对于设计模式, 从本质上说, 其最大的用途就是适应需求的变化. 因为有了设计模式,我们可以在设计阶段就为未来可能发生的变化留下足够的空间. 我们通过一个建造现代化养猪场的故事, 来讨论一下设计模式与需 ...
- javaScript设计模式之面向对象编程(object-oriented programming,OOP)(二)
接上一篇 面向对象编程的理解? 答:面向对象编程,就是将你的需求抽象成一个对象,然后针对这个对象分析其特征(属性)与动作(方法).这个对象我们称之为类.面向对象编程思想其中一个特点就是封装,就是把你需 ...
- [.net 面向对象程序设计进阶] (23) 团队开发利器(二)优秀的版本控制工具SVN(上)
[.net 面向对象程序设计进阶] (23) 团队开发利器(二)优秀的版本控制工具SVN(上) 本篇导读: 上篇介绍了常用的代码管理工具VSS,看了一下评论,很多同学深恶痛绝,有的甚至因为公司使用VS ...
- 201871010123-吴丽丽《面向对象程序设计(Java)》第十二周学习总结
201871010123-吴丽丽<面向对象程序设计(Java)>第十二周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ ...
- 201271050130-滕江南-《面向对象程序设计(java)》第十二周学习总结
201271050130-滕江南-<面向对象程序设计(java)>第十二周学习总结 项 目 内 容 这个作业属于哪个课程 https://www.cnblogs.co ...
- 201871010111-刘佳华《面向对象程序设计(java)》第十二周学习总结
201871010111-刘佳华<面向对象程序设计(java)>第十二周学习总结 实验十 集合与GUI初步 实验时间 2019-11-14 第一部分:基础知识总结 第九章知识总结 1. ...
- 201871010109-胡欢欢《面向对象程序设计(java)》第十二周学习总结
201871010109-胡欢欢<面向对象程序设计(java)>第十二周学习总结 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这 ...
- 201871010101-陈来弟《面向对象程序设计(Java)》第十二周学习总结
201871010101-陈来弟<面向对象程序设计(Java)>第十二周学习总结 实验十 集合与GUI初步 实验时间 2019-11-14 第一部分 理论部分 1.(1) 用户界面 ...
- 201871010104-陈园园 《面向对象程序设计(java)》第十二周学习总结
201871010104-陈园园 <面向对象程序设计(java)>第十二周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ ...
随机推荐
- ListView优化为何ViewHolder用static类(转载)
如果有人还不了解ViewHolder为什么可以起到优化作用,我这边再做下简单说明:Android的findViewById动作是比较耗时的,需要遍历布局的树形结构,才能找到相应的视图.所以如果想在这一 ...
- 前端工程师技能之photoshop巧用系列扩展篇——自动切图
× 目录 [1]初始设置 [2]自动切图 前面的话 随着photoshop版本的不断升级,软件本身增加了很多新的功能,也为切图工作增加了很多的便利.photoshop最新的版本新增了自动切图功能,本文 ...
- 开发笔记:基于EntityFramework.Extended用EF实现指定字段的更新
今天在将一个项目中使用存储过程的遗留代码迁移至新的架构时,遇到了一个问题——如何用EF实现数据库中指定字段的更新(根据UserId更新Users表中的FaceUrl与AvatarUrl字段)? 原先调 ...
- 百度地图JavaScript API覆盖物旋转时出现偏移
在项目中,调用百度地图JavaScript API,做覆盖物的旋转再添加到地图上,结果出现偏移了. 调试过程中的效果图: 发现图片的旋转并不是按车子的中心来的,而是之外的一个点.最后发现犯了一个很细节 ...
- 使用 CSS3 实现 3D 图片滑块效果【附源码下载】
使用 CSS3 的3D变换特性,我们可以通过让元素在三维空间中变换来实现一些新奇的效果. 这篇文章分享的这款 jQuery 立体图片滑块插件,利用了 3D transforms(变换)属性来实现多种不 ...
- javascript学习4
JavaScript Date(日期)对象 日期对象用于处理日期和时间. JavaScript Date(日期)对象 实例 返回当日的日期和时间 如何使用 Date() 方法或者当日的日期. getT ...
- elasticsearch中的API
elasticsearch中的API es中的API按照大类分为下面几种: 文档API: 提供对文档的增删改查操作 搜索API: 提供对文档进行某个字段的查询 索引API: 提供对索引进行操作 查看A ...
- Laravel5设计json api时候的一些道道
对于返回数据格式没规整的问题 在开发api的时候,这个问题是和客户端交涉最多的问题,比如一个user结构,返回的字段原本是个user_name的,它应该是string类型.但是呢,由于数据库设计这个字 ...
- 七、L2CAP
1. L2CAP 在BR/EDR模式下,在connection procedure成功执行后,两台设备通过一条物理信道(physical channel)连接在一起,同时两者之间建立起了一条 ...
- winform控件
公共控件:1.Button: Enabled - 开始不可用 Visible -不可视(用来设置权限,取用户看不见的值)2.CheckBox .CheckListBox -复选框,复选框组 3.Com ...