溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

.NET重構(gòu)(類型碼的設(shè)計、重構(gòu)方法)

發(fā)布時間:2020-06-23 01:49:21 來源:網(wǎng)絡(luò) 閱讀:1450 作者:王清培 欄目:編程語言

閱讀目錄:

  • 1.開篇介紹

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

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

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

1】開篇介紹

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

在這個Sex類型碼屬性被使用到的任何一個邏輯的地方都會有可能因為它的值不同而進行不同的邏輯分支,就好比我們在EmployeeCollectionEntity對象中定義一個方法,用來返回指定類型的所有EmployeeEntity,我們簡單假設(shè)在EmployeeeCollectionEntity的內(nèi)部肯定有一塊邏輯是用來根據(jù)當前方法的參數(shù)進行判斷,然后調(diào)用不同的方法返回當前集合中的所有執(zhí)行參數(shù)的EmployeeEntity;

上述只是一個簡單的使用場景,但是足以能簡單說明類型碼的意義和使用場景,下面我們將針對上面提到的這一個簡單的例子進行三種類型碼的使用分析和如何重構(gòu)設(shè)計;在類型碼不被任何邏輯使用只是提供給外部一個簡單的標識時,我們?nèi)绾翁幚?;在類型碼會直接影響實體內(nèi)部行為邏輯的情況下,我們?nèi)绾翁幚?;在類型碼會影響實體內(nèi)部邏輯的時候,但是我們又無法將其直接提取抽象出來時,我們?nèi)绾翁幚恚?/span>

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

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

在不影響對象內(nèi)部邏輯的情況下,問題很好處理;既然不影響對象內(nèi)部邏輯,那么它的表現(xiàn)形式起碼對于實體內(nèi)部邏輯來說無關(guān)緊要;這個時候我們對它的設(shè)計可以遵循一個原則就是OO,如果我們使用的是一個簡單的數(shù)字來表示類型碼的狀態(tài),那么我們就可以通過三個方式對它進行設(shè)計或者重構(gòu);

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

下面我們用上面1】節(jié)提到的簡單場景作為本節(jié)演示示例的領(lǐng)域模型;

EmployeeEntity 代碼:

public class EmployeeEntity
{
    private int sex;
    public int Sex
    {
        get { return sex; }
        set { sex = value; }
    }
}


EmployeeCollectionEntity代碼:

public class EmployeeCollectionEntity : List<EmployeeEntity>
{
    public IEnumerable<EmployeeEntity> GetEntityBySex(int sex)
    {
        return from item in this where item.Sex == sex select item;
    }
}

測試代碼,為了方便起見,我就沒有特地創(chuàng)建UnitTests項目,而是簡單的使用控制臺程序模擬:

EmployeeCollectionEntity empCollection = new EmployeeCollectionEntity()
            {
                new EmployeeEntity() { Sex = 1 },
                new EmployeeEntity() { Sex = 2 },
                new EmployeeEntity() { Sex = 2 }
            };
            var resultList = empCollection.GetEntityBySex(2);
            if (resultList.Count() == 2 && resultList.ToList()[0].Sex == 2 && resultList.ToList()[1].Sex==2)
                Console.WriteLine("is ok");
            Console.ReadLine();

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

下面我們將逐步使用三種方式對這種類型的業(yè)務(wù)場景進行重新設(shè)計也可以稱為重構(gòu);

第一:使用枚舉類型替換類型碼數(shù)字;

EmployeeEntity代碼:

public class EmployeeEntity
{
    public enum EmployeeSex
    {
        Male,
        Female
    }
    private EmployeeSex sex;
    public EmployeeSex Sex
    {
        get { return sex; }
        set { sex = value; }
    }
}

EmployeeCollectionEntity代碼:

public class EmployeeCollectionEntity : List<EmployeeEntity>
{
    public IEnumerable<EmployeeEntity> GetEntityBySex(EmployeeEntity.EmployeeSex sex)
    {
        return from item in this where item.Sex == sex select item;
    }
}

測試代碼:

EmployeeCollectionEntity empCollection = new EmployeeCollectionEntity()
           {
               new EmployeeEntity() { Sex = EmployeeEntity.EmployeeSex.Female },
               new EmployeeEntity() { Sex = EmployeeEntity.EmployeeSex.Male },
               new EmployeeEntity() { Sex = EmployeeEntity.EmployeeSex.Male }
           };
           var resultList = empCollection.GetEntityBySex(EmployeeEntity.EmployeeSex.Male);
           if (resultList.Count() == 2 && resultList.ToList()[0].Sex == EmployeeEntity.EmployeeSex.Male &&
               resultList.ToList()[1].Sex == EmployeeEntity.EmployeeSex.Male)
               Console.WriteLine("is ok");
           Console.ReadLine();

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

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

其實使用常量來代替類型碼時,比較常見的業(yè)務(wù)場景是在和遠程交互的時候,因為在我們將Entity翻譯成某種傳輸對象的時候需要將它的屬性使用字符串的形式表達;比如這里的EmployeeEntity,假設(shè)我們需要將某一個EmployeeEntity發(fā)送到某個消息隊列,然后消息隊列的后端程序需要將它直接插入到數(shù)據(jù)庫中,這個時候,我們的DomainModel在消息隊列的后端程序中是不存在的,也就是說并沒有和數(shù)據(jù)庫映射過,這里的屬性類型碼將是和數(shù)據(jù)庫等價的字符串;所以如果我們在選擇使用枚舉還是常量來替代類型碼是,選擇的標準就是類型碼是否需要持久化,也就是字符串化;

EmployeeEntity代碼:

public class EmployeeEntity
{
    public const int Male = 2;
    public const int Female = 2;
    private int sex;
    public int Sex
    {
        get { return sex; }
        set { sex = value; }
    }
}

EmployeeCollectionEntity代碼:

public IEnumerable<EmployeeEntity> GetEntityBySex(int sex)
{
    return from item in this where item.Sex == sex select item;
}

測試代碼:

EmployeeCollectionEntity empCollection = new EmployeeCollectionEntity()
            {
                new EmployeeEntity() { Sex = EmployeeEntity.Female},
                new EmployeeEntity() { Sex = EmployeeEntity.Male },
                new EmployeeEntity() { Sex = EmployeeEntity.Male}
            };
            var resultList = empCollection.GetEntityBySex(EmployeeEntity.Male);
            if (resultList.Count() == 2 && resultList.ToList()[0].Sex == EmployeeEntity.Male &&
                resultList.ToList()[1].Sex == EmployeeEntity.Male)
                Console.WriteLine("is ok");
            Console.ReadLine();

使用常量來代替類型碼就是在接口上只能使用數(shù)字來表示IEnumerable<EmployeeEntity> GetEntityBySex(int age),然后我們在調(diào)用的時候會直接使用常量類型empCollection.GetEntityBySex(EmployeeEntity.Male);

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

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

EmployeeEntity代碼:

public abstract class EmployeeEntity
{
    public abstract bool IsFemale { get; }
    public abstract bool IsMale { get; }
}

這個時候EmployeeEntity已經(jīng)不在是一個真實的Employee了;

MaleEmployeeEntity代碼:

public class MaleEmployeeEntity : EmployeeEntity
{
    public override bool IsFemale
    {
        get { return false; }
    }
    public override bool IsMale
    {
        get { return true; }
    }
}

FemaleEmployeeEntity代碼:

public class FemaleEmployeeEntity : EmployeeEntity
{
    public override bool IsFemale
    {
        get { return true; }
    }
    public override bool IsMale
    {
        get { return false; }
    }
}

EmployeeCollectionEntity代碼:

public class EmployeeCollectionEntity : List<EmployeeEntity>
   {
       public IEnumerable<EmployeeEntity> FemaleEmployeeList
       {
           get
           {
               return from item in this where item.IsFemale select item;
           }
       }
       public IEnumerable<EmployeeEntity> MaleEmployeeList
       {
           get
           {
               return from item in this where item.IsMale select item;
           }
       }
   }

測試代碼:

EmployeeCollectionEntity empCollection = new EmployeeCollectionEntity()
           {
               new FemaleEmployeeEntity(),
               new MaleEmployeeEntity() ,
               new MaleEmployeeEntity()
           };
           var resultList = empCollection.MaleEmployeeList;
           if (resultList.Count() == 2 && resultList.ToList()[0].IsMale && resultList.ToList()[1].IsMale)
               Console.WriteLine("is ok");
           Console.ReadLine();

既然咱們不存在類型碼了,那么就不會存在根據(jù)參數(shù)來獲取數(shù)據(jù)的接口,所以我們稍微變換一下,將參數(shù)拆成具體的屬性用來直接返回數(shù)據(jù)集合;

3】影響對象中的邏輯行為(抽象出類型碼,使用多態(tài)解決)

