閱讀目錄:數據庫
說到類型碼,咱們都會頗有印象,在某個Entity內部多多少少會出現一兩個類型碼來表示當前Entity在某個抽象角度屬於哪種層面,好比在EmployeeEntity中,基本上會有一個表示性別的Sex的屬性,同時Sex屬性的最終保存是在某個sex字段中的,它就是很典型的類型碼元素;Sex類型碼屬性用來表達了在用性別這一個抽象角度對實體進行分類時,那麼實體會存在着兩種被概括的層面(男、女);後端
在這個Sex類型碼屬性被使用到的任何一個邏輯的地方都會有可能由於它的值不一樣而進行不一樣的邏輯分支,就比如咱們在EmployeeCollectionEntity對象中定義一個方法,用來返回指定類型的全部EmployeeEntity,咱們簡單假設在EmployeeeCollectionEntity的內部確定有一塊邏輯是用來根據當前方法的參數進行判斷,而後調用不一樣的方法返回當前集合中的全部執行參數的EmployeeEntity;ide
上述只是一個簡單的使用場景,可是足以能簡單說明類型碼的意義和使用場景,下面咱們將針對上面提到的這一個簡單的例子進行三種類型碼的使用分析和如何重構設計;在類型碼不被任何邏輯使用只是提供給外部一個簡單的標識時,咱們如何處理;在類型碼會直接影響實體內部行爲邏輯的狀況下,咱們如何處理;在類型碼會影響實體內部邏輯的時候,可是咱們又沒法將其直接提取抽象出來時,咱們如何處理;單元測試
咱們帶着這個三個簡單的問題進行下面的具體分析;測試
在不影響對象內部邏輯的狀況下,問題很好處理;既然不影響對象內部邏輯,那麼它的表現形式起碼對於實體內部邏輯來講可有可無;這個時候咱們對它的設計能夠遵循一個原則就是OO,若是咱們使用的是一個簡單的數字來表示類型碼的狀態,那麼咱們就能夠經過三個方式對它進行設計或者重構;this
這裏有一個小小問題的就是,若是咱們正在進行一項局部DomainModel內部的重構時,咱們的工做量會很大並且須要很好的單元測試來支撐;可是若是咱們目前正在設計一個Entity問題就很簡單;spa
下面咱們用上面1】節提到的簡單場景做爲本節演示示例的領域模型;翻譯
EmployeeEntity 代碼:設計
1 public class EmployeeEntity 2 { 3 private int sex; 4 5 public int Sex 6 { 7 get { return sex; } 8 set { sex = value; } 9 } 10 }
EmployeeCollectionEntity代碼:code
1 public class EmployeeCollectionEntity : List<EmployeeEntity> 2 { 3 public IEnumerable<EmployeeEntity> GetEntityBySex(int sex) 4 { 5 return from item in this where item.Sex== sex select item; 6 } 7 }
測試代碼,爲了方便起見,我就沒有特意建立UnitTests項目,而是簡單的使用控制檯程序模擬:
1 EmployeeCollectionEntity empCollection = new EmployeeCollectionEntity() 2 { 3 new EmployeeEntity() { Sex= 1 }, 4 new EmployeeEntity() { Sex = 2 }, 5 new EmployeeEntity() { Sex = 2 } 6 }; 7 8 var resultList = empCollection.GetEntityBySex(2); 9 if (resultList.Count() == 2 && resultList.ToList()[0].Sex== 2 && resultList.ToList()[1].Sex==2) 10 Console.WriteLine("is ok"); 11 12 Console.ReadLine();
上述代碼很簡單,一個Employee用來表示員工實體,EmployeeCollectionEntity表示員工實體集,用來封裝一組包含業務邏輯的Empoyee集合;目前在EmployeeCollectionEntity中有一個方法GetEntityBySex(int sex),用來根據性別類型碼來獲取集合內部中知足條件的全部EmpoyeeEntity,在單元測試中的代碼,咱們使用1表示女性,2表示男性,單元測試經過測試代碼正確的查詢出兩組男性EmployeeEntity實體;
下面咱們將逐步使用三種方式對這種類型的業務場景進行從新設計也能夠稱爲重構;
第一:使用枚舉類型替換類型碼數字;
EmployeeEntity代碼:
1 public class EmployeeEntity 2 { 3 public enum EmployeeSex 4 { 5 Male, 6 Female 7 } 8 9 private EmployeeSex sex; 10 11 public EmployeeSex Sex 12 { 13 get { return sex; } 14 set { sex= value; } 15 } 16 }
EmployeeCollectionEntity代碼:
1 public class EmployeeCollectionEntity : List<EmployeeEntity> 2 { 3 public IEnumerable<EmployeeEntity> GetEntityBySex(EmployeeEntity.EmployeeSex sex) 4 { 5 return from item in this where item.Sex== sexselect item; 6 } 7 }
測試代碼:
1 EmployeeCollectionEntity empCollection = new EmployeeCollectionEntity() 2 { 3 new EmployeeEntity() { Sex= EmployeeEntity.EmployeeSex.Female }, 4 new EmployeeEntity() { Sex= EmployeeEntity.EmployeeSex.Male }, 5 new EmployeeEntity() { Sex= EmployeeEntity.EmployeeSex.Male } 6 }; 7 8 var resultList = empCollection.GetEntityBySex(EmployeeEntity.EmployeeSex.Male); 9 if (resultList.Count() == 2 && resultList.ToList()[0].Sex== EmployeeEntity.EmployeeSex.Male && 10 resultList.ToList()[1].Sex== EmployeeEntity.EmployeeSex.Male) 11 Console.WriteLine("is ok"); 12 13 Console.ReadLine();
經過使用枚舉咱們能很好的使用OOD的好處,這樣代碼中不會處處充斥這亂七八糟的魔幻數字;
第二:使用常量來代替類型碼;
其實使用常量來代替類型碼時,比較常見的業務場景是在和遠程交互的時候,由於在咱們將Entity翻譯成某種傳輸對象的時候須要將它的屬性使用字符串的形式表達;好比這裏的EmployeeEntity,假設咱們須要將某一個EmployeeEntity發送到某個消息隊列,而後消息隊列的後端程序須要將它直接插入到數據庫中,這個時候,咱們的DomainModel在消息隊列的後端程序中是不存在的,也就是說並無和數據庫映射過,這裏的屬性類型碼將是和數據庫等價的字符串;因此若是咱們在選擇使用枚舉仍是常量來替代類型碼是,選擇的標準就是類型碼是否須要持久化,也就是字符串化;
EmployeeEntity代碼:
1 public class EmployeeEntity 2 { 3 public const int Male = 2; 4 public const int Female = 2; 5 6 private int sex; 7 8 public int Sex 9 { 10 get { return sex; } 11 set { Sex= value; } 12 } 13 }
EmployeeCollectionEntity代碼:
1 public IEnumerable<EmployeeEntity> GetEntityBySex(int sex) 2 { 3 return from item in this where item.Sex== sexselect item; 4 }
測試代碼:
1 EmployeeCollectionEntity empCollection = new EmployeeCollectionEntity() 2 { 3 new EmployeeEntity() { Sex= EmployeeEntity.Female}, 4 new EmployeeEntity() { Sex= EmployeeEntity.Male }, 5 new EmployeeEntity() { Sex= EmployeeEntity.Male} 6 }; 7 8 var resultList = empCollection.GetEntityBySex(EmployeeEntity.Male); 9 if (resultList.Count() == 2 && resultList.ToList()[0].Sex== EmployeeEntity.Male && 10 resultList.ToList()[1].Sex == EmployeeEntity.Male) 11 Console.WriteLine("is ok"); 12 13 Console.ReadLine();
使用常量來代替類型碼就是在接口上只能使用數字來表示IEnumerable<EmployeeEntity> GetEntityBySex(int sex),而後咱們在調用的時候會直接使用常量類型empCollection.GetEntityBySex(EmployeeEntity.Male);
第三:使用Entity子類來替代類型碼;
對於EmployeeEntity若是在Sex角度上存在繼承體系,那麼咱們就可使用Entity子類的方式來解決;現假設,對於性別爲男和女都分別從EmployeeEntity上繼承各自的體系,MaleEmployeeEntity爲男,FemaleEmployeeEntity爲女,固然真實場景中不會爲了這一個小小的性別就獨立出一個繼承體系;
EmployeeEntity代碼:
1 public abstract class EmployeeEntity 2 { 3 public abstract bool IsFemale { get; } 4 public abstract bool IsMale { get; } 5 }
這個時候EmployeeEntity已經不在是一個真實的Employee了;
MaleEmployeeEntity代碼:
1 public class MaleEmployeeEntity : EmployeeEntity 2 { 3 public override bool IsFemale 4 { 5 get { return false; } 6 } 7 public override bool IsMale 8 { 9 get { return true; } 10 } 11 }
FemaleEmployeeEntity代碼:
1 public class FemaleEmployeeEntity : EmployeeEntity 2 { 3 public override bool IsFemale 4 { 5 get { return true; } 6 } 7 public override bool IsMale 8 { 9 get { return false; } 10 } 11 }
EmployeeCollectionEntity代碼:
1 public class EmployeeCollectionEntity : List<EmployeeEntity> 2 { 3 public IEnumerable<EmployeeEntity> FemaleEmployeeList 4 { 5 get 6 { 7 return from item in this where item.IsFemale select item; 8 } 9 } 10 11 public IEnumerable<EmployeeEntity> MaleEmployeeList 12 { 13 get 14 { 15 return from item in this where item.IsMale select item; 16 } 17 } 18 }
測試代碼:
1 EmployeeCollectionEntity empCollection = new EmployeeCollectionEntity() 2 { 3 new FemaleEmployeeEntity(), 4 new MaleEmployeeEntity() , 5 new MaleEmployeeEntity() 6 }; 7 8 var resultList = empCollection.MaleEmployeeList; 9 if (resultList.Count() == 2 && resultList.ToList()[0].IsMale && resultList.ToList()[1].IsMale) 10 Console.WriteLine("is ok"); 11 12 Console.ReadLine();
既然我們不存在類型碼了,那麼就不會存在根據參數來獲取數據的接口,因此咱們稍微變換一下,將參數拆成具體的屬性用來直接返回數據集合;
上面2】節中講到的方式都是類型碼不影響程序具體業務邏輯的狀況下的設計方式,可是一旦當類型碼直接影響到咱們DomainModel中的具體業務邏輯的狀況下我就須要將類型碼進行提取並抽象出繼承體系,而後將具體的邏輯跟類型碼繼承體系走,這也是面向對象中的面向職責設計,將行爲儘量的放入它調用最平凡的對象中去;
如今假設EmployeeEntity中有一組訂單OrderCollection,如今要根據EmployeeEntity的不一樣級別EmployeeLevel獲取(GetDistributionOrders)須要配送的OrderCollection,這裏有一個業務規則就是不一樣的等級在每次獲取配送訂單的時候是有不一樣的條件限制的,具體的條件限制跟當前的EmployeeLevel有關係,那麼這個時候咱們就須要將跟level相關的邏輯封裝進EmployeeLevel中去;
圖1:
Order代碼:
1 public class Order 2 { 3 public DateTime SubmitDtime { get; set; } 4 }
OrderCollection代碼:
1 public class OrderCollection : List<Order> 2 { 3 4 }
EmployeeEntity代碼:
1 public class EmployeeEntity 2 { 3 public EmployeeLevel Level { get; set; } 4 5 public OrderCollection AllDistributeionOrders { get; set; } 6 7 public OrderCollection GetDistributionOrders() 8 { 9 return Level.GetDistributionOrders();//將邏輯推入到類型碼以後的調用方式; 10 } 11 }
EmployeeLevel代碼:
1 public abstract class EmployeeLevel 2 { 3 public EmployeeEntity employee; 4 public abstract OrderCollection GetDistributionOrders(); 5 } 6 7 public class Normal : EmployeeLevel 8 { 9 public override OrderCollection GetDistributionOrders() 10 { 11 if (employee.AllDistributeionOrders == null && employee.AllDistributeionOrders.Count == 0) return null; 12 var orders = from order in employee.AllDistributeionOrders 13 where order.SubmitDtime <= DateTime.Now.AddDays(-5)//Normal 推遲五天配送 14 select order; 15 16 if (orders.ToList().Count == 0) return null; 17 18 OrderCollection result = new OrderCollection(); 19 20 orders.ToList().ForEach(order => { result.Add(order); }); 21 22 return result; 23 24 } 25 } 26 27 public class Super : EmployeeLevel 28 { 29 public override OrderCollection GetDistributionOrders() 30 { 31 if (employee.AllDistributeionOrders == null && employee.AllDistributeionOrders.Count == 0) return null; 32 var orders = from order in employee.AllDistributeionOrders 33 where order.SubmitDtime <= DateTime.Now.AddDays(-1)//Super 推遲一天配送 34 select order; 35 36 if (orders.ToList().Count == 0) return null; 37 38 OrderCollection result = new OrderCollection(); 39 40 orders.ToList().ForEach(order => { result.Add(order); }); 41 42 return result; 43 } 44 }
測試代碼:
1 OrderCollection orderColl = new OrderCollection(); 2 orderColl.Add(new Order() { SubmitDtime = DateTime.Now.AddDays(-2) }); 3 orderColl.Add(new Order() { SubmitDtime = DateTime.Now.AddDays(-7) }); 4 EmployeeEntity employee = new EmployeeEntity() 5 { 6 AllDistributeionOrders = orderColl 7 }; 8 9 EmployeeLevel level = new Super() { employee = employee }; 10 employee.Level = level; 11 12 var result = employee.GetDistributionOrders(); 13 if (result.Count == 2) 14 Console.WriteLine("Is ok"); 15 16 Console.ReadLine();
咱們定義了兩個EmployeeLevel,一個是Normal的,也就是普通的,他的配送限制條件是:配送必須推遲五天;二個Super,也就是超級的,他的配送只推遲一天;這樣的邏輯分支,若是咱們沒有將類型碼抽象出來進行設計,那麼咱們將面臨着一個條件分支的判斷,當後面須要加入其餘Level的時候咱們就會慢慢的陷入到判斷分支的泥潭;
在3】節中,咱們能很好的將類型碼抽象出來,可是若是咱們面臨着一個重構項目時,咱們很難去直接修改大面積的代碼,只能平衡一下將類型碼設計成具備策略意義的方式,不一樣的類型碼對應着不一樣的策略方案;
咱們仍是拿3】節中的示例來講,如今假設咱們在重構一個直接使用int做爲類型碼的EmployeeEntity,那麼咱們不可能去直接修改EmployeeEntity內部的邏輯,而是要經過引入策略工廠將不一樣的類型碼映射到策略方法中;
圖2:
因爲該節代碼比較簡單,因此就不提供示例代碼,根據上面的UML類圖基本上能夠知道代碼結構;