【封裝那些事】 缺失封裝

CoderFocus發表於2018-05-15
mark

缺失封裝

沒有將實現變化封裝在抽象和層次結構中時,將導致這種壞味。

表現形式通常如下:

  • 客戶程式與其需要的服務變種緊密耦合,每當需要支援新變種或修改既有變種時,都將影響客戶程式。
  • 每當需要在層次結構中支援新變種時,都新增了大量不必要的類,這增加了設計的複雜度。

為什麼不能缺失封裝?

開閉原則(OCP)指出,型別應對擴充套件開放,對修改關閉。也就是說應該通過擴充套件(而不是修改)來改變型別的行為。沒有在型別或層次結構中封裝實現變化時,便違反了OCP。

缺失封裝潛在的原因

未意識到關注點會不斷變化

沒有預測到關注點可能發生變化,進而沒有在設計中正確封裝這些關注點。

混合關注點

將彼此獨立的各個關注點聚合在一個層次結構中,而不是分開時,如果關注點發生變化,可能導致類的數量呈爆炸式增長。

幼稚的設計決策

採用過於簡單的方法,如為每種變化組合建立一個類時,可能導致設計無謂的複雜。

示例分析一

假設有一個Entryption類,它需要使用加密演算法對資料進行加密。可供選擇的加密演算法有很多,包括DES(資料加密標準)、AES(高階加密標準)、TDES(三重資料加密標準)等。Entryption類使用DES演算法對資料進行加密。

public class Encryption
{
    /// <summary>
    /// 使用DES演算法進行加密
    /// </summary>
    public void Encrypt()
    {
        // 使用DES演算法進行加密
    }
}
複製程式碼

假設出現了新需求,要求使用AES演算法對資料進行加密。

最差的方案出現了:

public class Encryption
{
    /// <summary>
    /// 使用DES演算法進行加密
    /// </summary>
    public void EncryptUsingDES()
    {
        // 使用DES演算法進行加密
    }

     /// <summary>
    /// 使用AES演算法進行加密
    /// </summary>
    public void EncryptUsingAES()
    {
        // 使用AES演算法進行加密
    }
}
複製程式碼

這種方案有很多不盡如人意的地方:

  • Encryption類變得更大、更難以維護,因為它實現了多種加密演算法,但是每次只使用一種。
  • 難以新增新演算法以及修改既有演算法,因為加密演算法是Encryption類不可分割的部分。
  • 加密演算法向Encryption類提供服務,但是與Encryption類緊緊耦合在一起,無法在其它地方重用。

不滿意就重構,首先使用繼承進行重構,會有2種方案可以選擇:

選擇1:

讓Encryption類根據需求繼承AESEncryptionAlgorithm或DESEncryptionAlgorithm類,並提供方法Encrypt()。這種方案帶來的問題是Encryption類在編譯階段就將關聯到特定的加密演算法,更嚴重的是類之間的關係並不是is-a關係。

/// <summary>
/// AES演算法加密類
/// </summary>
public class AESEncryptionAlgorithm
{
    /// <summary>
    /// 使用AES演算法進行加密
    /// </summary>
    public void EncryptUsingAES()
    {
        // 使用AES演算法進行加密
    }
}

/// <summary>
/// DES演算法加密類
/// </summary>
public class DESEncryptionAlgorithm
{
    /// <summary>
    /// 使用DES演算法進行加密
    /// </summary>
    public void EncryptUsingDES()
    {
        // 使用DES演算法進行加密
    }
}

public class Encryption: AESEncryptionAlgorithm
{
    /// <summary>
    /// 使用演算法進行加密
    /// </summary>
    public void Encrypt()
    {
        EncryptUsingAES();
    }
}
複製程式碼

選擇2:

建立子類AESEncryption和DESEncryption,它們都擴充套件了Encryption類,並分別包含加密演算法AES和DES的實現。客戶程式可建立Encryption的引用,這些引用指向特定子類的物件。通過新增新的子類,很容易支援新的加密演算法。但是這種方案的問題是AESEncryption和DESEncryption將繼承Encryption類的其它方法,降低了加密演算法的可重用性。

public abstract class Encryption
{
    /// <summary>
    /// 使用演算法進行加密
    /// </summary>
    public abstract void Encrypt();
}

/// <summary>
/// AES演算法加密類
/// </summary>
public class AESEncryption : Encryption
{
    /// <summary>
    /// 使用 AES演算法進行加密
    /// </summary>
    public override void Encrypt()
    {
        // 使用 AES演算法進行加密
    }
}

/// <summary>
/// DES演算法加密類
/// </summary>
public class DESEncryption : Encryption
{
    /// <summary>
    /// 使用 DES演算法進行加密
    /// </summary>
    public override void Encrypt()
    {
        // 使用 DES演算法進行加密
    }
}
複製程式碼

最佳的選擇是使用策略模式:

  • 可在執行階段給Encryption物件配置特定的加密演算法
  • 可在其它地方重用層次結構EncryptionAlgorithm中定義的演算法
  • 很容易根據需要支援新的演算法
/// <summary>
/// 演算法加密介面
/// </summary>
public interface EncryptionAlgorithm
{
   void Encrypt();
}

/// <summary>
/// DES演算法加密類
/// </summary>
public class DESEncryptionAlgorithm : EncryptionAlgorithm
{
    public void Encrypt()
    {
        //使用 DES演算法進行加密
    }
}
/// <summary>
/// AES演算法加密類
/// </summary>
public class AESEncryptionAlgorithm : EncryptionAlgorithm
{
    public void Encrypt()
    {
        //使用 AES演算法進行加密
    }
}

public class Encryption
{
    private EncryptionAlgorithm algo;

    public Encryption(EncryptionAlgorithm algo)
    {
        this.algo = algo;
    }
 
    /// <summary>
    /// 使用演算法進行加密
    /// </summary>
    public void Encrypt()
    {
        algo.Encrypt();
    }
}
複製程式碼

示例分析二

支援使用不同演算法(DES和AES)對各種內容(Image和Text)進行加密的設計。

最簡單最直觀的的設計:

mark

在這個設計中,有兩個變化點:支援的內容型別和加密演算法型別。對於這兩個變化點的每種可能組合,都使用了一個類來表示。這樣會有一個嚴重的問題:假設現在要求支援新加密演算法TDES和新內容型別Data,類的數量呈爆炸性增長。因為變化點混在了一起,沒有分別進行封裝。

mark

使用橋接模式進行封裝:

mark

使用橋接模式,分別封裝這兩個關注點的變化。現在要引入新內容型別Data和新加密演算法TDES,只需要新增兩個新類。既解決了類數量呈爆炸增長的問題,又增加了根為介面EncryptionAlgorithm層次結構中的加密演算法的可重用性。

總結

  1. 不相關的關注點混在一起,抽象將變得難以重用。

  2. 對業務中可能的變化點,要給予擴充套件點,保證開閉原則(OCP),對擴充套件開放,對修改關閉。


參考:《軟體設計重構》


                                                       -----END-----


                  喜歡本文的朋友們,歡迎掃一掃下圖關注公眾號擼碼那些事,收看更多精彩內容

                                         【封裝那些事】 缺失封裝


相關文章