上面2】節(jié)中講到的方式都是類型碼不影響程序具體業(yè)務(wù)邏輯的情況下的設(shè)計方式,但是一旦當類型碼直接影響到我們DomainModel中的具體業(yè)務(wù)邏輯的情況下我就需要將類型碼進行提取并抽象出繼承體系,然后將具體的邏輯跟類型碼繼承體系走,這也是面向?qū)ο笾械拿嫦蚵氊熢O(shè)計,將行為盡可能的放入它調(diào)用最平凡的對象中去;

現(xiàn)在假設(shè)EmployeeEntity中有一組訂單OrderCollection,現(xiàn)在要根據(jù)EmployeeEntity的不同級別EmployeeLevel獲?。℅etDistributionOrders)需要配送的OrderCollection,這里有一個業(yè)務(wù)規(guī)則就是不同的等級在每次獲取配送訂單的時候是有不同的條件限制的,具體的條件限制跟當前的EmployeeLevel有關(guān)系,那么這個時候我們就需要將跟level相關(guān)的邏輯封裝進EmployeeLevel中去;

圖1:

.NET重構(gòu)(類型碼的設(shè)計、重構(gòu)方法)

Order代碼:

public class Order
{
    public DateTime SubmitDtime { get; set; }
}

OrderCollection代碼:

public class OrderCollection : List<Order>
{
}

EmployeeEntity代碼:

public class EmployeeEntity
{
    public EmployeeLevel Level { get; set; }
    public OrderCollection AllDistributeionOrders { get; set; }
    public OrderCollection GetDistributionOrders()
    {
        return Level.GetDistributionOrders();//將邏輯推入到類型碼之后的調(diào)用方式;
    }
}

EmployeeLevel代碼:

public abstract class EmployeeLevel
{
    public EmployeeEntity employee;
    public abstract OrderCollection GetDistributionOrders();
}
public class Normal : EmployeeLevel
{
    public override OrderCollection GetDistributionOrders()
    {
        if (employee.AllDistributeionOrders == null && employee.AllDistributeionOrders.Count == 0) return null;
        var orders = from order in employee.AllDistributeionOrders
                     where order.SubmitDtime <= DateTime.Now.AddDays(-5)//Normal 推遲五天配送
                     select order;
        if (orders.ToList().Count == 0) return null;
        OrderCollection result = new OrderCollection();
        orders.ToList().ForEach(order => { result.Add(order); });
        return result;
    }
}
public class Super : EmployeeLevel
{
    public override OrderCollection GetDistributionOrders()
    {
        if (employee.AllDistributeionOrders == null && employee.AllDistributeionOrders.Count == 0) return null;
        var orders = from order in employee.AllDistributeionOrders
                     where order.SubmitDtime <= DateTime.Now.AddDays(-1)//Super 推遲一天配送
                     select order;
        if (orders.ToList().Count == 0) return null;
        OrderCollection result = new OrderCollection();
        orders.ToList().ForEach(order => { result.Add(order); });
        return result;
    }
}

測試代碼:

OrderCollection orderColl = new OrderCollection();
           orderColl.Add(new Order() { SubmitDtime = DateTime.Now.AddDays(-2) });
           orderColl.Add(new Order() { SubmitDtime = DateTime.Now.AddDays(-7) });
           EmployeeEntity employee = new EmployeeEntity()
           {
               AllDistributeionOrders = orderColl
           };
           EmployeeLevel level = new Super() { employee = employee };
           employee.Level = level;
           var result = employee.GetDistributionOrders();
           if (result.Count == 2)
               Console.WriteLine("Is ok");
           Console.ReadLine();

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

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

在3】節(jié)中,我們能很好的將類型碼抽象出來,但是如果我們面臨著一個重構(gòu)項目時,我們很難去直接修改大面積的代碼,只能平衡一下將類型碼設(shè)計成具有策略意義的方式,不同的類型碼對應(yīng)著不同的策略方案;

我們還是拿3】節(jié)中的示例來說,現(xiàn)在假設(shè)我們在重構(gòu)一個直接使用int作為類型碼的EmployeeEntity,那么我們不可能去直接修改EmployeeEntity內(nèi)部的邏輯,而是要通過引入策略工廠將不同的類型碼映射到策略方法中;

圖2:

.NET重構(gòu)(類型碼的設(shè)計、重構(gòu)方法)

由于該節(jié)代碼比較簡單,所以就不提供示例代碼,根據(jù)上面的UML類圖基本上可以知道代碼結(jié)構(gòu);


作者:王清培

出處:http://wangqingpei557.blog.51cto.com/

本文版權(quán)歸作者和51CTO共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權(quán)利。



向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI