|
|
連載
.NETで始めるデザインパターン
第6回 リファクタリングにより導き出すCompositeパターン
太陽システム株式会社 中西 庸文
(Microsoft MVP 2005 - Solutions Architect)
2005/07/06 |
|
|
■リファクタリング5:クラスのインスタンスによる「じか書きされた親要素」の置き換え
次のステップとして、「じか書きされた親要素」を探し出し、Composite(複合要素)として振る舞うことが可能となったElementクラスのインスタンスへと置き換える。また、すでにElementクラスのインスタンスに置き換えられている子要素は、親要素であるElementクラスのインスタンスに含まれるようにする。
じか書きされた親要素は、下層から上層の順でクラスのインスタンスに置き換えていけば、リファクタリングをスムーズに行うことができる。そこでWriteTasksToメソッドのtaskエレメントから置き換えを開始する。
Elementクラスのインスタンスへと置き換えられたtaskエレメントの子要素となるactualPointエレメント、estimatedPointエレメント、contentエレメントの各インスタンスは、コレクティング・パラメータであるStringBuilderのインスタンスの代わりに引き渡されたtaskエレメントのインスタンスに、子要素として追加してやればよい。
using System;
using System.Text;
namespace DesignPatterns.Core.Composite
{
public class StoriesWriter
{
……中略……
// Elementのインスタンスによるじか書きされた親要素の置き換え
private void WriteTasksTo(StringBuilder xml, Story story)
{
foreach (Task task in story)
{
xml.Append("<task");
xml.Append(" no=\"");
xml.Append(task.No.ToString());
xml.Append("\"");
xml.Append(" storyNo=\"");
xml.Append(task.StoryNo.ToString());
xml.Append("\">");
Element taskElement = new Element("task");
taskElement.AddAttribute("no", task.No.ToString());
taskElement.AddAttribute("storyNo", task.StoryNo.ToString());
// StringBuilderの代わりにElementを引き渡す
WriteTaskActualPointTo(xml, task);
WriteTaskActualPointTo(taskElement, task);
WriteTaskEstimatedPointTo(xml, task);
WriteTaskEstimatedPointTo(taskElement, task);
WriteTaskContentTo(xml, task);
WriteTaskContentTo(taskElement, task);
// StringBuilderにはElementの文字列表現を格納する
xml.Append("</task>");
xml.Append(taskElement.ToString());
}
}
// 引き渡された親Elementに子Elementを追加する
private void WriteTaskActualPointTo(StringBuilder xml, Task task)
private void WriteTaskActualPointTo(Element taskElement, Task task)
{
Element actualPointElement = new Element("actualPoint");
actualPointElement.AddAttribute("isOver", task.IsOverPoint().ToString());
actualPointElement.Content = task.ActualPoint.ToString("##.0");
xml.Append(actualPointElement.ToString());
taskElement.Add(actualPointElement);
}
// 引き渡された親Elementに子Elementを追加する
private void WriteTaskEstimatedPointTo(StringBuilder xml, Task task)
private void WriteTaskEstimatedPointTo(Element taskElement, Task task)
{
Element estimatedPointElement = new Element("estimatedPoint");
estimatedPointElement.Content = task.EstimatedPoint.ToString("##.0");
xml.Append(estimatedPointElement.ToString());
taskElement.Add(estimatedPointElement);
}
// 引き渡された親Elementに子Elementを追加する
private void WriteTaskContentTo(StringBuilder xml, Task task)
private void WriteTaskContentTo(Element taskElement, Task task)
{
Element contentElement = new Element("content");
contentElement.Content = task.Content;
xml.Append(contentElement.ToString());
taskElement.Add(contentElement);
}
}
}
|
|
じか書きされた親要素がElementのインスタンスに置き換えられたStoriesWriterクラス(C#) |
|
■リファクタリング6:すべての「じか書きされた親要素」の置き換え
最後のステップとして、リファクタリング4とリファクタリング5を繰り返して、すべてのじか書きされた親要素を識別してクラスのインスタンスに置き換える。
このリファクタリングによってもたらされたコードを以下に示す。
using System;
using System.Text;
namespace DesignPatterns.Core.Composite
{
// StoriesWriterクラス
public class StoriesWriter
{
private Stories stories;
// コンストラクタ
public StoriesWriter(Stories stories)
{
this.stories = stories;
}
// XMLを文字列で出力する
public string GetContents()
{
StringBuilder xml = new StringBuilder();
WriteStoriesTo(xml);
return xml.ToString();
}
// すべてのストーリーを出力する
private void WriteStoriesTo(StringBuilder xml)
{
Element storiesElement = new Element("stories");
foreach (Story story in stories)
{
Element storyElement = new Element("story");
storyElement.AddAttribute("no", story.No.ToString());
storyElement.AddAttribute("priority", story.Priority.ToString());
WriteStoryContentTo(storyElement, story);
WriteTasksTo(storyElement, story);
storiesElement.Add(storyElement);
}
xml.Append(storiesElement.ToString());
}
// ストーリーの内容を出力する
private void WriteStoryContentTo(Element storyElement, Story story)
{
Element contentElement = new Element("content");
contentElement.Content = story.Content;
storyElement.Add(contentElement);
}
// すべてのタスクを出力する
private void WriteTasksTo(Element storyElement, Story story)
{
foreach (Task task in story)
{
Element taskElement = new Element("task");
taskElement.AddAttribute("no", task.No.ToString());
taskElement.AddAttribute("storyNo", task.StoryNo.ToString());
WriteTaskActualPointTo(taskElement, task);
WriteTaskEstimatedPointTo(taskElement, task);
WriteTaskContentTo(taskElement, task);
storyElement.Add(taskElement);
}
}
// タスクの実測ポイントを出力する
private void WriteTaskActualPointTo(Element taskElement, Task task)
{
Element actualPointElement = new Element("actualPoint");
actualPointElement.AddAttribute("isOver", task.IsOverPoint().ToString());
actualPointElement.Content = task.ActualPoint.ToString("##.0");
taskElement.Add(actualPointElement);
}
// タスクの予定ポイントを出力する
private void WriteTaskEstimatedPointTo(Element taskElement, Task task)
{
Element estimatedPointElement = new Element("estimatedPoint");
estimatedPointElement.Content = task.EstimatedPoint.ToString("##.0");
taskElement.Add(estimatedPointElement);
}
// タスクの内容を出力する
private void WriteTaskContentTo(Element taskElement, Task task)
{
Element contentElement = new Element("content");
contentElement.Content = task.Content;
taskElement.Add(contentElement);
}
}
}
|
|
すべてのじか書きされた親要素がElementのインスタンスに置き換えられたStoriesWriterクラス(C#) |
|
これで、すべてのじか書きされたツリー構造はCompositeパターンを形成するElementクラスのインスタンスに置き換えられた。以上ですべてのリファクタリングは終わりだ。
●リファクタリングの手順
ここまでに行ったリファクタリングは「Replace Implicit Tree with Composite(K)」(じか書きされたツリー構造のCompositeへの置き換え)と呼ばれているものだ。
その手順を簡単に振り返ってみよう。
-
オブジェクトとして表現可能な「じか書きされた子要素」の識別
テスト駆動開発のアプローチで、CompositeパターンのLeaf(単純要素)として振る舞うElementクラスを作成する。
-
クラスのインスタンスによる「じか書きされた子要素」の置き換え
じか書きされた子要素をElementクラスのインスタンスに置き換える。
-
すべての「じか書きされた子要素」の置き換えと共通インターフェイスの抽出
(今回は必要がなかったのでスキップ)
-
オブジェクトとして表現可能な「じか書きされた親要素」の識別
テスト駆動開発のアプローチで、ElementクラスがCompositeパターンのComposite(複合要素)として振る舞えるようにする。
-
クラスのインスタンスによる「じか書きされた親要素」の置き換え
じか書きされた親要素をElementクラスのインスタンスに置き換える。
-
すべての「じか書きされた親要素」の置き換え
4と5を繰り返し、すべてのじか書きされた親要素を識別してElementクラスのインスタンスに置き換える。
ツリー構造が、基本データ型(今回の例ではString型)で表現されているような場合には、このようなリファクタリングを実施する。これによってCompositeパターンが導かれ、手続き的処理がオブジェクトによって適切に構造化される。その結果として、ツリー構造が変化するような場合でも柔軟に対応できるようになる。
●まとめ
前回では「長すぎるメソッド」に対するリファクタリングからCollecting Parameterパターンを導くための実例を、今回は「基本データ型への執着」を除去するためのリファクタリングからCompositeパターンが導かれる実例を、それぞれコードを中心に解説した。
最後に補足しておくと、前回および今回のサンプル・プログラムをSystem.Xml.XmlTextWriterクラスを使用して記述した場合には、Compositeパターンを導入する必要はないだろう。
XmlTextWriterクラスには、適切な形式でXMLを出力するためのメソッドやプロパティが豊富に用意されており、「Move Accumulation to Collecting Parameter(K)」(情報蓄積ロジックのCollecting Parameterへの移行)のリファクタリングまでで十分だ。無理やりCompositeパターンを導入することは可能だが、それでは不必要な複雑性を作り込んでしまうことにほかならない。
このようにリファクタリングからデザインパターンを導く場合でも、パターン導入のトレードオフをしっかりと考慮する必要がある。しかし、このようなトレードオフはリファクタリングを終了すべき段階を見極めることで実現できるため、あらかじめパターンを導入する場合のトレードオフよりは比較的容易である。
さて次回は、本連載の最終回として、デザインパターンを実践するうえでの注意点、特にデザパタ初心者が陥りがちな「わな」について説明する予定である。デザインパターン利用者に必要な知識なのでお見逃しなく。
Insider.NET 記事ランキング
本日
月間