天天看點

.NET重構(類型碼的設計、重構方法)1】開篇介紹2】不影響對象中的邏輯行為(枚舉、常量、Entity子類來替代類型碼)3】影響對象中的邏輯行為(抽象出類型碼,使用多态解決)4】無法直接抽象出類型碼(使用政策模式解決)

閱讀目錄:

1.開篇介紹

2.不影響對象中的邏輯行為(枚舉、常量、Entity子類來替代類型碼)

3.影響對象中的邏輯行為(抽象出類型碼,使用多态解決)

4.無法直接抽象出類型碼(使用政策模式解決)

說到類型碼,我們都會很有印象,在某個Entity内部多多少少會出現一兩個類型碼來表示目前Entity在某個抽象角度屬于哪一種層面,比如在EmployeeEntity中,基本上會有一個表示性别的Sex的屬性,同時Sex屬性的最終儲存是在某個Sex字段中的,它就是很典型的類型碼元素;Sex類型碼屬性用來表達了在用性别這一個抽象角度對實體進行分類時,那麼實體會存在着兩種被歸納的層面(男、女);

在這個Sex類型碼屬性被使用到的任何一個邏輯的地方都會有可能因為它的值不同而進行不同的邏輯分支,就好比我們在EmployeeCollectionEntity對象中定義一個方法,用來傳回指定類型的所有EmployeeEntity,我們簡單假設在EmployeeeCollectionEntity的内部肯定有一塊邏輯是用來根據目前方法的參數進行判斷,然後調用不同的方法傳回目前集合中的所有執行參數的EmployeeEntity;

上述隻是一個簡單的使用場景,但是足以能簡單說明類型碼的意義和使用場景,下面我們将針對上面提到的這一個簡單的例子進行三種類型碼的使用分析和如何重構設計;在類型碼不被任何邏輯使用隻是提供給外部一個簡單的辨別時,我們如何處理;在類型碼會直接影響實體内部行為邏輯的情況下,我們如何處理;在類型碼會影響實體内部邏輯的時候,但是我們又無法将其直接提取抽象出來時,我們如何處理;

我們帶着這個三個簡單的問題進行下面的具體分析;

在不影響對象内部邏輯的情況下,問題很好處理;既然不影響對象内部邏輯,那麼它的表現形式起碼對于實體内部邏輯來說無關緊要;這個時候我們對它的設計可以遵循一個原則就是OO,如果我們使用的是一個簡單的數字來表示類型碼的狀态,那麼我們就可以通過三個方式對它進行設計或者重構;

這裡有一個小小問題的就是,如果我們正在進行一項局部DomainModel内部的重構時,我們的工作量會很大而且需要很好的單元測試來支撐;但是如果我們目前正在設計一個Entity問題就很簡單;

下面我們用上面1】節提到的簡單場景作為本節示範示例的領域模型;

EmployeeEntity 代碼:

1

2

3

4

5

6

7

8

9

<code>public</code> <code>class</code> <code>EmployeeEntity</code>

<code>{</code>

<code>    </code><code>private</code> <code>int</code> <code>sex;</code>

<code>    </code><code>public</code> <code>int</code> <code>Sex</code>

<code>    </code><code>{</code>

<code>        </code><code>get</code> <code>{ </code><code>return</code> <code>sex; }</code>

<code>        </code><code>set</code> <code>{ sex = value; }</code>

<code>    </code><code>}</code>

<code>}</code>

EmployeeCollectionEntity代碼:

<code>public</code> <code>class</code> <code>EmployeeCollectionEntity : List&lt;EmployeeEntity&gt;</code>

<code>    </code><code>public</code> <code>IEnumerable&lt;EmployeeEntity&gt; GetEntityBySex(</code><code>int</code> <code>sex)</code>

<code>        </code><code>return</code> <code>from</code> <code>item </code><code>in</code> <code>this</code> <code>where</code> <code>item.Sex == sex </code><code>select</code> <code>item;</code>

測試代碼,為了友善起見,我就沒有特地建立UnitTests項目,而是簡單的使用控制台程式模拟:

10

<code>EmployeeCollectionEntity empCollection = </code><code>new</code> <code>EmployeeCollectionEntity()</code>

<code>            </code><code>{</code>

<code>                </code><code>new</code> <code>EmployeeEntity() { Sex = 1 },</code>

<code>                </code><code>new</code> <code>EmployeeEntity() { Sex = 2 },</code>

<code>                </code><code>new</code> <code>EmployeeEntity() { Sex = 2 }</code>

<code>            </code><code>};</code>

<code>            </code><code>var</code> <code>resultList = empCollection.GetEntityBySex(2);</code>

