免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2539 | 回复: 8
打印 上一主题 下一主题

对象建模笔记--角色建模1 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2003-04-23 20:37 |只看该作者 |倒序浏览
对象建模笔记--角色建模1

摘要
??面向对象技术其实是一种以现实世界的自然观点看待软件代码的方法。原先的软件的编程方式是过程式的,但是这种过程式的编程方式没有办法处理过多的代码行,因为人的思考能力是有限的,很少有人同一时刻看的代码能够超过500行以上的,当然,有特异功能者除外。人们发明了非常多的方法来使过程式编程方法更加的方便,其基本的思路都是通过一些技术,对大段的代码行进行分割,例如代码块、函数、模块、工程、静态联编、动态联编等等。但是直到面向对象技术出现之前,并没有出项完整、彻底的解决方案。(2002-10-21 12:51:35)

论坛徽章:
0
2 [报告]
发表于 2003-04-23 20:38 |只看该作者

对象建模笔记--角色建模1

面向对象技术其实是一种以现实世界的自然观点看待软件代码的方法。原先的软件的编程方式是过程式的,但是这种过程式的编程方式没有办法处理过多的代码行,因为人的思考能力是有限的,很少有人同一时刻看的代码能够超过500行以上的,当然,有特异功能者除外。人们发明了非常多的方法来使过程式编程方法更加的方便,其基本的思路都是通过一些技术,对大段的代码行进行分割,例如代码块、函数、模块、工程、静态联编、动态联编等等。但是直到面向对象技术出现之前,并没有出项完整、彻底的解决方案。

??我们刚开始学习面向对象的时候,总是要了解上一堆狗啊,哺乳动物啊之类的关系,其实这些都是为了向我们展示面向对象是符合人类对世界的认识的,也就是符合人类的世界观的。但是糟糕的是,面向对象技术看起来似乎应该是符合人们的世界观的,但是好像却要比过程式编程技术要难的多。好吧,我们撰写此文的目的就是为了解决这个似是而非的问题,揭开面向对象的面纱。你会发现,面向对象其实就是这么自然。

??我们所有讨论的例子都集中在企业应用中,一方面的原因是企业应用其实是人类社会的一个缩影,能够灵活的运用面向对象技术来分析企业应用,那应该说你已经到了老鸟的级别了。另一个方面则是因为个人的原因,本人并没有接触过其它性质的系统,所以这也是没有办法的事情。

??首先我们要谈的是对象所扮演的角色的问题。我们说狗是对象扮演的角色,文具也是对象扮演的角色。对象能够模拟出现实世界中各种各样的概念、事物、事件…。我们把扮演特定角色的对象称为对象角色(object role)。现在我们先抛出第一个问题,一个公司中拥有各种各样不同的雇员,包括工程师(engineers)、销售员(salesmen)、主管(directors)、会计(accountants)。我们如何把这些现实世界中的概念(或者说是事物,这取决于你如何看待这些名词,以及你的需求)对应到面向对象中?或者说,我们如何用对象来表示这些概念?类似的问题还有很多,但我们这里就不多说了。值得注意的是,我们这里提到的对象可以理解为类,其实还包括类型,不过在有些语言中类型也被当作类来处理,而类的具体实例,我们会称之为对象实例。

论坛徽章:
0
3 [报告]
发表于 2003-04-23 20:39 |只看该作者

对象建模笔记--角色建模1

第一种处理方法,也是最简单的处理方法――使用单个对象来表示统一的概念。这是下文中所有方法的基础,因此不要小看它。这是什么意思呢?也就是说,不论你要处理的概念是工程师,还是主管,或是会计。我都用


  1. ??public class Employee
复制代码




??来表示它们。那么我应该如何分辨员工是工程师,还是会计呢。好,最简单的方法是使用一个string来表示员工职业的分别:



  1. ??public class Employee{
  2.   ???String employeeType;
  3. ??}
复制代码




??当然,其它的类型信息也是允许的:



  1. ??public class Employee{
  2. ???JobDescription employeeType;
  3. ??}

复制代码

论坛徽章:
0
4 [报告]
发表于 2003-04-23 20:40 |只看该作者

对象建模笔记--角色建模1

这里的JobDescription是另一个用于描述职位信息的类。是不是非常的简单?这只是一个开始。但是要注意,根据我的经验,越是简单的处理方式其实越是实用。对没有太大差别的概念使用统一的对象来表示它们,并使用字段来表示概念的区别,对于大部分的需求都已经足够了。而困挠我们迟迟不敢做出简单的决策的原因往往是对未来变化的恐惧,也许你认为在未来用户可能会修改他们的需求,要求加入新的业务逻辑,处理不同的职位。例如,工资的算法依赖于不同的职位。对未知事物的恐惧往往导致我们进行过分的设计。放轻松些,变化并没有什么了不起的,就像下文提到的,只要接口设计足够的稳定,变化不足畏惧,适应变化正是面向对象所擅长的。因此,记住这一点,永远只是处理当前的需求,不要进行过分设计。这也是敏捷方法所提倡的。

