小酌重構系列[3]——方法、字段的提高和下降

本文要介紹的是4種重構策略,它們分別是提高方法、下降方法、提高字段和下降字段。
因爲這4種重構策略具備必定的相通性,因此我將它們放到一篇來說解。html

定義

如下是這4種策略的定義ide

提高方法:當子類的方法描述了相同的行爲時,應將這樣的方法提高到基類。
下降方法:在基類中的行爲僅和個別子類相關時,應將這樣的行爲下降到子類。
提高字段:當子類中的字段描述着相同的信息時,應將這樣的字段提高到基類。
下降字段:當基類中的字段僅僅用於個別子類時,應將這樣的字段下降到子類。
url

以上的定義是較爲爲枯燥無趣的,各位讀者大可沒必要care文字的內容,由於這是我本身的理解,大家應該會有本身的理解。
接下來,我要介紹本文的重點——語義,這有助於咱們理解並良好地使用這些重構策略。spa

理解「語義」

語義含義

在前面的文章中,我經常提到一個詞「語義」,咱們先來看看語義的定義。如下內容是對語義的解釋,這段內容我引用自百度百科code

數據的含義就是語義(semantic)。簡單的說,數據就是符號。數據自己沒喲佮意義,只有賦予含義的數據纔可以被使用,這時候數據就轉化爲了信息,而數據的含義就是語義。htm

語義能夠簡單地看做是數據對應的現實世界中的事物所表明的概念和含義,以及這些含義直接的關係,是數據在某個領域上的解釋和邏輯表示。對象

語義具備領域特徵,不屬於任何領域的語義是不存在的。blog

而我對它的理解是:在事物所處的環境下,事物所表現的概念和含義。
這裏面有2點要強調一下:繼承

1. 事物:是指現實世界(真實)存在的個體,好比一我的、一輛車。這也是咱們所說的對象。
2. 環境:是指現實世界的環境,咱們也能夠將環境理解爲上下文。
ip

咱們須要結合這2點去理解語義,事物不能脫離環境單獨存在,事物在不一樣的環境下表現出的特徵和行爲會有所不一樣。

語義舉例

一輛普通的大衆捷達汽車,若是它處於「出租車公司」這樣一個語境,那麼它的表現特徵是「出租車」,體現出來的行爲是「爲市民提供有償的乘車服務」。
若是這輛車處於「某人的車」這樣一個語境,那麼它的表現特徵是「私家車」,體現出來的行爲是」車主能夠自駕去作xxx事「。

當你在街上分別看到下面兩部車時,你會理所固然地認爲左邊的是「出租車」,右邊的是「私家車」。

image
你近乎條件反射地知道了這兩部車所表明的語義!你爲何可以如此快速地定義它們呢?
由於咱們對這兩部車已經有了足夠的認知,即便咱們不去觸碰它們,可是結合咱們自身的知識和經驗,它們所表明的含義已經深深地刻在咱們的心底。

從這個例子咱們能夠很容易地看出,當事物處於不一樣的環境時,它們表現的特徵和行爲是有差別的。
這也是所謂的「語義異構」,它指的是同一事物在解釋上所存在的差別,也就體現爲同一事物在不一樣領域中的理解不一樣。


另外,因爲小孩子認知上的不足,他們對這兩部車的理解和大人也會有所不一樣。

小孩:「這兩部車都能帶我去遊樂場玩」
大人:左邊那輛車能」爲市民提供有償的乘車服務「,右邊那輛車的「車主能夠自駕去作xxx事」

小孩因爲對事物的認知較爲淺薄,因此他們的主觀判斷也是較淺顯的。
大人因爲對事物已經足夠了解了,因此他們的主觀判斷時較深入的。

每一個人都是從小孩成長到大人的,人們對事物的探索和認知也會經歷這個過程。在不一樣時期,不一樣場合,人們對同一個事物的認知和理解是不一樣的。

如今大體介紹完了語義,咱們正式進入本文的示例環節。如下這4則示例代碼很是簡單,請結合語義去感覺這4種重構策略。

提高方法

當子類的方法描述了相同的行爲時,應將這樣的方法提高到基類。

下圖表示了這個重構策略(藍色表示重構前,紅色表示重構後)

image

方法提高到基類時,應該注意兩點:

1. 基類中定義的行爲實現細節,應該是全部子類共有的。
2. 子類應該具備重寫基類行爲的能力,重寫時應該是對行爲細節的附加,而不該當隨意篡改基類的行爲細節(你確實能夠這麼作,但我不建議這麼作)

示例

重構前

這段代碼定義了3個類:Vechicle(機動車),Car(汽車)和Motorcycle(摩托車)。在Car裏定義了Turn()方法,表示汽車的行駛行爲。

namespace PullUpMethod.Before
{
    public abstract class Vehicle
    {
        // other methods
    }