<code>            </code><code>if</code> <code>(resultList.Count() == 2 &amp;&amp; resultList.ToList()[0].Sex == 2 &amp;&amp; resultList.ToList()[1].Sex==2)</code>

<code>                </code><code>Console.WriteLine(</code><code>"is ok"</code><code>);</code>

<code>            </code><code>Console.ReadLine();</code>

上述代碼很簡單,一個Employee用來表示員工實體,EmployeeCollectionEntity表示員工實體集,用來封裝一組包含業務邏輯的Empoyee集合;目前在EmployeeCollectionEntity中有一個方法GetEntityBySex(int sex),用來根據性别類型碼來擷取集合内部中滿足條件的所有EmpoyeeEntity,在單元測試中的代碼,我們使用1表示女性,2表示男性,單元測試通過測試代碼正确的查詢出兩組男性EmployeeEntity實體;

下面我們将逐漸使用三種方式對這種類型的業務場景進行重新設計也可以稱為重構;

第一:使用枚舉類型替換類型碼數字;

EmployeeEntity代碼:

11

12

13

14

<code>    </code><code>public</code> <code>enum</code> <code>EmployeeSex</code>

<code>        </code><code>Male,</code>

<code>        </code><code>Female</code>

<code>    </code><code>private</code> <code>EmployeeSex sex;</code>

<code>    </code><code>public</code> <code>EmployeeSex Sex</code>

<code>    </code><code>public</code> <code>IEnumerable&lt;EmployeeEntity&gt; GetEntityBySex(EmployeeEntity.EmployeeSex sex)</code>

測試代碼:

<code>           </code><code>{</code>

<code>               </code><code>new</code> <code>EmployeeEntity() { Sex = EmployeeEntity.EmployeeSex.Female },</code>

<code>               </code><code>new</code> <code>EmployeeEntity() { Sex = EmployeeEntity.EmployeeSex.Male },</code>

<code>               </code><code>new</code> <code>EmployeeEntity() { Sex = EmployeeEntity.EmployeeSex.Male }</code>

<code>           </code><code>};</code>

<code>           </code><code>var</code> <code>resultList = empCollection.GetEntityBySex(EmployeeEntity.EmployeeSex.Male);</code>

<code>           </code><code>if</code> <code>(resultList.Count() == 2 &amp;&amp; resultList.ToList()[0].Sex == EmployeeEntity.EmployeeSex.Male &amp;&amp;</code>

<code>               </code><code>resultList.ToList()[1].Sex == EmployeeEntity.EmployeeSex.Male)</code>

<code>               </code><code>Console.WriteLine(</code><code>"is ok"</code><code>);</code>

<code>           </code><code>Console.ReadLine();</code>

通過使用枚舉我們能很好的使用OOD的好處,這樣代碼中不會到處充斥這亂七八糟的魔幻數字;

第二:使用常量來代替類型碼;

其實使用常量來代替類型碼時,比較常見的業務場景是在和遠端互動的時候,因為在我們将Entity翻譯成某種傳輸對象的時候需要将它的屬性使用字元串的形式表達;比如這裡的EmployeeEntity,假設我們需要将某一個EmployeeEntity發送到某個消息隊列,然後消息隊列的後端程式需要将它直接插入到資料庫中,這個時候,我們的DomainModel在消息隊列的後端程式中是不存在的,也就是說并沒有和資料庫映射過,這裡的屬性類型碼将是和資料庫等價的字元串;是以如果我們在選擇使用枚舉還是常量來替代類型碼是,選擇的标準就是類型碼是否需要持久化,也就是字元串化;

<code>    </code><code>public</code> <code>const</code> <code>int</code> <code>Male = 2;</code>

<code>    </code><code>public</code> <code>const</code> <code>int</code> <code>Female = 2;</code>

<code>public</code> <code>IEnumerable&lt;EmployeeEntity&gt; GetEntityBySex(</code><code>int</code> <code>sex)</code>

<code>    </code><code>return</code> <code>from</code> <code>item </code><code>in</code> <code>this</code> <code>where</code> <code>item.Sex == sex </code><code>select</code> <code>item;</code>

<code>                </code><code>new</code> <code>EmployeeEntity() { Sex = EmployeeEntity.Female},</code>

<code>                </code><code>new</code> <code>EmployeeEntity() { Sex = EmployeeEntity.Male },</code>

<code>                </code><code>new</code> <code>EmployeeEntity() { Sex = EmployeeEntity.Male}</code>

<code>            </code><code>var</code> <code>resultList = empCollection.GetEntityBySex(EmployeeEntity.Male);</code>

<code>            </code><code>if</code> <code>(resultList.Count() == 2 &amp;&amp; resultList.ToList()[0].Sex == EmployeeEntity.Male &amp;&amp;</code>

<code>                </code><code>resultList.ToList()[1].Sex == EmployeeEntity.Male)</code>

