Martin04年寫的書,15年后的我看了之后,感覺之前看的書都白看了,哈哈!有點誇張,廢話不多說,開始!
1、案例一 團體
假設有個需求,讓你設計兩個類,一個是用戶類,一個是公司類你會怎么設計,大多數人會這么設計,代碼如下:
public class User { public string UserName { get; set; } public string Adress { get; set; } public string Email { get; set; } } public class Company { public string CompanyName { get; set; } public string Adress { get; set; } public string Email { get; set; } }
ok,代碼能很好的完成需求,但是不完美,里面的Adress和Email是重復的概念.so,Martin引入了"團體"一詞,實際上就是對兩個類型進行了抽象,將重復的概念抽象到一個類中,代碼如下:
/// <summary> /// 通過團體來封裝共有的屬性 /// </summary> public class Group { public string Adress { get; set; } public string Email { get; set; } } public class User { public string UserName { get; set; } /// <summary> /// 這里通過值對象來實現共有的屬性,更合適,避免繼承的強依賴,而且在C#中只能單繼承 /// 而且這樣的代碼更容易理解,表示User中存在一個團體類,里面封裝了該團體的所有屬性 /// </summary> public Group Group { get; set; } } public class Company { public string CompanyName { get; set; } public Group Group { get; set; } }
類圖可以這樣表示.
2、案例二 組織層次
假設需要設計一個權限系統,該系統包含用戶、角色、權限、部門,大多數人聽到這個需求會這么編寫代碼,如下:
/// <summary> /// 部門 /// </summary> public class Department { public string DepartmentName { get; set; } public ICollection<Role> Roles { get; set; } } /// <summary> /// 角色 /// </summary> public class Role { public string RoleName { get; set; } public ICollection<User> Users { get; set; } public ICollection<Action> Actions { get; set; } } /// <summary> /// 用戶 /// </summary> public class User { public string UserName { get; set; } } /// <summary> /// 權限 /// </summary> public class Action { public string ActionName { get; set; } }
ok,代碼能很好的完成需求,但是這個時候boss告訴你,我們不需要角色這個概念了,所有部門下的用戶一律平等,對boss來說他們都是員工,那么這個時候我們需要修改模型,修改模型往往是不好的,那么怎么規避這種操作呢?代碼如下:
/// <summary> /// 部門 /// </summary> public class Department: IDepartment { public string DepartmentName { get; set; } public ICollection<IRole> Roles { get; set; } } /// <summary> /// 角色 /// </summary> public class Role: IRole { public string RoleName { get; set; } public ICollection<IUser> Users { get; set; } } /// <summary> /// 用戶 /// </summary> public class User: IUser { public string UserName { get; set; } public ICollection<IAction> Actions { get; set; } } /// <summary> /// 權限 /// </summary> public class Action: IAction { public string ActionName { get; set; } } public interface IDepartment { } public interface IRole { } public interface IUser { } public interface IAction { }
上面的代碼通過接口來約束層級關系,這個時候如果boss提出角色類(Role)不要了,那么我們不必修改模型,直接將關聯的約束修改掉,如下:
/// <summary> /// 部門 /// </summary> public class Department: IDepartment { public string DepartmentName { get; set; } public ICollection<IUser> Roles { get; set; } }
這個時候部門類就不再需要Role類了,但是它還是保存下來了,通過接口約束的轉變,部門類這個時候只需要用戶類就好了,通常情況,修改約束比修改模型結構要容易.
或者你可以像下面這樣:
/// <summary> /// 用戶 /// </summary> public class User: IUser, IRole { public string UserName { get; set; } public ICollection<IAction> Actions { get; set; } }
只需改變對應的實現,拿掉Role類,也能完成需求.總之修改約束跟靈活,雖然上面的例子可能不那么適合.
雖然通過接口約束,能解決上面的問題,但是上面的設計還是有問題,結構層次單一。
還是上面的例子,假設boss說為了滿足用戶的需求,我們需要給部門類增加單獨的權限,同時用戶類又持有對應的權限,那么這個時候權限就同時為部門類和用戶類負責,這個時候當一個用戶登陸進來.我們去數據庫查找到他對應的部門,然后拿到這個部門的所有權限,然后查到當前用戶對應的角色,將該角色下的所有權限拿出來和部門權限做一個並集,去重,得出最終的權限.那么隨着需求的增多,Action需要服務的層次增多,那么到最后代碼將變得難以理解,所以我們必須重構代碼.
重構版本,換一個例子,假設有一個子公司、區域子公司、部門、銷售辦事處、新增的零時服務小組,服務小組必須同時服務於部門、銷售辦事處,模型圖如下:
代碼如下:
/// <summary> /// 子公司 /// </summary> public class ChildCompany:INode { public string ChildCompanyName { get; set; } } /// <summary> /// 區域子公司 /// </summary> public class AreaChildCompany : INode { public string ChildAreaChildCompany { get; set; } } /// <summary> /// 部門 /// </summary> public class Department : INode { public string DepartmentName { get; set; } } /// <summary> /// 銷售辦事處 /// </summary> public class SaleOffice: INode { public string SaleOfficeName { get; set; } } /// <summary> /// 新增的零時服務小組 /// </summary> public class ServiceGroup : INode { } /// <summary> /// 組織節點接口 /// </summary> public interface INode { } /// <summary> /// 組織 /// </summary> public class Organization { /// <summary> /// 父節點 /// </summary> public INode ParentNode; /// <summary> /// 子節點 /// </summary> public INode ChildNode; /// <summary> /// 組織的開始生效時間 /// </summary> public DateTime StartTime { get; set; } = DateTime.Now; /// <summary> /// 組織的生命結束期 /// </summary> public DateTime EndTime { get; set; } } public class Pragram { static void Main(string[] args) { var organizationOne = new Organization() { ParentNode = new Department(), ChildNode = new ServiceGroup(), EndTime=DateTime.Now.AddDays(1) }; var organizationTwo = new Organization() { ParentNode = new SaleOffice(), ChildNode = new ServiceGroup(), EndTime = DateTime.Now.AddDays(1) }; //將兩個組織持久化到數據庫,這樣ServiceGroup就同時服務於Department部門和SaleOffice銷售辦事處 //接着當零食服務小組登錄系統后,我們先去數據庫查到對應的沒有過期的組織對象,一般根據ServiceGroup的Guid去查找 //拿到對應的服務對象后,調用該對象的通知方法,通知他們對應的信息 } }
上面的代碼,將組織抽象成一種類型,通過數據庫持久化,這樣的形式去連接目標對象和服務對象.這樣就能將這種復雜的關聯關系從原先的強類型耦合種解放出來.這樣的設計更加的柔化,和不具有侵入性.所影響的范圍也更小,模型的改動也最小,基本是通過擴展的方式,而不是像上面的代碼那樣去修改模型種的代碼.