小酌重構系列[8]——提取接口


前言

世間唯一“不變”的是“變化”本身,這句話同樣適用於軟件設計和開發。
在軟件系統中,模塊(類、方法)應該依賴於抽象,而不應該依賴於實現。

當需求發生“變化”時,如果模塊(類、方法)依賴於具體實現,具體實現也需要修改;
如果模塊(類、方法)依賴於接口,則無需修改現有實現,而是基於接口擴展新的實現。

面向實現?面向接口?

接口可以被復用,但接口的實現卻不一定能被復用。

面向實現編程,意味着軟件的模塊(類、方法)之間的耦合性非常高,每次遭遇“變化”,都會涉及到修改,並且可能是牽一發而動全身的。
每次修改,都需要對原有的代碼重新測試,也可能給舊的代碼引入新的錯誤。

面向接口編程,是為了應對軟件設計和開發中的“變化”,它是一種“以不變應萬變”的思維模式。
只要確保我們的抽象(接口)是不變的,無論需求怎么變化,我們總能通過擴展新的實現自如地應對。

接口是穩定的,關閉的,但接口的實現是可變的,開放的。

開閉原則

“依賴於抽象,而不是具體實現”,它同時也是開閉原則的一種體現。

開閉原則的定義:當軟件需要變化時,盡量通過擴展軟件實體的行為來實現變化,而不是通過修改已有的代碼來實現變化。它是面向對象的基本原則之一。

開閉原則主要有兩個特征:
(1)擴展開放(Open for extension)

(2)修改關閉(Closed for modification)
開閉原則要求模塊(類、方法)應具備良好的擴展性,同時對現有的功能具有一定的保護能力。

開閉原則是一個比較模糊的一個原則,它沒有告訴你如何才能對擴展開放,以及如何才能對修改關閉。
這需要借助我們自身的經驗,以及對需求的理解程度,去分析軟件系統中抽象的部分,識別其中的“變化”和“不變”。

提取接口

“提取接口”是面向對象編程常用的解耦策略,將一些可能發生變化的具體實現提取為接口,將“變化”封裝起來,從而達到依賴接口、而非具體實現的目的。

示例

重構前

以下是一個課程注冊的場景,這段代碼提供了2個類:ClassRegistration和RegistrationProcessor,RegistrationProcessor依賴於ClassRegistration的Create()方法和Total屬性。

public class ClassRegistration
{
    public void Create()
    {
        // create registration code
    }

    public decimal Total { get; private set; }
}

public class RegistrationProcessor
{
    public decimal ProcessRegistration(ClassRegistration registration)
    {
        registration.Create();
        return registration.Total;
    }
}

假如系統的業務發生了變化,ClassRegistration類的Create()方法已經不能滿足新的業務了,我們需要使用另外的注冊方法。
這意味着,我們需要修改ClassRegistration類的Create()方法,且需要讓其同時滿足舊業務和新業務。
image

RegistrationProcessor依賴於ClassRegistration,既然ClassRegistration存在着諸多變數,我們可以使用“提取接口”的重構策略,讓RegistrationProcessor依賴於某個接口。

重構后

重構后,RegistrationProcessor依賴於IClassRegistration接口,RegistrationProcessor不必去關心IClassRegistration的具體實現是什么。
新的業務要求不同的Create()方式時,我們無需更改現有的ClassRegistration,而是添加新class並實現IClassRegistration接口。

另外,在不同場景下,舊業務和新業務的實現可能在不同場景下被使用。
這時,我們可以借助IoC框架將IClassRegistration接口的實例注入到指定場景。

public interface IClassRegistration
{
    void Create();

    decimal Total { get; }
}

public class ClassRegistration : IClassRegistration
{
    public void Create()
    {
        // create registration code
    }

    public decimal Total { get; private set; }
}

public class RegistrationProcessor
{
    public decimal ProcessRegistration(IClassRegistration registration)
    {
        registration.Create();
        return registration.Total;
    }
}

最后,下面這幅圖描述了這次重構過程。

image


免責聲明!

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



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