|
|
連載
.NETで始めるデザインパターン
第3回 リファクタリングにより導き出すStrategyパターン
太陽システム株式会社 中西 庸文
2005/03/19 |
|
前回で述べたように、デザインパターンはリファクタリングにより導き出すことができる。そのリファクタリング過程を明らかにすることで、そこで導き出されるデザインパターンの意味や目的がより分かりやすくなるだろう。
そこで今回からはサンプル・コードをリファクタリングし、そこからデザインパターンを導き出す方法を解説していく。もし可能なら自分で実際にコードを記述してそのリファクタリングの過程を確認しながら読み進めてほしい。そうすれば、自分の経験としてその内容をより深く理解できるはずだ。
なお本稿の開発手順は、すべてテスト駆動開発(TDD:Test-Driven Development)のスタイルで行う(テスト駆動開発については、「テスト駆動開発ツール最前線」を参照されたい)。
リファクタリング前の設計
いまから使用するサンプル・コードでは、次のような仕様のXpValuesクラスを作成する。
【XpValuesクラス】 |
XP(エクストリーム・プログラミング)の「5つの価値」の内容(コミュニケーション、シンプル、フィードバック、勇気、敬意)を保持する。
|
(1)「5つの価値」の内容をプレーン・テキスト形式で出力できる。 |
(2)「5つの価値」の内容をHTML形式で出力できる。 |
まずはこのXpValuesクラス用のテスト・コードを見ていただこう(テスト・コードの実装内容については解説しないので、詳しくはコード内のコメントを参照してほしい)。
■XpValuesTestクラスの実装内容
このテスト・コードを記述した段階で、以下のようにXpValuesクラスの構造を定義した。
●XpValuesクラスには、そのインスタンス生成を行うための静的メソッドとして次の2種類がある。
(1)CreatePlainTextFormatableXpValuesメソッド
このメソッドで生成されたXpValuesクラスのインスタンスは、プレーン・テキスト形式でXPの価値を出力する。
(2)CreateHtmlTextFormatableXpValuesメソッド
このメソッドで生成されたXpValuesクラスのインスタンスは、HTML形式でXPの価値を出力する。
テスト・コードは以下のようになっている。
using System;
using System.Collections;
using NUnit.Framework;
using DesignPatterns.Core.TemplateMethod;
namespace DesignPatterns.Tests.TemplateMethod
{
// XpValuesクラスのテスト・クラス
[TestFixture] public class XpValuesTest
{
// XpValuesクラスのインスタンスを格納するフィールド変数
private XpValues target;
// テストの初期化
[SetUp] public void Init()
{
// XpValuesオブジェクトの生成(プレーン・テキスト形式で出力できる)
target = XpValues.CreatePlainTextFormatableXpValues("XP 2nd Edition");
}
// XpValuesオブジェクトを生成するテスト
[Test] public void CreateInsatance()
{
Assert.AreEqual("XP 2nd Edition", target.Name, "名前が違います。");
Assert.AreEqual(0, target.Count, "価値の数が違います。");
}
// XPの「5つの価値」の内容を登録するテスト
[Test] public void Add()
{
target.Add("コミュニケーション");
target.Add("シンプル");
target.Add("フィードバック");
target.Add("勇気");
target.Add("敬意");
Assert.AreEqual(5, target.Count, "価値の数が違います。");
Assert.AreEqual("コミュニケーション", target[0], "価値が違います。");
Assert.AreEqual("シンプル", target[1], "価値が違います。");
Assert.AreEqual("フィードバック", target[2], "価値が違います。");
Assert.AreEqual("勇気", target[3], "価値が違います。");
Assert.AreEqual("敬意", target[4], "価値が違います。");
}
// 「5つの価値」の登録内容を走査するテスト
[Test] public void GetEnumerator()
{
target.Add("コミュニケーション");
target.Add("シンプル");
IEnumerator en = target.GetEnumerator();
Assert.IsTrue(en.MoveNext(), "次の要素は存在するはずです。");
Assert.AreEqual("コミュニケーション", en.Current, "要素が違います。");
Assert.IsTrue(en.MoveNext(), "次の要素は存在するはずです。");
Assert.AreEqual("シンプル", en.Current, "要素が違います。");
Assert.IsFalse(en.MoveNext(), "次の要素は存在しないはずです。");
}
// プレーン・テキスト形式で「5つの価値」を出力するテスト
[Test] public void PlainTextFormat()
{
target.Add("コミュニケーション");
target.Add("シンプル");
target.Add("フィードバック");
target.Add("勇気");
target.Add("敬意");
string expected =
"XP 2nd Editionの5つの価値\r\n" +
"・コミュニケーション\r\n" +
"・シンプル\r\n" +
"・フィードバック\r\n" +
"・勇気\r\n" +
"・敬意\r\n";
Assert.AreEqual(expected, target.Format(), "結果が違います。");
}
// HTML形式で「5つの価値」を出力するテスト
[Test] public void HtmlTextFormat()
{
// XpValuesオブジェクトの生成(HTML形式で出力できる)
target = XpValues.CreateHtmlTextFormatableXpValues("XP 2nd Edition");
target.Add("コミュニケーション");
target.Add("シンプル");
target.Add("フィードバック");
target.Add("勇気");
target.Add("敬意");
string expected =
"<p>XP 2nd Editionの5つの価値</p>\r\n" +
"<ul>\r\n" +
"<li>コミュニケーション\r\n" +
"<li>シンプル\r\n" +
"<li>フィードバック\r\n" +
"<li>勇気\r\n" +
"<li>敬意\r\n" +
"</ul>\r\n";
Assert.AreEqual(expected, target.Format(), "結果が違います。");
}
}
}
|
|
XpValuesクラスのテスト・コード(C#) |
|
■XpValuesクラスの初期コード(リファクタリング前)
次に、これらのテストをすべてパスするXpValuesクラスの実装コードを以下に示す。これはリファクタリング前の、取りあえずテストをパスさせるために記述してみたというレベルのコードだ。
using System;
using System.Collections;
using System.Text;
namespace DesignPatterns.Core.TemplateMethod
{
// XpValuesクラス
public class XpValues : IEnumerable
{
private string name;
// 出力形式のタイプを保持するフィールド変数
private int formatType;
private ArrayList values = new ArrayList();
// プライベート・コンストラクタ
private XpValues(string name, int formatType)
{
this.name = name;
this.formatType = formatType;
}
// 保持する「価値」をプレーン・テキスト形式で出力できるインスタンスを生成
public static XpValues CreatePlainTextFormatableXpValues(string name)
{
return new XpValues(name, 0);
}
// 保持する「価値」をHTML形式で出力できるインスタンスを生成
public static XpValues CreateHtmlTextFormatableXpValues(string name)
{
return new XpValues(name, 1);
}
// 名前を取得する
public string Name
{
get { return name; }
}
// 「価値」の数を取得する
public int Count
{
get { return values.Count; }
}
// 「価値」を取得する
public string this[int index]
{
get { return values[index].ToString(); }
}
// 「価値」を登録する
public void Add(string xpValue)
{
values.Add(xpValue);
}
// XpValues を反復処理できる列挙子を取得する
public IEnumerator GetEnumerator()
{
return values.GetEnumerator();
}
// 保持する「価値」を出力する
public string Format()
{
if (formatType == 0) // プレーン・テキスト形式
{
StringBuilder builder = new StringBuilder();
builder.Append(GetTitle() + "\r\n");
foreach (string xpValue in values)
builder.Append("・" + xpValue + "\r\n");
return builder.ToString();
}
else // HTML形式
{
StringBuilder builder = new StringBuilder();
builder.Append("<p>" + GetTitle() + "</p>\r\n");
builder.Append("<ul>\r\n");
foreach (string xpValue in values)
builder.Append("<li>" + xpValue + "\r\n");
builder.Append("</ul>\r\n");
return builder.ToString();
}
}
// タイトルを取得する
private string GetTitle()
{
return name + "の" + values.Count.ToString() + "つの価値";
}
}
}
|
|
リファクタリング前のXpValuesクラスの実装コード(C#) |
|
リファクタリングによるStrategyパターンの適用
これから実際にXpValuesクラスをリファクタリングしていくわけだが、代表的なリファクタリング・カタログには以下の2つがあるため、ここではリファクタリングの名前を次に示すような表記により区別することにする。
-
Martin Fowler氏による書籍『リファクタリング』のカタログに記載されているリファクタリングの名前は、「メソッドの移動(F)」というように(F)と表記する。
-
Joshua Kerievsky氏による書籍『Refactoring to Patterns』のカタログに記載されているリファクタリングの名前は、「Replace Conditional Logic with Strategy(K)」というように(K)と表記する。
また以降のソース・コードでは、リファクタリングにより追加および修正されたコードは青色で、リファクタリングにより削除されたコードは赤色に取り消し線で表記する。
●Strategyパターンによる条件ロジックの置き換え
まず、XpValuesクラスの実装コードで気になるのがFormatメソッドだ。このメソッドからは条件記述や重複したアルゴリズムなどの「コードの不吉な匂い」が漂っている。
リファクタリングの第1段階としては、条件記述に関する問題を解決しよう。条件記述をこのままにしておけば、新しい形式で「価値」を出力するような変更が発生した場合に、条件記述が増えてFormatメソッドはさらに複雑化することになるだろう。
そこで、異なった形式で「価値」を出力するためのアルゴリズムをクラスに分離して独立させ、さらにそれぞれのアルゴリズムをカプセル化して交換可能にできれば、それぞれのアルゴリズムは条件分岐から独立したものとなるので、このような変更に対する修正を最小限に抑えられるはずだ。
これは「Strategyパターン」の目的と一致する。書籍『オブジェクト指向における再利用のためのデザインパターン』では、Strategyパターンの目的は次のように記述されている。
アルゴリズムの集合を定義し、各アルゴリズムをカプセル化して、それらを交換可能にする。Strategyパターンを利用することで、アルゴリズムを、それを利用するクライアントからは独立に変更することができるようになる。
|
Strategyとは本来「戦略」という意味だが、ここではカプセル化された各アルゴリズムのことをStrategyと考えてよいだろう。
それでは、条件ロジックからStrategyパターンを導くためのリファクタリングの手順を紹介しよう。