使用常量來代替類型碼就是在接口上隻能使用數字來表示IEnumerable&lt;EmployeeEntity&gt; GetEntityBySex(int age),然後我們在調用的時候會直接使用常量類型empCollection.GetEntityBySex(EmployeeEntity.Male);

第三:使用Entity子類來替代類型碼;

對于EmployeeEntity如果在Sex角度上存在繼承體系,那麼我們就可以使用Entity子類的方式來解決;現假設,對于性别為男和女都分别從EmployeeEntity上繼承各自的體系,MaleEmployeeEntity為男,FemaleEmployeeEntity為女,當然真實場景中不會為了這一個小小的性别就獨立出一個繼承體系;

<code>public</code> <code>abstract</code> <code>class</code> <code>EmployeeEntity</code>

<code>    </code><code>public</code> <code>abstract</code> <code>bool</code> <code>IsFemale { </code><code>get</code><code>; }</code>

<code>    </code><code>public</code> <code>abstract</code> <code>bool</code> <code>IsMale { </code><code>get</code><code>; }</code>

這個時候EmployeeEntity已經不在是一個真實的Employee了;

MaleEmployeeEntity代碼:

<code>public</code> <code>class</code> <code>MaleEmployeeEntity : EmployeeEntity</code>

<code>    </code><code>public</code> <code>override</code> <code>bool</code> <code>IsFemale</code>

<code>        </code><code>get</code> <code>{ </code><code>return</code> <code>false</code><code>; }</code>

<code>    </code><code>public</code> <code>override</code> <code>bool</code> <code>IsMale</code>

<code>        </code><code>get</code> <code>{ </code><code>return</code> <code>true</code><code>; }</code>

FemaleEmployeeEntity代碼:

<code>public</code> <code>class</code> <code>FemaleEmployeeEntity : EmployeeEntity</code>

15

16

17

<code>   </code><code>{</code>

<code>       </code><code>public</code> <code>IEnumerable&lt;EmployeeEntity&gt; FemaleEmployeeList</code>

<code>       </code><code>{</code>

<code>           </code><code>get</code>

<code>               </code><code>return</code> <code>from</code> <code>item </code><code>in</code> <code>this</code> <code>where</code> <code>item.IsFemale </code><code>select</code> <code>item;</code>

<code>           </code><code>}</code>

<code>       </code><code>}</code>

<code>       </code><code>public</code> <code>IEnumerable&lt;EmployeeEntity&gt; MaleEmployeeList</code>

<code>               </code><code>return</code> <code>from</code> <code>item </code><code>in</code> <code>this</code> <code>where</code> <code>item.IsMale </code><code>select</code> <code>item;</code>

<code>   </code><code>}</code>

<code>               </code><code>new</code> <code>FemaleEmployeeEntity(),</code>

<code>               </code><code>new</code> <code>MaleEmployeeEntity() ,</code>

<code>               </code><code>new</code> <code>MaleEmployeeEntity()</code>

<code>           </code><code>var</code> <code>resultList = empCollection.MaleEmployeeList;</code>

<code>           </code><code>if</code> <code>(resultList.Count() == 2 &amp;&amp; resultList.ToList()[0].IsMale &amp;&amp; resultList.ToList()[1].IsMale)</code>

既然咱們不存在類型碼了,那麼就不會存在根據參數來擷取資料的接口,是以我們稍微變換一下,将參數拆成具體的屬性用來直接傳回資料集合;

上面2】節中講到的方式都是類型碼不影響程式具體業務邏輯的情況下的設計方式,但是一旦當類型碼直接影響到我們DomainModel中的具體業務邏輯的情況下我就需要将類型碼進行提取并抽象出繼承體系,然後将具體的邏輯跟類型碼繼承體系走,這也是面向對象中的面向職責設計,将行為盡可能的放入它調用最平凡的對象中去;

現在假設EmployeeEntity中有一組訂單OrderCollection,現在要根據EmployeeEntity的不同級别EmployeeLevel擷取(GetDistributionOrders)需要配送的OrderCollection,這裡有一個業務規則就是不同的等級在每次擷取配送訂單的時候是有不同的條件限制的,具體的條件限制跟目前的EmployeeLevel有關系,那麼這個時候我們就需要将跟level相關的邏輯封裝進EmployeeLevel中去;

圖1:

<a href="http://blog.51cto.com/attachment/201311/141116287.jpg" target="_blank"></a>

Order代碼:

<code>public</code> <code>class</code> <code>Order</code>

<code>    </code><code>public</code> <code>DateTime SubmitDtime { </code><code>get</code><code>; </code><code>set</code><code>; }</code>

OrderCollection代碼:

