组织结构的领域建模 (4): 机构类型的三种建模方式比较

从上面的几篇文章分析,我们可以得知,机构是有各种类型的。对机构类型进行领域建模,可以有三种方式:

  1. 没有“机构”这个共同基类的概念,为公司、部门机构类型等分别独立建模;
  2. 只建立机构这个具体类型,通过机构类的一个名为类型(category)的属性区分当前机构的类型是公司还是部门;
  3. 建立一个类型层次结构,公司部门都是机构这个抽象基类的具体子类。

下文分别将这三种建模方式简称为“无父类”“无子类”“类型层级”方式,并分别比较三种方式的优缺点。

“无父类”方式

“无子类”方式

“类型层级”方式

有人也许会问:我们为什么要区分独立机构和机构单元,为何要定义公司、部门等各种机构子类,而不是统一建立一个机构类,而通过一个鉴别属性(例如orgType)表明这是一个公司还是部门?那样不是更加简单明了?

对这个问题的回答涉及到面向对象和面向过程的比较。

通过定义机构类型层级,我们可以实现下面的行为:

  • 不同的机构子类型可以拥有不同的属性集。

    例如公司可以包含工商执照号码,组织机构代码等等属性,而部门没有这些属性。

  • 保证机构层级的合法性。例如公司下面可以设立部门和下级公司,部门下面只可以设立部门。采用上面的类型层级,我们可以在机构基类上定义createChild()方法,在当前机构下创建一个下属机构:

1
2
3
4
5
@Entity
@Inheritance
public abstract class Organization extends AbstractEntity {
public abstract void createChild(Organization org);
}

在公司类上这样实现它:

1
2
3
4
5
6
7
8
9
@Entity
@DiscriminatorValue("Company")
public class Company extends Organization {
@Override
public void createChild(Organization org) {
org.setParent(this);
getRepository().save(org);
}
}

在部门类上这样实现它:

1
2
3
4
5
6
7
8
9
10
11
12
@Entity
@DiscriminatorValue("Dept")
public class Department extends Organization {
@Override
public void createChild(Organization org) {
if (org instanceof Company) {
throw new OrganizationCreationException("Cannot create Company under Department");
}
org.setParent(this);
getRepository().save(org);
}
}

当试图在部门下创建公司时,部门类将抛出OrganizationCreationException异常。

  • 保证只有独立机构才能够聘用员工。

我们可以在独立机构下定义employ()方法,由当前机构聘用员工:

1
2
3
4
5
6
7
8
@Entity
public abstract class OrganizationEntity extends Organization {
public Employment employ(Employee employee, Date employmentDate) {
Employment employment = new Employment(this, employee, employmentDate);
getRepository().save(employment);
return employment;
}
}

公司、厅、局是独立机构的子类,因此可以继承employ方法,聘用员工;部门、处、室不是独立机构的子类而是机构单元的子类,因此无法聘用员工。

你可以争辩说,用一个全局的机构Organization类表示所有的机构,通过鉴别属性orgType区分机构类型也可以做到上述各点。但是实现起来复杂、笨拙,并且脆弱。

  • 机构类的属性集必须是所有可能的机构类型属性集的超集,而且所有的机构类型都拥有所有这些属性。这一点使得该类非常复杂,而且部门类也拥有工商执照号码是非常奇怪甚至错误的事情。
  • 机构类的行为集必须是所有可能的机构类型行为集的超集。因此,部门也拥有聘用员工、与员工签订劳务合同的资格(这一点不符合国家政策),至少在接口上看是如此。
  • 为了保证机构层级和机构层级的合理性,必须在机构类内部写出大量过程式代码,充满了if…else if…或switch…case…这样的意大利面条式复杂、冗长的代码。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity
public abstract class Organization extends AbstractEntity {
public Employment employ(Employee employee, Date employmentDate) {
if (orgType == OrgType.Department) {
throw new RuntimeException("....")
}
if (orgType == OrgType.Office) {
throw new RuntimeException("....")
}
...
Employment employment = new Employment(this, employee, employmentDate);
getRepository().save(employment);
return employment;
}
}
  • 在扩展时违反开放-封闭原则。每当添加新的机构类型时,必须对机构类做出大量的改动,以保证其行为的正确性。我们几乎需要在机构类的每一个方法上添加一段代码块,而不像面向对象的方式那样,只需要在新的子类上定义新的行为,而原有的代码丝毫不需要改动。

面向对象的方式采用分散决策的策略,将每个决策点分散到每个子类去决定;而面向过程的方式采用集中决策的策略,在基类(或过程类)上实现一切,维护一切,形成一个难于理解、难于维护、难于测试、正确性无法验证、可靠性无法保证的巨型上帝类。

充分利用面向对象的继承、封装、多态等方式,可以构造一个良好的系统。