论坛徽章:
0
5 [报告]
发表于 2003-04-23 20:40 |只看该作者

对象建模笔记--角色建模1

好吧,这时候我们需求开始发生变化了。工程师、主管、销售员以及会计这些员工之间出现了非常大的差异。因此我们尝试使用另一种方法:每个单独的概念对应到一个独立的对象上。这样,我们就有了工程师对象、主管对象、销售员对象、会计对象。为简便起见,这里的代码我就不写了,和前一个方法的代码非常的类似,只是代码的量变大了而已。相信很多程序员对这种处理方法都不陌生。这种方法的好处是它能够把不同类型员工给分开,并使得基于不同职业进行业务逻辑处理成为可能,而且不会导致对象间耦合度的上升。

??有利就必有弊。这种方法有两个致命的缺点:第一种是重复代码的产生,我们注意到,不论是工程师,还是主管,他们都属于员工,因此他们都有姓名、性别、年龄、住址等相同的个人信息。由于使用的不同的对象来建模它们,这些属性不得不重复好多遍。就算我们有这个耐心把这些属性一一放到各个对象中,但噩梦仍然没有结束,如果我们那需要增加一些属性,而这些属性恰巧是属于各个对象所共有的。那么我们不得不在各个对象中依次加入这些属性。天哪,总有一天我会疯掉的。什么,继承?你说对了,这就是我们要使用继承机制的一大原因。关于这一点,我们在下文讨论。

??现在我们来看第二个缺点。完整性不足。举一个例子,如果有一位员工,他既是工程师,又是主管。如何处理这种情况?我们只好为这个人建立两个对象,但是我们就没有办法分辨出它们其实表示的是同一个人。如果工程师和主管之间存在依赖关系的话,那可就糟糕了。这种问题在企业应用中时有发生,最经典的例子是供应商和客户同属于一个客户的情况。在http://www.erptao.org上有一篇文章就是专门讨论对这种情况的解决方法的。大家可以参考。

??总的来说,这种方法不算是一种好的处理思路,能不用就不用吧。不过仍然是要具体情况具体分析。

论坛徽章:
0
6 [报告]
发表于 2003-04-23 20:41 |只看该作者

对象建模笔记--角色建模1

当当当当!继承机制进场!我们回到第一个问题结束时留下的疑问,现在我们希望充分利用面向对象的三大特性之一的继承来解决我们遇到的问题。好,我们首先定义一个员工对象(或是其它什么名字),这个对象定义了员工的公用属性以及公用逻辑。然后我们再让工程师对象继承自员工对象,在工程师对象中实现工程师特有的属性和逻辑。好,依法炮制,我们得到了一个父对象-员工,以及三个子对象。如果我们需要处理的层次仍然停留在员工层次上,我们只需要处理员工对象,如果我们需要处理特定的员工,那我们就单独处理员工对象的子对象。如果我们有一位员工既是工程师,有时主管,那我们就也可以创建相应的子对象,如果我们希望增加退休员工的概念,我们也可以再加一个子对象。如果...等等,还如果呢,出问题了。只要是有面向对象编程经验的程序员都知道单根继承和多重继承的区别,而现在的很多语言都是不支持多重继承的。那么,我如果要在不支持多重继承的语言中处理员工既是工程师又是主管的例子,我就需要创建一个工程师/主管的子对象,那么不用多少时间,我们手上的子对象的数量就会急剧上升。这就是典型的子类爆炸的情形。此外,继承的机制一般都是静态的,而不是动态的,也就是说,我不能够在运行时随便改变对象的类型(注意,和多态的概念不同)。

??好吧,是时候谈点抽象的概念了。我记得Martin Fowler在他的分析模式一书中指出,分析问题应该站在概念的层次上,而不是站在实现的层次上。什么叫做概念的层次呢?简单的说就是分析对象该做什么,而不是分析对象怎么做。前者属于分析的阶段,后者属于设计甚至是实现的阶段。在需求工程中有一种称为CRC卡片的玩艺儿,是用来分析类的职责和关系的,其实那种方法就是从概念层次上进行面向对象设计。因此,如果要从概念层次上进行分析,这就要求你从领域专家的角度来看待程序是如何表示现实世界中的概念的。下面的这句话有些拗口,从实现的角度上来说,概念层次对应于合同,合同的实现形式包括接口和基类。简单的说吧,在概念层次上进行分析就是设计出接口(或是基类),而不用关心具体的接口实现(实现推迟到子类再实现)。结合上面的论述,我们也可以这样推断,接口应该是要符合现实世界的观念的。