<code>public</code> <code>class</code> <code>OrderCollection : List&lt;Order&gt;</code>

<code>    </code><code>public</code> <code>EmployeeLevel Level { </code><code>get</code><code>; </code><code>set</code><code>; }</code>

<code>    </code><code>public</code> <code>OrderCollection AllDistributeionOrders { </code><code>get</code><code>; </code><code>set</code><code>; }</code>

<code>    </code><code>public</code> <code>OrderCollection GetDistributionOrders()</code>

<code>        </code><code>return</code> <code>Level.GetDistributionOrders();</code><code>//将邏輯推入到類型碼之後的調用方式;</code>

EmployeeLevel代碼:

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

<code>public</code> <code>abstract</code> <code>class</code> <code>EmployeeLevel</code>

<code>    </code><code>public</code> <code>EmployeeEntity employee;</code>

<code>    </code><code>public</code> <code>abstract</code> <code>OrderCollection GetDistributionOrders();</code>

<code>public</code> <code>class</code> <code>Normal : EmployeeLevel</code>

<code>    </code><code>public</code> <code>override</code> <code>OrderCollection GetDistributionOrders()</code>

<code>        </code><code>if</code> <code>(employee.AllDistributeionOrders == </code><code>null</code> <code>&amp;&amp; employee.AllDistributeionOrders.Count == 0) </code><code>return</code> <code>null</code><code>;</code>

<code>        </code><code>var</code> <code>orders = </code><code>from</code> <code>order </code><code>in</code> <code>employee.AllDistributeionOrders</code>

<code>                     </code><code>where</code> <code>order.SubmitDtime &lt;= DateTime.Now.AddDays(-5)</code><code>//Normal 推遲五天配送</code>

<code>                     </code><code>select</code> <code>order;</code>

<code>        </code><code>if</code> <code>(orders.ToList().Count == 0) </code><code>return</code> <code>null</code><code>;</code>

<code>        </code><code>OrderCollection result = </code><code>new</code> <code>OrderCollection();</code>

<code>        </code><code>orders.ToList().ForEach(order =&gt; { result.Add(order); });</code>

<code>        </code><code>return</code> <code>result;</code>

<code>public</code> <code>class</code> <code>Super : EmployeeLevel</code>

<code>                     </code><code>where</code> <code>order.SubmitDtime &lt;= DateTime.Now.AddDays(-1)</code><code>//Super 推遲一天配送</code>

<code>OrderCollection orderColl = </code><code>new</code> <code>OrderCollection();</code>

<code>           </code><code>orderColl.Add(</code><code>new</code> <code>Order() { SubmitDtime = DateTime.Now.AddDays(-2) });</code>

<code>           </code><code>orderColl.Add(</code><code>new</code> <code>Order() { SubmitDtime = DateTime.Now.AddDays(-7) });</code>

<code>           </code><code>EmployeeEntity employee = </code><code>new</code> <code>EmployeeEntity()</code>

<code>               </code><code>AllDistributeionOrders = orderColl</code>

<code>           </code><code>EmployeeLevel level = </code><code>new</code> <code>Super() { employee = employee };</code>

<code>           </code><code>employee.Level = level;</code>

<code>           </code><code>var</code> <code>result = employee.GetDistributionOrders();</code>

<code>           </code><code>if</code> <code>(result.Count == 2)</code>

<code>               </code><code>Console.WriteLine(</code><code>"Is ok"</code><code>);</code>

我們定義了兩個EmployeeLevel,一個是Normal的,也就是普通的,他的配送限制條件是:配送必須推遲五天;二個Super,也就是超級的,他的配送隻推遲一天;這樣的邏輯分支,如果我們沒有将類型碼抽象出來進行設計,那麼我們将面臨着一個條件分支的判斷,當後面需要加入其他Level的時候我們就會慢慢的陷入到判斷分支的泥潭;

在3】節中,我們能很好的将類型碼抽象出來,但是如果我們面臨着一個重構項目時,我們很難去直接修改大面積的代碼,隻能平衡一下将類型碼設計成具有政策意義的方式,不同的類型碼對應着不同的政策方案;

我們還是拿3】節中的示例來說,現在假設我們在重構一個直接使用int作為類型碼的EmployeeEntity,那麼我們不可能去直接修改EmployeeEntity内部的邏輯,而是要通過引入政策工廠将不同的類型碼映射到政策方法中;

圖2:

<a href="http://blog.51cto.com/attachment/201311/141254800.jpg" target="_blank"></a>

由于該節代碼比較簡單,是以就不提供示例代碼,根據上面的UML類圖基本上可以知道代碼結構;

 本文轉自 王清培 51CTO部落格,原文連結:http://blog.51cto.com/wangqingpei557/1327850,如需轉載請自行聯系原作者