Linux中国 Linux中国门户站!
设为主页 设为主页
收藏本站 收藏本站
 
当前位置 :首页 ->Linux技术 ->系统管理 ->正文

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

来源:Linux-cn.com 作者:Webmaster 时间:2007-05-05 点击: [收藏] [投稿]

需求说变就变,我们原先对单个人承担多个角色的担心还是发生了。按照上面的设计,要处理这种实现是很麻烦,很不自然的。因为你必须为一个现实中的人创建两个person对象。这是非常要命的,尤其是角色之间有相互冲突、相互调用的业务逻辑的时候。它可能会对整个设计产生致命的影响。看来,我们又有必要挥举无情重构的大刀来向我们的代码砍去了。

无情的重构是XP非常提倡的最优实践之一。应该承认,重构的思路是非常正确的。但是重构是不是一定需要做到如此彻底的程度呢,这方面我有着不同的看法。对代码的每一次修改都可能造成成本的上升,当一个团队,而不是一个个人在开发软件的时候,你更多需要考虑的是成本、质量、实现的辨正的统一。尤其是你需要对团队中的每一位程序设计师非常的了解,了解他们的能力,了解他们的性格。因此不顾一切的重构在现实中往往会遇到各种各样的阻力,但是如果重构最终达到的效果并不十分明显,或是远低于贯彻它所付出的代价的时候,重构就是不合适的。但是应该说,在适当的情况下的这种实践是非常有必要的,特别是需要研究代码重用的时候,比如我们现在讨论的对模式的改进的问题。这种情况下代码的每一步优化都将极大的提高生产力。

在稍微离题之后,重新回到我们的讨论之中。我们已经知道需求要求我们为一个对象表示多种角色。所以,person和personRole之间的关系演变为一对多的关系,这样就能够解决一个员工可以拥有多种职位的需求。可以想象到,这种做法的一个不好的地方是,用户需要开始了解部分的实现了,因为原先定义的接口已经不合用了。在这个例子中,用户除了需要知道员工自身(person类),还需要知道职位的信息(personrole),这样才能够处理员工和职位之间一对多的关系。在这种情况下,我们对职位信息的处理,还是可以采用上文所讨论的显示的方式,例如isSalesman()方法。但是由于部分实现已经暴露给客户端了,这种方式已经失去了它的存在意义。更好的方法是采用参数化的hasType(String)方法。这种方法的优点是为各职位对象定义了统一的逻辑接口,同时可以动态的增加职位对象,而对接口不会有影响。和上面的所有讨论的方法类似的,有一利就有一弊。这种方法定义的接口不够清晰,至少我们需要了解string参数的定义。此外,编译器不再为我们执行类型检查的工作,这部分的压力移到了程序员的身上。因此,这种动态的参数化技巧的使用是需要非常小心的,只有确保它为你带来的利益能够超出它的成本的时候才能够使用它。这种参数化的方法的命名一般可以采用hasType( typename)或是beType( typenam )的方法。


class Person {
public void addRole(PersonRole value) {
_roles.addElement(value);
};
public PersonRole roleOf(String roleName) {
Enumeration e = _roles.elements();
while (e.hasMoreElements()) {
PersonRole each = (PersonRole) e.nextElement();
if (each.hasType(roleName)) return each;
};
return null;
};
private Vector _roles = new Vector();
…
}
public class PersonRole{
public boolean hasType (String value) {
return false;
};
…
public class Salesman extends PersonRole{
public boolean hasType (String value) {
if (value.equalsIgnoreCase("Salesman")) return true;
if (value.equalsIgnoreCase("JobRole")) return true;
else return super.hasType(value);
};
public void numberOfSales (int value){
_numberOfSales = value;
};
public int numberOfSales () {
return _numberOfSales;
};
private int _numberOfSales = 0;
}
// To set a salesman’s sales we do the following
Person subject;
Salesman subjectSalesman = (Salesman) subject.roleOf("Salesman");
subjectSalesman.numberOfSales(50);

我们来分析这段代码。和以往的代码不同的是,接口已经消失了。因为在原先的需求中,我们是不关心员工和职位之间的关系的,换言之,我们认为员工和职位是一体的,只是不同的职位对于我们来说有着不同的处理方法。但是根据现在的需求,我们需要处理员工和职位两者的关系,对于我们来说,员工和职位已经是两个并行的类树了。所以,我们看到客户端的调用代码中是先声明员工,再调用员工的roleOf方法来调用员工所从事的某种职位。因此,客户端需要知道职位的信息,才能够正确的获得职位对象。注意hasType方法中的方法检查,它是根据多态的基本原则来实现的,对于Salesman类型和Salesman的父类都返回真,其实,对父类类型的判断也可以放到父类的方法中。

上面的例子看不出具体的应用,我们可以考虑一种情况,我们需要对公司中的所有销售人员(可能有人身兼多职)进行一项业务逻辑处理。我们的代码可以写成这样:


Enumeration e = persons.elements();
while (e.hasMoreElements()) {
Person each = (Person) e.nextElement();
if (each.hasRole("Salesman")) {
Salesman sm = (Salesman) each.roleOf("Salesman");

//其它的处理

hasRole方法是一个新增加的函数,其实现的机制类似于roleOf方法。

好吧,使用现在的代码,我们已经能够处理员工和职位之间一对多的情况了。那么,我们有没有想过另一种的情况呢,员工和职位之间是多对一的情况。比如最简单的例子是114、1000的人工台。对于用户来说,只存在接线员这个角色,而每次接线员这个角色对应的人是不同的。因此,我们的视角就转移了,转移到了角色上,类似的,我们同样可以根据需求来改进我们的设计,但是接口一定已经不同了,因为需求的变化非常之大。因此,我们再一次深入到设计的背后,思考为什么我们采用A设计,而不是B设计。这些都是有根据的,决不是因为A设计比较Cool之类的原因。最大的设计选择的影响因素就是需求,包括功能性需求和非功能性需求(约束条件)。Martin Fowler感叹到,设计其实是一种权衡策略,一针见血的指出了设计的本质。结合到我们的例子中,为什么我们采用的设计是在员工类中增加一个角色列表,而不是相反的情况呢。这是因为需求要求我们这样做,职位是员工的某一类属性,虽然这类的属性比较特殊,但员工对象仍然处于核心的地位。假设我们的需求发生了变化,对员工的信息不再关心,而转而关注职位信息,那样我们的设计就会截然不同。如果在处理信息系统中,往往职位和员工都占有很重要的位置,大量的信息和逻辑都是基于这组概念的。这样我们可以就需要在员工和职位之间设计双向关联,来表现他们之间的多对多的关系。最简单的例子是,一般的信息处理是从员工角度出发的,但是人力资源系统的职位处理就需要从职位角度出发。还是那句话,关键还是取决于我们的视角,我们的需求。

 如果您对本文有任何疑问或者建议,请到讨论区发表您的意见: >> 论坛入口 <<



上一篇:对象建模笔记--角色建模2   下一篇:我的E-Mail服务器为什么变慢了?

文章评论】 【收藏本文】 【推荐好友】 【打印本文】 【我要投稿】 【论坛讨论
更多相关文章
Power by linux-cn.com 粤ICP备05006655号