    public class Car : Vehicle
    {
        public void Turn(Direction direction)
        {
            // code here
        }
    }

    public class Motorcycle : Vehicle
    {
    }

    public enum Direction
    {
        Left,
        Right
    }
}

在這個場景中,Motorcycle也具備行駛行爲,若是在Motorcycle中也定義一個Turn()方法,會形成語義上的重複,因此咱們應將Car中的Turn()方法提高到基類Vehicle。

重構後

namespace PullUpMethod.After
{
    public abstract class Vehicle
    {
        public virtual void Turn(Direction direction)
        {
            // 基類行爲的實現細節
        }
    }

    public class Car : Vehicle
    {
        
    }

    public class Motorcycle : Vehicle
    {
        public override void Turn(Direction direction)
        {
            // 使用基類行爲的細節
            base.Turn(direction);
            // 附加一些子類自己的行爲細節
        }
    }

    public enum Direction
    {
        Left,
        Right
    }
}

Vehicle類的Turn()方法使用了virtual關鍵字,當基類的實現方法不能知足子類的需求時,咱們能夠在子類中override。

下降方法

在基類中的行爲僅和個別子類相關時,應將這樣的行爲下降到子類。

下圖表示了這個重構策略(藍色表示重構前,紅色表示重構後)

image

示例

重構前

這段代碼定義了3個類:Animal(動物)、Dog(狗)和Cat(貓),在Animal裏定義了Bark()方法,表示動物的吠叫行爲。

namespace PushDownMethod.Before
{
    public abstract class Animal
    {
        public void Bark()
        {
            // code to bark
        }
    }

    public class Dog : Animal
    {
    }

    public class Cat : Animal
    {
    }
}

在這個場景中,Dog可以吠叫,吠叫行爲不屬於Cat,Cat只能喵喵叫,因此應將Bark()方法下降到Dog類。

重構後

namespace PushDownMethod.After
{
    public abstract class Animal
    {
    }

    public class Dog : Animal
    {
        public void Bark()
        {
            // code to bark
        }
    }

    public class Cat : Animal
    {
    }
}

提高字段

當子類中的字段描述着相同的信息時,應將這樣的字段提高到基類。

下圖表示了這個重構策略(藍色表示重構前,紅色表示重構後)

image

在C#中,字段一般都是以private修飾的。當使用這種重構策略時,爲了讓子類可以訪問,提高到基類的字段至少應該使用protected修飾符。

示例

重構前

這段代碼定義了3個類:Account(帳戶)、CheckingAccount(活期帳戶)和SavingAccount(儲蓄帳戶),CheckingAcount和SavingAccount繼承自Account。

namespace PullUpField.Before
{
    public abstract class Account
    {
    }

    public class CheckingAccount : Account
    {
        private decimal _minimumCheckingBalance = 5m;
    }

    public class SavingAccount : Account
    {
        private decimal _minimumSavingBalance = 5m;
    }
}

在這個場景中,CheckingAccount和SavingAccount都定義了最小余額字段,雖然命名不一樣,但表示的含義是同樣的,因此應在Account中定義最小余額字段,同時使用protected修飾該字段。

重構後

namespace PullUpField.After
{
    public abstract class Account
    {
        protected decimal _minimumBalance = 5m;
    }

    public class CheckingAccount : Account
    {

    }

    public class SavingAccount : Account
    {
 
    }
}

 

下降字段

當基類中的字段僅僅用於個別子類時,應將這樣的字段下降到子類。

下圖表示了這個重構策略(藍色表示重構前,紅色表示重構後)

image

示例

重構前

這段代碼定義了3個類:Task(任務)、BugTask(缺陷任務)和FeatureTask(功能任務),基類Task定義了_resolution字段。

namespace PushDownField.Before
{
    public abstract class Task
    {
        protected string _resolution;
    }

    public class BugTask : Task
    {
    }

    public class FeatureTask : Task
    {
        
    }
}

在這個場景中,_resolution字段表示「bug的解決狀態」,這個字段和BugTask類有關,和Feature類是無關的,因此應將_resolution字段定義在BugTask類,並以private修飾。

重構後

namespace PushDownField.After
{
    public abstract class Task
    {
 
    }

    public class BugTask : Task
    {
        private string _resolution;
    }

    public class FeatureTask : Task
    {

    }
}

總結

這4種方式是較爲簡單的重構策略,也是常用的重構策略。
若是要使用好這些策略,須要咱們對類、方法和字段的語義有一個清晰地瞭解和認知。

即便再簡單的重構策略,也須要左右權衡,不然可能形成「過分重構」或「重構不當」。
若是您只是剛開始經歷重構,請不要過於擔憂這兩點,你能發現這兩個問題,說明你已經思考過了,你須要經歷這個過程纔可以有所成長。

相關文章
相關標籤/搜索