文章目錄
- 一 限定初識
- 二 限定識別
- 三 限定實現
一 限定初識
-
一個 員工 可以擁有多份 工作經驗,而各個 工作經驗 的 時間段 不能相互重疊。可以得出一個推論:對于一個 員工 而言,每個 時間段 只能有一條 工作經驗。
-
UML中第二種表述方式,如下圖:
-
標有“: 時間段”的方框,叫做“限定符”(qualifier)。
-
對于一個員工,任何一個時間段,要么沒有工作經驗,要么有一條工作經驗,但不能有多條工作經驗。換句話說,一個員工可以有多條工作經驗,但限定在一個時間段的話,那么最多就只能有一條工作經驗。
-
限定機制起到兩個作用:第一,表達了更豐富的語義,把原來用注解說明的約束變成了更嚴格的符號;第二,簡化了關聯關系的多重性,把原來的一對多,在形式上,變成了一對一。
二 限定識別
- 在技能實體上,原來有一個“同一技能不能錄入兩次”的約束。現在由于增加對技能類別的限定,已經表達相同的意思。
- 項目和項目成員之間的關聯,是否應該使用限定呢?
- 雖然項目成員里面也有時間段屬性,但是項目和項目成員之間的關聯并沒有被時間段所限定。因為即使在同一個時間段,一個項目還是可以有多個成員。
- 盡管項目經理和項目成員中都有時間段,但項目經理的關聯被時間段所限定了,而項目成員則沒有。現在的表示方法清楚地體現出了兩者之間的這種區別,而之前只能通過注釋中的文字來表達。
- 項目成員“不必”用時間段來限定,而不是“不能”限定。這是因為,理論上其實也可以在項目一端加一個時間段限定。最終效果如下:
三 限定實現
- 以工作經驗(work_experience)表和技能(skill)進行“限定”在數據庫里的實現。通過添加唯一索引,在工作經驗表上體現出時間段的限定,并且在技能表上體現出技能類別的限定。
// domain.orgmng.emp;
//imports ...
public class Emp extends AggregateRoot {// other fields ...// protected List<Skill> skills = new ArrayList<>();protected Map<Long, Skill> skills = new HashMap<>();// protected List<WorkExperience> experiences;protected Map<Period, WorkExperience> experiences = new HashMap<>();// other methods...public Collection<Skill> getSkills() {// return Collections.unmodifiableList(skills);return Collections.unmodifiableCollection(skills.values());}public Optional<Skill> getSkill(Long skillTypeId) {// return skills.stream()// .filter(s -> s.getSkillTypeId().equals(skillTypeId))// .findAny();return Optional.ofNullable(skills.get(skillTypeId));}public void addSkill(Long skillTypeId, SkillLevel level, int duration, Long userId) {skillTypeShouldNotDuplicated(skillTypeId);Skill newSkill = new Skill(tenantId, skillTypeId, userId).setLevel(level).setDuration(duration);//skills.add(newSkill);skills.put(skillTypeId, newSkill);}private void skillTypeShouldNotDuplicated(Long newSkillTypeId) {// if (skills.stream().anyMatch(// s -> s.getSkillTypeId().equals(newSkillTypeId))) {if (skills.get(newSkillTypeId) != null) {throw new BusinessException("同一技能不能錄入兩次!");}}// public List<WorkExperience> getExperiences() {// return Collections.unmodifiableList(experiences);// }public Collection<WorkExperience> getExperiences() {return Collections.unmodifiableCollection(experiences.values());}public void addExperience(Period period, String company, Long userId) {durationShouldNotOverlap(period);WorkExperience newExperience = new WorkExperience(tenantId, period, LocalDateTime.now(), userId).setCompany(company);//experiences.add(newExperience);experiences.put(period, newExperience);}private void durationShouldNotOverlap(Period newPeriod) {// if (experiences.stream().anyMatch(// e -> e.getPeriod().overlap(newPeriod))) {if (experiences.values().stream().anyMatch(e -> e.getPeriod().overlap(newPeriod))) {throw new BusinessException("工作經驗的時間段不能重疊!");}}// other methods...
}
- 把Emp類的skills屬性的類型改成Map。Map的Key實際就是 技能類別ID,就保證了對 技能類別 所限定的唯一性。
- getSkills() 方法,我們取了Map的 values(),并把方法的返回值類型改成了Collection。
- getSkill(Long skillTypeId) 方法,我們直接從 Map 里取值,而不是像以前那樣通過遍歷 List 來搜索。
- 當寫程序的時候,如果發現從 List 里搜索比較麻煩,可能就已經想到改成 Map 了。我們就可以“反推”出模型中很可能應該使用限定。這其實是在編寫代碼的過程中,以優化代碼結構為啟發,反過來促使模型演進的一個例子。