【封裝那些事】 未利用封裝


mark

未利用封裝

客戶代碼使用顯式類型檢查(使用一系列if-else或switch語句檢查對象的類型),而不利用出層次結構內已封裝的類型變化時,將導致這種壞味。

為什么要利用封裝?

一種臭名昭著的壞味是,在客戶代碼中使用條件語句(if-else或switch語句)來顯式地檢查類型,並根據類型執行相應的操作。我們這里討論的是:要檢查的類型都封裝在了層次結構中,但沒有利用這一點,即使用顯式類型檢查,而不依賴於動態多態性。這將導致如下問題:

  • 顯式類型檢查讓客戶程序和具體類型緊密耦合,降低了設計的可維護性。例如,引入新類型后,必須修改客戶程序,在其中檢查新類型以及執行相應操作的代碼。
  • 客戶程序必須顯式地檢查層次結構中所有相關的類型。如果未檢查一個或多個這樣的類型,客戶程序在運行階段可能出現意外的行為。相反,如果利用了運行時多態,完全可以避免這種問題。

未利用封裝潛在的原因

以過程型思維使用面向對象語言

開發時的思維是以代碼執行過程為導向,自然而然就會使用if-else語句和switch語句。

未應用面向對象原則

無力將面向對象的概念付諸實踐。

示例分析一

根為抽象類DataBuffer的層次結構封裝了各種基本數據結構型數組,DataBuffer的子類DataBufferByte、DataBufferUShort、DataBufferInt支持相應的基本數據類型數組。DataBuffer定義了常量TYPE_BYTE、TYPE_USHORT、TYPE_INT。客戶程序使用TYPE_BYTE、TYPE_USHORT、TYPE_INT的DataBuffer來存儲數據。

下面是客戶程序的示例,演示如何使用switch語句執行針對具體類型的顯式類型檢查。

switch (transferType)
{
    case DataBuffer.TYPE_BYTE:
        byte[] bdata = (byte[])inData;
        pixel = bdata[0] & 0xff;
        length = bdata.Length;
        break;
    case DataBuffer.TYPE_USHORT:
        short[] sdata = (short[])inData;
        pixel = sdata[0] & 0xffff;
        length = sdata.Length;
        break;
    case DataBuffer.TYPE_INT:
        int[] idata = (int[])inData;
        pixel = idata[0];
        length = idata.Length;
        break;
    default:
        throw new Exception("不支持的transferType");
}

上面代碼使用的數據成員transferType定義如下:

protected int transferType;

重構建議:將決定行為的條件語句刪除,並在層次結構中引入多態方法。

在客戶程序中,提供合適的DataBuffer子類對象。在DataBuffer層次結構類型中,定義方法GetPixel()和GetLengthl()。

public abstract class DataBuffer
{
    public const int TYPE_BYTE = 1;
    public const int TYPE_DOUBLE = 2;
    public const int TYPE_FLOAT = 3;
    public const int TYPE_INT = 4;
    public const int TYPE_USHORT = 5;

    public abstract int GetPixel(object inData);
    public abstract int GetLength(object inData);
}

public class DataBufferInt: DataBuffer
{
    public override int GetPixel(object inData)
    {
        int[] idata = (int[])inData;
        return  idata[0];
    }
    public override int GetLength(object inData)
    {
        int[] idata = (int[])inData;
        return idata.Length;
    }
}

public class DataBufferByte : DataBuffer
{
    public override int GetPixel(object inData)
    {
        byte[] bdata = (byte[])inData;
        return bdata[0] & 0xff;
    }
    public override int GetLength(object inData)
    {
        byte[] bdata = (byte[])inData;
        return bdata.Length;
    }
}

public class DataBufferUShort : DataBuffer
{
    public override int GetPixel(object inData)
    {
        short[] sdata = (short[])inData;
        return sdata[0] & 0xffff;  
    }
    public override int GetLength(object inData)
    {
        short[] sdata = (short[])inData;
        return sdata.Length;
    }
}

並將客戶程序switch語句及其case語句簡化為:

int pixel = GetPixel(inData);
int length = GetLength(inData);

由於引用dataBuffer指向的是傳入的DataBuffer子類對象,因此上述語句將調用相應子類的GetPixel()和GetLength()方法。這里需要注意的是客戶程序代碼提供特定DataBuffer子類對象,檢查輸入數據類型和創建DataBuffer子類對象的工作由客戶程序負責。可能需要在客戶代碼或一個工廠類中使用switch-case語句,而只需要使用一次這個switch-case語句。由於客戶程序不知道具體是哪個DataBuffer子類,所以它與DataBuffer層次結構耦合更低。這樣在DataBuffer層次結構修改既有類型和添加新類型時,不會對客戶程序造成影響。即使有影響也是只需要使用一次的這個switch-case語句,修改代碼代價極小。

這讓我想起,我在看完《重構》后天真幼稚的想消除項目中的switch-case語句,只要項目中存在switch-case語句我就覺得存在壞味道,此后的一段時間我很痛苦,因為項目中總是存在消滅不了的switch-case語句。其實如果項目中需要與外部世界的實體交互,要避免使用條件邏輯很難。例如用戶在頁面的操作在代碼中肯定對應不同的對象來處理,這中間必須使用條件邏輯判斷使用哪個對象處理。但是這樣的判斷應該只有一處,負責日后的代碼維護是個災難。

示例分析二

還是那句話switch-case語句和if-else語句不可怕,可怕的是多個witch-case語句和if-else語句。

對於這樣的代碼我們要給予充分的關注:

代碼1:

if(obj is XXX)
{
    //做事情A
}
if(obj is YYY)
{
    //做事情B
}
if(obj is ZZZ)
{
    //做事情C
}

代碼2:

if(obj is XXX)
{
    //做事情A
}
if(obj is YYY)
{
    //做事情B
}
if(obj is ZZZ)
{
    //做事情C
}

代碼3:

if(obj is XXX)
{
    //做事情A
}
if(obj is YYY)
{
    //做事情B
}
if(obj is ZZZ)
{
    //做事情C
}

這樣的代碼是難以擴展的,新增一個類NNN,就需要找到代碼1、2、3甚至n進行修改,很容易遺漏。而且遺漏造成的錯誤只用在代碼運行階段才能發現。

這種情況反映出來的問題就是沒有利用封裝,已經有了層次結構,卻沒有予以利用。沒有面向接口編程,每個地方面向的都是具體的實現類,每個地方都需要判斷實例的類型才可以進行下一步的動作。

進行重構:

代碼1:

obj.DoSomething1();

代碼2:

obj.DoSomething2();

代碼3:

obj.DoSomething3();

obj可以是XXX、YYY、ZZZ。對於現在的代碼,新增一個類NNN,代碼1、2、3甚至n處根本不需要任何改動。因為它們實現了統一的接口,並且符合開閉原則。

參考:《軟件設計重構》

作者: 擼碼那些事
來源: http://songwenjie.cnblogs.com/
聲明:本文為博主學習感悟總結,水平有限,如果不當,歡迎指正。如果您認為還不錯,不妨點擊一下下方的 推薦按鈕,謝謝支持。轉載與引用請注明出處。
微信公眾號:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM