連載
.NETで始めるデザインパターン

第3回 リファクタリングにより導き出すStrategyパターン

太陽システム株式会社 中西 庸文
2005/03/19
Page1 Page2 Page3

■リファクタリング4:「ポリモーフィズムによる条件記述の置き換え(F)」によるStrategy階層の構築

 条件記述に関する問題を解決するための最後のリファクタリングを行うことにしよう。これまでのリファクタリングはすべて、このリファクタリングを行うための準備と言っても過言ではない。

 ここでは、条件記述を無くすために「ポリモーフィズムによる条件記述の置き換え(F)」を行う(なおポリモーフィズムとは、「派生クラスのインスタンスのオーバーライド・メソッドを、基本クラスの型を通じて呼び出すことで、処理内容を切り替える機能」のことだ。詳しくは「オブジェクト指向プログラミング超入門 第5回 継承を使わないとしても知っておくべきこと」を参照されたい)。

 まずはTextFormatterクラスのFormatメソッドを仮想メソッドにして派生クラスでオーバーライドできるようにする。

using System;
using System.Text;

namespace DesignPatterns.Core.TemplateMethod
{
  public class TextFormatter
  {
    // 仮想メソッドにする
    public virtual string Format(XpValues xpValues)
    {
      if (xpValues.FormatType == 0)
      {
        StringBuilder builder = new StringBuilder();
        builder.Append(GetTitleFor(xpValues) + "\r\n");
        foreach (string xpValue in xpValues)
          builder.Append("・" + xpValue + "\r\n");
        return builder.ToString();
      }
      else
      {
        StringBuilder builder = new StringBuilder();
        builder.Append("<p>" + GetTitleFor(xpValues) + "</p>\r\n");
        builder.Append("<ul>\r\n");
        foreach (string xpValue in xpValues)
          builder.Append("<li>" + xpValue + "\r\n");
        builder.Append("</ul>\r\n");
        return builder.ToString();
      }
    }
    ……中略……
  }
}
派生クラスでオーバーライドできるようにしたTextFormatterクラスのFormatメソッド(C#)

■リファクタリング4の途中過程(プレーン・テキスト形式で「価値」の出力を行うStrategyの作成)

 さらにTextFormatterクラスの派生クラスであるPlainTextFormatterクラスを作成して、基本クラスであるFormatメソッドをオーバーライドする。

using System;

namespace DesignPatterns.Core.TemplateMethod
{
  // プレーン・テキスト形式で「価値」の出力を行うStrategy
  public class PlainTextFormatter : TextFormatter
  {
    public override string Format(XpValues xpValues)
    {
      return null;
    }
  }
}
新しく作成したPlainTextFormatterクラス(C#)

 ここでもコンパイルとテストを実行して、きちんとテストがパスするかを確認しよう。

 次にXpValuesクラスのCreatePlainTextFormatabletXpValuesメソッドでは、TextFormatterクラスではなく、PlainTextFormatterクラスのインスタンスを生成するように変更する。ここで、リファクタリング3で行った「Extract Parameter(K)」(パラメータの抽出)が活きてくる。

using System;
using System.Collections;

namespace DesignPatterns.Core.TemplateMethod
{
  public class XpValues : IEnumerable
  {
    ……中略……

    public static XpValues CreatePlainTextFormatableXpValues(string name)
    {
      // プレーン・テキスト形式で「価値」の出力を行うStrategyの生成
      return new XpValues(name, 0, new PlainTextFormatter());
    }
    ……中略……
  }
}
PlainTextFormatterオブジェクトを生成するようになったXpValuesクラスのCreatePlainTextFormatabletXpValuesメソッド(C#)

 さらにTextFormatterクラスのFormatメソッドにあった、プレーン・テキスト出力部分のロジックを派生クラス(PlainTextFormatterクラス)のFormatメソッドへ移動する。

using System;
using System.Text;

namespace DesignPatterns.Core.TemplateMethod
{
  public class PlainTextFormatter : TextFormatter
  {
    public override string Format(XpValues xpValues)
    {
      StringBuilder builder = new StringBuilder();
      builder.Append(GetTitleFor(xpValues) + "\r\n");
      foreach (string xpValue in xpValues)
        builder.Append("・" + xpValue + "\r\n");
      return builder.ToString();
    }
  }
}
基本クラスのロジックが移動されたPlainTextFormatterクラスのFormatメソッド(C#)

 派生クラスへ移動されたロジックは基本クラスのGetTitleForメソッドを呼び出すので、GetTitleForメソッドのアクセス修飾子をprotectedに変更する。

 そして、ロジック移動後のFormatメソッドの条件分岐内では例外をスローするようにする。これは派生クラスでオーバーライドされているFormatメソッドが確実に呼び出されているかどうかを確認するためだ。

 具体的なコードは次のとおりだ。

using System;
using System.Text;

namespace DesignPatterns.Core.TemplateMethod
{
  public class TextFormatter
  {
    public virtual string Format(XpValues xpValues)
    {
      if (xpValues.FormatType == 0)
      {
        // 例外をスロー
        throw new Exception();
      }
      else
      {
        StringBuilder builder = new StringBuilder();
        builder.Append("<p>" + GetTitleFor(xpValues) + "</p>\r\n");
        builder.Append("<ul>\r\n");
        foreach (string xpValue in xpValues)
          builder.Append("<li>" + xpValue + "\r\n");
        builder.Append("</ul>\r\n");
        return builder.ToString();
      }
    }

    // アクセス修飾子を変更
    protected string GetTitleFor(XpValues xpValues)
    {
      return xpValues.Name + "の" + xpValues.Count.ToString() + "つの価値";
    }
  }
}
派生クラスへプレーン・テキスト出力のためのロジックが移動された後のTextFormatterクラス(C#)

 コンパイルしてテストを実行すると、正常に実行されるはずだ。

■リファクタリング4の途中過程(HTML形式で「価値」の出力を行うStrategyの作成)

 次にHtmlTextFormatterクラスを作成し、PlainTextFormatterクラスと同様の変更を行う。

 具体的には、まずは下に示すHtmlTextFormatterクラスを作成しよう。なお、このクラスのFormatメソッドには、すでに基本クラス(TextFormatterクラス)からHTMLテキスト出力のロジックを移動している。

using System;
using System.Text;

namespace DesignPatterns.Core.TemplateMethod
{
  // HTML形式で「価値」の出力を行うStrategy
  public class HtmlTextFormatter : TextFormatter
  {
    public override string Format(XpValues xpValues)
    {
      StringBuilder builder = new StringBuilder();
      builder.Append("<p>" + GetTitleFor(xpValues) + "</p>\r\n");
      builder.Append("<ul>\r\n");
      foreach (string xpValue in xpValues)
        builder.Append("<li>" + xpValue + "\r\n");
      builder.Append("</ul>\r\n");
      return builder.ToString();
    }
  }
}
新しく作成したHtmlTextFormatterクラス(C#)

 さらにXpValuesクラスのCreateHtmlTextFormatableXpValuesメソッドで先ほど作成したHtmlTextFormatterクラスのインスタンスを生成するように変更する。

using System;
using System.Collections;

namespace DesignPatterns.Core.TemplateMethod
{
  public class XpValues : IEnumerable
  {
    ……中略……

    public static XpValues CreateHtmlTextFormatableXpValues(string name)
    {
      // HTML形式で「価値」の出力を行うStrategyの生成
      return new XpValues(name, 0, new HtmlTextFormatter());
    }
    ……中略……
  }
}
HtmlTextFormatterオブジェクトを生成するようになったXpValuesクラスのCreateHtmlTextFormatableXpValuesメソッド(C#)

 最後に、ロジック移動後のTextFormatterクラスのFormatメソッドは例外を投げるように変更しておこう。GetTitleForメソッドのアクセス修飾子は先ほど変更したので、今回は変更する必要はない。

using System;
using System.Text;

namespace DesignPatterns.Core.TemplateMethod
{
  public class TextFormatter
  {
    public virtual string Format(XpValues xpValues)
    {
      if (xpValues.FormatType == 0)
      {
        throw new Exception();
      }
      else
      {
        // 例外をスロー
        throw new Exception();
      }
    }
    ……中略……
  }
}
派生クラスへHTMLテキスト出力のロジックが移動された後のTextFormatterクラス(C#)

 以上のコードを再度コンパイルしてテストしてみよう。これも正常にコンパイルが終了し、テストも正しくパスするだろう。

■リファクタリング4の途中過程(Strategy 階層のトップに位置するクラスを抽象クラスにする)

 ここまでくればTextFormatterクラスのFormatメソッド内の条件分岐は不要となる。またFormatメソッドは、派生クラス側ですべてオーバーライドされているので、TextFormatterクラスを抽象クラスに変え、Formatメソッドを抽象メソッドに変更する。

using System;
using System.Text;

namespace DesignPatterns.Core.TemplateMethod
{
  // 抽象クラスにする
  public abstract class TextFormatter
  {
    // 抽象メソッドにする
    public abstract string Format(XpValues xpValues);
    ……中略……
  }
}
抽象クラスに変更したTextFormatter クラスと抽象メソッドに変更したFormatメソッド(C#)

 コンパイルしてテストを実行する。問題なく実行できるはずだ。

■リファクタリング4の途中過程(出力形式のタイプの削除)

 ここまでのリファクタリングの結果として、出力形式のタイプを保持するXpValuesクラスのフィールド変数formatTypeとFormatTypeプロパティは不要となるので削除する。

 併せてプライベート・コンストラクタ、CreatePlainTextFormatableXpValuesメソッド、CreateHtmlTextFormatableXpValuesメソッドも変更する。

using System;
using System.Collections;

namespace DesignPatterns.Core.TemplateMethod
{
  public class XpValues : IEnumerable
  {
    private string name;
    // 出力形式のタイプを保持するフィールド変数の削除
    private int formatType;
    private ArrayList values = new ArrayList();
    private TextFormatter formatter;

    // プライベート・コンストラクタのパラメータ削除
    private XpValues(string name, int formatType, TextFormatter formatter)
    {
      this.name = name;
      this.formatType = formatType;
      this.formatter = formatter;
    }

    public static XpValues CreatePlainTextFormatableXpValues(string name)
    {
      // パラメータの削除
      return new XpValues(name, 0, new PlainTextFormatter());
    }

    public static XpValues CreateHtmlTextFormatableXpValues(string name)
    {
      // パラメータの削除
      return new XpValues(name, 1, new HtmlTextFormatter());
    }
    ……中略……

    // プロパティの削除
    public int FormatType
    {
      get { return formatType; }
    }
    ……中略……
  }
}
XpValuesクラスにおける不要となったFormatTypeプロパティの削除とそれに伴う変更(C#)

 最後にもう一度プログラムをコンパイルし、テスト・コードをパスするか確認しておこう。

 これで「ポリモーフィズムによる条件記述の置き換え(F)」が完了した。

●リファクタリングの結果

 ここまでのリファクタリングによって作成および改善されたクラス構造を以下の図に示す。

リファクタリングの結果として導かれたStrategyパターンのクラス図

 ここまでに行ったリファクタリングは「Replace Conditional Logic with Strategy(K)」(Strategyパターンによる条件ロジックの置き換え)と呼ばれているものだ。

 その手順を簡単に振り返ってみよう。

1. Strategy 階層のトップに位置するクラス(TextFormatterクラス)の作成

2. 「メソッドの移動(F)」によるアルゴリズムの集合の分離
 (XpValuesクラスのFromatメソッドのプレーン・テキスト形式の出力とHTML形式の出力のアルゴリズムをTextFormatterクラスへ移動)

3. 「Extract Parameter(K)」(パラメータの抽出)による各Strategyのインスタンス切り替えの準備
 (XpValuesクラスのFormatメソッドで行っていたTextFormatterクラスのインスタンス生成をそのパラメータとして指定するようにメソッド・シグネチャを変更。TextFormatterクラスのインスタンス生成はXpValuesクラスのCreatePlainTextFormatXpValuesメソッドとCreateHtmlTextFormatXpValuesメソッドへ移動)

4. 「ポリモーフィズムによる条件記述の置き換え(F)」によるStrategy階層の構築
 (PlainTextFormatterクラスとHtmlTextFormatterクラスを作成し、XpValuesクラスのFromatメソッドのプレーン・テキスト形式の出力のアルゴリズムはPlainTextFormatterクラスへ、HTML形式の出力のアルゴリズムはHtmlTextFormatterクラスへ移動して、TextFormatterクラスを両クラスの抽象クラスに変更する。これによりそれぞれのアルゴリズムがカプセル化されて交換可能となり、ポリモーフィズムの機能によってそれぞれのアルゴリズムを使い分けられるようになる)

 メソッド内に条件記述があり、その分岐によって異なる振る舞いをするアルゴリズムが定義されているような場合には、このようなリファクタリングを実施する。これによってStrategyパターンが適用され、異なるアルゴリズムをそれぞれカプセル化して交換可能にすることができるのだ。

 実はここまでのリファクタリングは、これから行おうとしているリファクタリングのための布石にすぎない。次回は引き続きリファクタリングを行って、さらなるパターンを導いていくことにしよう。End of Article


 INDEX
  .NETで始めるデザインパターン
  第3回 リファクタリングにより導き出すStrategyパターン
    1.リファクタリング前の設計
    2.リファクタリングによるStrategyパターンの適用
  3.ポリモーフィズムによる条件記述の置き換え
 
インデックス・ページヘ  「.NETで始めるデザインパターン」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間