[.NET] Fluent Interface: 實作 Method Chaining 又不會有耦合性的作法

有用過 jQuery 的人應該會對它的方法鏈 (method chain) 印象深刻吧,一條龍的方法呼叫可以簡化很多程式碼量,雖然看起來有點不容易維護,但就不需要特別設定的程式撰寫上,它還是一個很方便的 pattern。

有用過 jQuery 的人應該會對它的方法鏈 (method chain) 印象深刻吧,一條龍的方法呼叫可以簡化很多程式碼量,雖然看起來有點不容易維護,但就不需要特別設定的程式撰寫上,它還是一個很方便的 pattern,它在 .NET 上也可以實作的出來,只是有個問題,在 JavaScript 上是弱型別 (weak type) 環境,但 .NET 是強型別 (strong type) 的環境,如果用 object 來實作,反而會有很多問題,像是這樣:

{
    // object's MathExpression 
    public class MathExpression
    {
        private int _value;

        public MathExpression(int InitialValue)
        {
            this._value = InitialValue;
        }

        public object Add(int Value)
        {
            this._value += Value;
            return this;
        }

        public object Subtract(int Value)
        {
            this._value -= Value;
            return this;
        }

        public object Multiply(int Value)
        {
            this._value *= Value;
            return this;
        }

        public object Divide(int Value)
        {
            this._value /= Value;
            return this;
        }

        public int GetValue()
        {
            return this._value;
        }
    }
}

光是呼叫上的撰寫就會累死人;

((((op.Add(100) as MathExpression)
	.Add(100) as MathExpression)
	.Divide(50) as MathExpression)
	.Subtract(100) as MathExpression)
	.Multiply(5);

當然,也可以直接回傳 MathExpression,但是這樣就會有高耦合性的問題,例如我今天如果要換成 double 型別,float 型別,long 型別或 decimal 型別時,是否就要整個重寫?這時候就有個好方法,就是使用介面來做,這樣的介面設計稱為 Fluent Interface (流利介面),由 Eric Evans 和 Martin Fowler 提出,它有三個特性:

1. 透過呼叫方法來定義物件內容。
2. 物件會自我參考 (self-referential),且新的物件內容會和最後一個物件內容等價。
3. 透過回傳一個 void 內容 (簡單的說就是 null 或不回傳最後的物件內容) 結束。

它的實作也很簡單,以上面 MathExpression 為例,我們宣告了一個 IMathExpression 介面:

{
    IMathExpression Add(int Value); // +
    IMathExpression Subtract(int Value); // -
    IMathExpression Multiply(int Value); // *
    IMathExpression Divide(int Value); // /
    int GetValue();
}

然後使用 MathExpressionImpl 來實作它:

{
    private int _value;

    public MathExpressionImpl(int InitialValue)
    {
        this._value = InitialValue;
    }

    public IMathExpression Add(int Value)
    {
        this._value += Value;
        return this;
    }

    public IMathExpression Subtract(int Value)
    {
        this._value -= Value;
        return this;
    }

    public IMathExpression Multiply(int Value)
    {
        this._value *= Value;
        return this;
    }

    public IMathExpression Divide(int Value)
    {
        this._value /= Value;
        return this;
    }

    public int GetValue()
    {
        return this._value;
    }
}

整個 Fluent Interface 的重點是,介面的成員都會回傳自己,但對物件的控制都由介面成員來做,這樣也可以保有封裝的優點,而回傳介面的方式可確保在執行完方法後取得的一定是最新的物件,而且和一開始就建置的物件是相同的。

用戶端程式寫起來就很輕鬆:

op2.Add(100).Add(100).Divide(50).Subtract(100).Multiply(5);

Fluent Interface 適合用在需要對一個相同物件進行大量設定或方法呼叫的場合,它可以簡化不少的程式碼撰寫量,不過若沒有這樣的需求,那麼也許就不太適用。

Reference:

http://en.wikipedia.org/wiki/Fluent_interface