论坛徽章:
0
7 [报告]
发表于 2003-04-23 20:41 |只看该作者

对象建模笔记--角色建模1

好,现在我们设计出的接口看起来像是这样的:


??interface Person {
???public String name();
???public void name(String newName);
???public Money salary ();
???public void salary (Money newSalary);
???public Money payAmount ();
???public void makeManager ();
??}

??interface Engineer extends Person{
???public void numberOfPatents (int value);
???public int numberOfPatents ();
??}

??interface Salesman extends Person{
???public void numberOfSales (int numberOfSales);
???public int numberOfSales ();
??}

??interface Manager extends Person{
???public void budget (Money value);
???public Money budget ();
??}



??接口看起来不错,但是如何实现它呢,大致的做法有三种:内部标示(Internal Flag)、隐藏委托(Hidden Delegate)、 以及状态对象(State Object)。

??下面的例子只实现了销售员部分的代码,没有使用到多重继承或是动态继承,但却较好的解决了前述的问题:


??public class PersonImpFlag implements Person, Salesman, Engineer,Manager{
??// Implementing Salesman
??public static Salesman newSalesman (String name){
???PersonImpFlag result;
???result = new PersonImpFlag (name);
???result.makeSalesman();
???return result;
??};
??public void makeSalesman () {
???_jobTitle = 1;
??};
??public boolean isSalesman () {
???return _jobTitle == 1;
??};
??public void numberOfSales (int value){
???requireIsSalesman () ;
???_numberOfSales = value;
??};
??public int numberOfSales () {
???requireIsSalesman ();
???return _numberOfSales;
??};
??private void requireIsSalesman () {
???if (! isSalesman()) throw new PreconditionViolation ("Not a Salesman" ;
??};
???private int _numberOfSales;
???private int _jobTitle;
??}

论坛徽章:
0
8 [报告]
发表于 2003-04-23 20:42 |只看该作者

对象建模笔记--角色建模1

这部分的代码使用了内部标示。在这个例子中我们用了一个单独的员工类来实现四个接口。因此我们需要对不同的子对象进行区分。newSalesman、makeSalesman、以及isSalesman方法就是这种区分子对象的方法的具体实现。注意,其中newSalesman是一个静态方法,它起到了一个构造器的作用。此外,这里使用的方法属于显式类型方法(Explicit Type Method)。为了保证销售员的实现代码仅为销售员对象所调用,上面的代码中使用一个私有方法requireIsSalesman来作为守卫,如果有非销售员对象调用了销售员接口的方法,就会引发一个运行时错误。对于显式类型方法来说,一般的名称可以使用isTypename或是beTypename,这种处理方法的好处是接口比较简单,但是如果加入新的类型的时候,基类的接口必须跟着变化。

??从需求上来看,payAmount操作应该是一个多型性操作,由员工基类定义,但由不同的子对象来实现。我们的实现方法是使用了一个case语句。一般来说,面向对象技术是不提倡使用case语句的,但这里使用它却无伤大雅,因为case语句是隐藏在员工类中的。因为我们的处理方式不能够使用到面向对象的多态机制,因此我们只好采用内部case语句的方式来实现这种多型性。


??public Money payAmount (){
???if (isSalesman()) return payAmountSalesman();
???if (isEngineer()) return payAmountEngineer();
???throw new PreconditionViolation ("Invalid Person";
??};
??private Money payAmountSalesman () {
???return _salary.add (Money.dollars(5).multiply(_numberOfSales));
??};
??private Money payAmountEngineer () {
???return _salary.add (Money.dollars(2).multiply(_numberOfPatents));
??};

论坛徽章:
0
9 [报告]
发表于 2003-04-23 20:42 |只看该作者

对象建模笔记--角色建模1

可以看到,内部标示的做法为复杂的分类提供了一种可能的实现,但是它却孕育了一个复杂的员工类,它囊括了所有子对象的数据和行为,并提供了选择机制。如果不对它加以控制,那么它将会变成一个庞然大物。可以看出,这种做法虽然利用到了面向对象的思路,但是实现的方法还是过程式的,但是它有自己的很多好处。所以,只要员工类不至于失控,这种设计思路还是非常有价值的。在应用系统的很多地方,都可以采用该方法或是该方法的变种。应该认识到,面向接口编程和面向对象编程还是有差别的。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP