DDD 代码三

DDD 学习笔记 | 极客时间 | 手把手教你落地 DDD

🏆 原文:12|代码实现(下):怎样更加“面向对象”?

使用表格与业务方确认需求简单明了

属性创建修改备注
组织 ID-系统自动生成,不可修改
租户操作不能跨租户,因此不可更改,但需要作为后续操作的输入参数

更新、取消操作可以放在 Handler 领域服务中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package chapter12.unjuanable.domain.orgmng.org;
// imports...

@Component
public class OrgHandler {
    private final CommonValidator commonValidator;
    private final OrgNameValidator nameValidator;
    private final OrgLeaderValidator leaderValidator;
    private CancelOrgValidator cancelValidator;

    public OrgHandler(CommonValidator commonValidator
            , OrgNameValidator nameValidator
            , OrgLeaderValidator leaderValidator
            , CancelOrgValidator cancelValidator) {
        // 为依赖注入赋值...
    }

    public void updateBasic(Org org, String newName
                , Long newLeader, Long userId) {
        updateName(org, newName);
        updateLeader(org, newLeader);
        updateAuditInfo(org, userId);
    }

    public void cancel(Org org, Long userId) {
        cancelValidator.cancelledOrgShouldNotHasEmp(org.getTenant()
                        , org.getId());
        cancelValidator.OnlyEffectiveOrgCanBeCancelled(org);
        org.setStatus(OrgStatus.CANCELLED);
        updateAuditInfo(org, userId);
    }

    private void updateLeader(Org org, Long newLeader) {
        if (newLeader != null && !newLeader.equals(org.getLeader())) {
            leaderValidator.leaderShouldBeEffective(org.getTenant()
                                , newLeader);
            org.setLeader(newLeader);
        }
    }

    private void updateName(Org org, String newName) {
        if (newName != null && !newName.equals(org.getName())) {
            nameValidator.orgNameShouldNotEmpty(newName);
            nameValidator.nameShouldNotDuplicatedInSameSuperior(
                  org.getTenant(), org.getSuperior(), newName);
            org.setName(newName);
        }
    }

    private void updateAuditInfo(Org org, Long userId) {
        // 设置最后修改人和时间
    }
}

提高应用 API 的封装性

  • 最小接口原则,目的是减小模块间的耦合,提高程序的可维护性
  • 要为每个功能编写单独的参数 DTO,只包含必要的参数

添加组织的 JSON 参数修改前

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "createdAt": "2022-10-05T06:49:39.659Z",
  "createdBy": 0,
  "id": 0,
  "lastUpdatedAt": "2022-10-05T06:49:39.659Z",
  "lastUpdatedBy": 0,
  "leader": 0,
  "name": "string",
  "orgType": "string",
  "status": "string",
  "superior": 0,
  "tenant": 0
}

添加组织的 JSON 参数修改后

1
2
3
4
5
6
7
{
  "leader": 0,
  "name": "string",
  "orgType": "string",
  "superior": 0,
  "tenant": 0
}

通过“表意接口”提高封装性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package chapter12.unjuanable.domain.orgmng.org;
// imports ...

@Component
public class OrgHandler {
    //...
    
    public void cancel(Org org, Long userId) {
        cancelValidator.OrgToBeCancelledShouldNotHasEmp(
                                    org.getTenantId(), org.getId());
        cancelValidator.OrgToBeCancelledShouldBeEffective(org);
        org.setStatus(OrgStatus.CANCELLED); // 直接为 Status 赋值
        updateAuditInfo(org, userId);
    }
    
    // ...
}

其中,org.setStatus(OrgStatus.CANCELLED) 直接将组织的状态设置成了“已撤销”。从面向对象的角度来看,更好的做法是 Org 类提供一个 cancel() 方法,像下面这样:

1
2
3
4
5
6
7
8
9
package chapter12.unjuanable.domain.orgmng.org;

public class Org {
    
    //Org 自己管理自己的状态
    public void cancel() { 
        this.status = OrgStatus.CANCELLED;
    }
}

这样,Org 类就可以自己管理自己的状态,OrgHandler 就不必了解 Org 内部状态的转换细节,只需告诉 Org 需要撤销就可以了,像下面这样。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package chapter12.unjuanable.domain.orgmng.org;

@Component
public class OrgHandler {
    
    public void cancel(Org org, Long userId) {
        cancelValidator.OrgToBeCancelledShouldNotHasEmp(
                        org.getTenant(), org.getId());
        cancelValidator.OrgToBeCancelledShouldBeEffective(org);
        org.cancel();   // 只需告诉 Org 要进行撤销,但不必了解 Org 内部细节
        updateAuditInfo(org, userId);
    }
}

类似的,我们看一下“只有有效的组织才能被撤销”这条规则的实现代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package chapter12.unjuanable.domain.orgmng.org.validator;

@Component
public class CancelOrgValidator {
    // 只有有效的组织才能被撤销
    public void OnlyEffectiveOrgCanBeCancelled(Org org) {
        //直接访问了状态属性
        if (!org.getStatus().equals(OrgStatus.EFFECTIVE)) { 
            throw new BusinessException("该组织不是有效状态,不能撤销!");
        }
    }
}

我们看到 CancelOrgValidator 直接访问了 Org 的状态,判断是否为有效组织。这实际上又是重构中的一种坏味道,叫做特性依恋(Feature Envy)。Status 这个特性是属于 Org 类的,而 CancelOrgValidator 要通过访问这个特性来实现自己的逻辑,所以 CancelOrgValidator “依恋”了 Org 的特性。这是对象封装性被破坏的征兆。

解决方法是将这段判断逻辑移动到 Org 类内部,重构后的 Org 类是这样的。

1
2
3
4
5
6
7
package chapter12.unjuanable.domain.orgmng.org;

public class Org {
    public boolean isEffective() {
        return status.equals(OrgStatus.EFFECTIVE);
    }
}

这样,CancelOrgValidator 就可以不依赖 Org 的内部状态了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package chapter12.unjuanable.domain.orgmng.org.validator;

@Component
public class CancelOrgValidator {

    // 只有有效的组织才能被撤销
    public void OnlyEffectiveOrgCanBeCancelled(Org org) {
        //不再依赖 Org 的内部状态
        if (!org.isEffective()) { 
            throw new BusinessException("该组织不是有效状态,不能撤销!");
        }
    }
}

Org.cancel() 和 Org.isEffective() 既提高了对象的封装性,也表达了这个功能的业务含义,因此也是表意接口模式的一种应用。

comments powered by Disqus