菊池 Blog

移転しました 続・菊池 和彦の足跡

AILight Banner
AILight Blog

プロフィール

菊池 Blog

目次

Blog 利用状況

記事分類

過去の記事

タグ

リファクタリングパターン:型依存性の除去

直前のエントリとおんなじネタなんですが、リファクタリングパターンになるかなと書いてみます。

目的

ソースコードの汎用性を高め仕様変更によって型が変わる事に対しての耐性を高める。

初期コード

public class Product
{
    public string Name;
    public int UnitPrice;
}

public class Sales
{
    public Product Item;
    public int Amount;
    public int Price { get { return Item.UnitPrice * Amount; } }
}

解決対象となる初期コードの問題点

各エンティティのプロパティ型が固定されており、他の型を利用するのに多くの変更が必要となる。

リファクタリングの手順

ソースコード上の各プリミティブ型を使われる文脈を含めて分類し型エイリアスを行う。

個数や金額等、数値であっても意味が異なれば異なるエイリアスを割り当てる。

using TCurrency = int;
using TAmount = int;

public class Product
{
    public string Name;
    public TCurrency UnitPrice;
}

public class Sales
{
    public Product Item;
    public TAmount Amount;
    public TCurrency Price { get { return Item.UnitPrice * Amount; } }
}

各クラスに汎化用クラスを導入する。

Product を AbstractProduct に名前を変更し、AbstractProductを継承するProductを作成する。

using TCurrency = int;
using TAmount = int;

public class Product : AbstractProduct {}
public class AbstractProduct
{
    public string Name;
    public TCurrency UnitPrice;
}
public class Sales : AbstractSales {}
public class AbstractSales
{
    public Product Item;
    public TAmount Amount;
    public TCurrency Price { get { return Item.UnitPrice * Amount; } }
}

型エイリアスをのTプレフィックスをt_プレフィックスに変更、元のエイリアスをジェネリックパラメータ化する

using t_Amount=int;
using t_Currency=int;
public class Product : AbstractProduct<t_Currency> {}
public class AbstractProduct <TCurrency>
{
    public string Name;
    public TCurrency UnitPrice;
}
public class Sales : AbstractSales<t_Amount,t_Currency> {}
public class AbstractSales<TAmount,TCurrency>
{
    public Product Item;
    public TAmount Amount;
    public TCurrency Price { get { return Item.UnitPrice * Amount; } }
}

コンパイルを実行すると、演算が必要な処理がコンパイルエラーとなるので、abstract 化して実装を派生クラスに移す。(この場合 AbstractSales.Price が影響を受ける)

using t_Amount=int;
using t_Currency=int;
public class Product : AbstractProduct<t_Currency> {}
public class AbstractProduct <TCurrency>
{
    public string Name;
    public TCurrency UnitPrice;
}
public class Sales : AbstractSales<t_Amount,t_Currency> {
    public override t_Currency Price { get { return Item.UnitPrice * Amount; } }
}
public class AbstractSales<TAmount,TCurrency>
{
    public Product Item;
    public TAmount Amount;
    public abstract TCurrency Price { get; }
}

この移動で一旦はコンパイルが通るようになるはず。

移動されたコードの内容全てをメソッド抽出する。

using t_Amount=int;
using t_Currency=int;
public class Product : AbstractProduct<t_Currency> {}
public class AbstractProduct <TCurrency>
{
    public string Name;
    public TCurrency UnitPrice;
}
public class Sales : AbstractSales<t_Amount,t_Currency> {
    public override t_Currency Price { get { return GetPrice(); } }
    public t_Currency GetPrice()
    {
        return Item.UnitPrice * Amount;
    }
}
public class AbstractSales<TAmount,TCurrency>
{
    public Product Item;
    public TAmount Amount;
    public abstract TCurrency Price { get; }
}

戻り値型を格納する構造体を導入し、構造体を ref パラメータ渡しするようにする、リターン値は構造体に書き戻すようにする。

using t_Amount=int;
using t_Currency=int;
public class Product : AbstractProduct<t_Currency> {}
public class AbstractProduct <TCurrency>
{
    public string Name;
    public TCurrency UnitPrice;
}
public struct CurrencyContainer
{
    public t_Currency Value;
}
public class Sales : AbstractSales<t_Amount,t_Currency> {
    public override t_Currency Price
    {
        get {
            CurrencyContainer container; 
            GetPrice( ref container );
             return container.Value;
        }
    }
    public t_Currency GetPrice( ref CurrencyContainer container)
    {
        container.Value = Item.UnitPrice * Amount;
    }
}
public class AbstractSales<TAmount,TCurrency>
{
    public Product Item;
    public TAmount Amount;
    public abstract TCurrency Price { get; }
}

計算部の呼び出し部を既定クラスに戻し、abstract宣言を補う

using t_Amount=int;
using t_Currency=int;
public class Product : AbstractProduct<t_Currency> {}
public class AbstractProduct <TCurrency>
{
    public string Name;
    public TCurrency UnitPrice;
}
public struct CurrencyContainer
{
    public t_Currency Value;
}
public class Sales : AbstractSales<t_Amount,t_Currency> {
    public void GetPrice( ref CurrencyContainer container)
    {
        container.Value = Item.UnitPrice * Amount;
    }
}
public class AbstractSales<TAmount,TCurrency>
{
    public Product Item;
    public TAmount Amount;
    public override t_Currency Price
    {
        get {
            CurrencyContainer container; 
            GetPrice( ref container );
             return container.Value;
        }
    }
    public abstract void GetPrice( ref CurrencyContainer container);
}

計算コードを含む部分を partial 化し、計算部の派生リストを空にする。

using t_Amount=int;
using t_Currency=int;
public class Product : AbstractProduct<t_Currency> {}
public class AbstractProduct <TCurrency>
{
    public string Name;
    public TCurrency UnitPrice;
}
public struct CurrencyContainer
{
    public t_Currency Value;
}
public partial class Sales : AbstractSales<t_Amount,t_Currency> { }
public partial class Sales 
{
    public void GetPrice( ref CurrencyContainer container)
    {
        container.Value = Item.UnitPrice * Amount;
    }
}
public class AbstractSales<TAmount,TCurrency>
{
    public Product Item;
    public TAmount Amount;
    public TCurrency Price
    {
        get {
            CurrencyContainer container; 
            GetPrice( ref container );
             return container.Value;
        }
    }
    public abstract void GetPrice( ref CurrencyContainer container);
}

型エイリアスとそれに関連する宣言を別ソースに切り分ける

// 型依存しているソース
using t_Amount=int;
using t_Currency=int;
public class Product : AbstractProduct<t_Currency> {}
public struct CurrencyContainer
{
    public t_Currency Value;
}
public partial class Sales : AbstractSales<t_Amount,t_Currency> { }
// 型依存して無いソース
public class AbstractProduct <TCurrency>
{
    public string Name;
    public TCurrency UnitPrice;
}
public partial class Sales 
{
    public void GetPrice( ref CurrencyContainer container)
    {
        container.Value = Item.UnitPrice * Amount;
    }
}
public class AbstractSales<TAmount,TCurrency>
{
    public Product Item;
    public TAmount Amount;
    public override TCurrency Price
    {
        get {
            CurrencyContainer container; 
            GetPrice( ref container );
             return container.Value;
        }
    }
    public abstract void GetPrice( ref CurrencyContainer container);
}

こんな手順で普通のクラスなら何でも型依存性を除去できそうな気がするー

失敗したら教えてくださいな。

投稿日時 : 2007年12月28日 18:21


コメントを追加

タイトル
名前
URL
コメント