本文為 Dennis Gao 原創技術文章,發表于博客園博客,未經作者本人允許禁止任何形式的轉載。
現在,我們需要設計一個項目管理系統,目前我們收集到了如下這些需求:
- REQ1:一個項目內有多名項目成員
- REQ2:一名項目成員只能被指派給一個項目
- REQ3:一個項目內僅有一名項目成員被指派為項目經理負責管理項目
- REQ4:所有項目成員均是公司員工
- REQ5:公司員工的薪水由基本工資和項目獎金組合而成
- REQ6:項目經理的項目獎金由項目的成敗決定
- REQ7:項目中包含項目計劃
- REQ8:一個項目計劃由多個項目計劃項組成
根據上面的需求描述,我們首先識別出若干個概念名詞:
- 項目(Project)
- 項目成員(Project Member)
- 項目經理(Project Manager)
- 公司員工(Employee)
- 薪水(Salary)
- 基本工資(Base Salary)
- 項目獎金(Project Bonus)
- 項目計劃(Schedule)
- 項目計劃項(Schedule Item)
根據需求 “REQ4:所有項目成員均是公司員工”,我們可以得到 Employee 與 ProjectMember 的關系。
類 ProjectMember 實現了抽象類 Employee。Employee 類中包含計算薪水(Salary)操作,并負責封裝需求 “REQ5:公司員工的薪水由基本工資和項目獎金組合而成”。ProjectMember 類覆寫父類的薪水計算方法。
1 public abstract class Employee 2 { 3 public Employee(int id, string name) 4 { 5 ID = id; 6 Name = name; 7 } 8 9 public int ID { get; private set; } 10 public string Name { get; private set; } 11 12 public double CalculateSalary() 13 { 14 return GetBaseSalary() + GetProjectBonus(); 15 } 16 17 protected abstract double GetBaseSalary(); 18 protected abstract double GetProjectBonus(); 19 } 20 21 public class ProjectMember : Employee 22 { 23 public ProjectMember(int id, string name) 24 : base(id, name) 25 { 26 } 27 28 public Project AssignedProject { get; private set; } 29 30 public void AssignProject(Project project) 31 { 32 AssignedProject = project; 33 } 34 35 protected override double GetBaseSalary() { return 1000; } 36 protected override double GetProjectBonus() { return 200; } 37 }
根據需求 “REQ3:一個項目內僅有一名項目成員被指派為項目經理負責管理項目”,可以推斷出 ProjectManager 與 ProjectMember 的關系。
ProjectManager 繼承自 ProjectMember。ProjectMember 類覆寫父類的薪水計算方法,以實現需求 “REQ6:項目經理的項目獎金由項目的成敗決定”。
1 public class ProjectManager : ProjectMember 2 { 3 public ProjectManager(int id, string name) 4 : base(id, name) 5 { 6 } 7 8 protected override double GetBaseSalary() { return 2000; } 9 10 protected override double GetProjectBonus() 11 { 12 return AssignedProject.IsSuccess ? 800 : 0; 13 } 14 }
由下面三個需求可以識別出 Project 與 ProjectMember/ProjectManager 之間的關系。
REQ1:一個項目內有多名項目成員
REQ2:一名項目成員只能被指派給一個項目
REQ3:一個項目內僅有一名項目成員被指派為項目經理負責管理項目
Project 聚合(Aggregation)了 ProjectMember,ProjectMember 當不在該項目中時仍然可以存在,比如轉去做其他項目。
Project 關聯(Association)了 ProjectManager,ProjectManager 當不在該項目時,需要轉換為 ProjectMember。
ProjectManager 的薪水將由所負責的項目的成敗決定,會調用 Project 的狀態以計算薪水。
1 public class Project 2 { 3 private ProjectManager _manager; 4 private List<ProjectMember> _members = new List<ProjectMember>(); 5 private Schedule _schedule = new Schedule(); 6 7 public Project(string name, ProjectManager manager) 8 { 9 Name = name; 10 _manager = manager; 11 } 12 13 public string Name { get; private set; } 14 public ProjectManager Manager { get { return _manager; } } 15 public ReadOnlyCollection<ProjectMember> Members { get { return _members.AsReadOnly(); } } 16 public Schedule Schedule { get { return _schedule; } } 17 public bool IsSuccess { get { return (new Random().Next(1, 100) % 2) > 0; } } 18 19 public void AssignMembers(IEnumerable<ProjectMember> members) 20 { 21 _members.AddRange(members); 22 _members.ForEach(m => m.AssignProject(this)); 23 } 24 25 public void AddScheduleItem(ScheduleItem item) 26 { 27 _schedule.Add(item); 28 } 29 }
根據需求 “REQ7:項目中包含項目計劃” 可得出 Project 與 Schedule 的關系。
根據需求 “REQ8:一個項目計劃由多個項目計劃項組成” 可得出 Schedule 與 ScheduleItem 的關系。
Project 聚合(Aggregation)了 Schedule。Schedule 由多個 ScheduleItem 組成(Composition)。
1 public class Schedule : List<ScheduleItem> 2 { 3 } 4 5 public class ScheduleItem 6 { 7 public string Description { get; set; } 8 public DateTime BeginTime { get; set; } 9 public DateTime EndTime { get; set; } 10 }
由此,我們得到了滿足全部需求的類圖:
現在,我們可通過以上類的定義來組織業務邏輯。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 ProjectManager manager = new ProjectManager(1, @"Dennis Gao"); 6 ProjectMember member2 = new ProjectMember(2, @"Super Man"); 7 ProjectMember member3 = new ProjectMember(3, @"Iron Man"); 8 ProjectMember member4 = new ProjectMember(3, @"Spider Man"); 9 10 var projectMembers = new List<ProjectMember>() { manager, member2, member3, member4 }; 11 12 Project project = new Project("EarnMoney", manager); 13 project.AssignMembers(projectMembers); 14 15 ScheduleItem item1 = new ScheduleItem() 16 { 17 Description = "Team Building", 18 BeginTime = DateTime.Now.AddDays(5), 19 EndTime = DateTime.Now.AddDays(6), 20 }; 21 project.AddScheduleItem(item1); 22 23 Console.WriteLine("Salary List of Project [{0}] Members:", project.Name); 24 foreach (var member in project.Members) 25 { 26 Console.WriteLine( 27 "\tProject Member [{0}] has TotalSalary [{1}].", 28 member.Name, member.CalculateSalary()); 29 } 30 31 Console.WriteLine(); 32 Console.WriteLine("[{0}] members will have a [{1}] on [{2}].", 33 project.Name, project.Schedule.First().Description, 34 project.Schedule.First().BeginTime); 35 36 Console.ReadKey(); 37 } 38 }
由于在業務邏輯中,ProjectManager 的項目獎金由項目的成敗來決定,但是項目的成敗又多少帶了點運氣。
1 public bool IsSuccess { get { return (new Random().Next(1, 100) % 2) > 0; } }
1 protected override double GetProjectBonus() 2 { 3 return AssignedProject.IsSuccess ? 800 : 0; 4 }
所以,我們可能會得到兩種輸出結果,成功的項目和失敗的項目。
失敗的項目沒有項目獎金:
成功的項目拿到了項目獎金:
我們給出 UML 中的相關定義:
我們可以從不同的角度來理解和區分這三種關系:
所以,總結來說,聚合(Aggregation)是一種特殊的關聯(Association),合成(Composition)是一種特殊的聚合(Aggregation)。
Association->Aggregation->Composition
參考資料
- Introduction to Object Oriented Programming Concepts (OOP) and More
- Understanding Association, Aggregation, and Composition
- Aggregation vs Composition
- What is the difference between dependency and association?
- UML類圖基本元素符號
- UML中依賴(Dependency)和關聯(Association)之間的區別
- UML用例圖
- UML序列圖
- UML類圖
完整代碼

using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace UML { class Program { static void Main(string[] args) { ProjectManager manager = new ProjectManager(1, @"Dennis Gao"); ProjectMember member2 = new ProjectMember(2, @"Super Man"); ProjectMember member3 = new ProjectMember(3, @"Iron Man"); ProjectMember member4 = new ProjectMember(3, @"Spider Man"); var projectMembers = new List<ProjectMember>() { manager, member2, member3, member4 }; Project project = new Project("EarnMoney", manager); project.AssignMembers(projectMembers); ScheduleItem item1 = new ScheduleItem() { Description = "Team Building", BeginTime = DateTime.Now.AddDays(5), EndTime = DateTime.Now.AddDays(6), }; project.AddScheduleItem(item1); Console.WriteLine("Salary List of Project [{0}] Members:", project.Name); foreach (var member in project.Members) { Console.WriteLine( "\tProject Member [{0}] has TotalSalary [{1}].", member.Name, member.CalculateSalary()); } Console.WriteLine(); Console.WriteLine("[{0}] members will have a [{1}] on [{2}].", project.Name, project.Schedule.First().Description, project.Schedule.First().BeginTime); Console.ReadKey(); } } public abstract class Employee { public Employee(int id, string name) { ID = id; Name = name; } public int ID { get; private set; } public string Name { get; private set; } public double CalculateSalary() { return GetBaseSalary() + GetProjectBonus(); } protected abstract double GetBaseSalary(); protected abstract double GetProjectBonus(); } public class ProjectMember : Employee { public ProjectMember(int id, string name) : base(id, name) { } public Project AssignedProject { get; private set; } public void AssignProject(Project project) { AssignedProject = project; } protected override double GetBaseSalary() { return 1000; } protected override double GetProjectBonus() { return 200; } } public class ProjectManager : ProjectMember { public ProjectManager(int id, string name) : base(id, name) { } protected override double GetBaseSalary() { return 2000; } protected override double GetProjectBonus() { return AssignedProject.IsSuccess ? 800 : 0; } } public class Schedule : List<ScheduleItem> { } public class ScheduleItem { public string Description { get; set; } public DateTime BeginTime { get; set; } public DateTime EndTime { get; set; } } public class Project { private ProjectManager _manager; private List<ProjectMember> _members = new List<ProjectMember>(); private Schedule _schedule = new Schedule(); public Project(string name, ProjectManager manager) { Name = name; _manager = manager; } public string Name { get; private set; } public ProjectManager Manager { get { return _manager; } } public ReadOnlyCollection<ProjectMember> Members { get { return _members.AsReadOnly(); } } public Schedule Schedule { get { return _schedule; } } public bool IsSuccess { get { return (new Random().Next(1, 100) % 2) > 0; } } public void AssignMembers(IEnumerable<ProjectMember> members) { _members.AddRange(members); _members.ForEach(m => m.AssignProject(this)); } public void AddScheduleItem(ScheduleItem item) { _schedule.Add(item); } } }
本文為 Dennis Gao 原創技術文章,發表于博客園博客,未經作者本人允許禁止任何形式的轉載。
文章列表