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

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

太陽システム株式会社 中西 庸文
Microsoft MVP 2005 - Solutions Architect)
2005/07/06
Page1 Page2 Page3


Back Issue
1
.NET開発におけるデザインパターンの有用性
2
うまくデザインパターンを使うための心得
3
リファクタリングにより導き出すStrategyパターン
4
リファクタリングにより導き出すTemplate Methodパターン
5
Compositeパターンを導き出すための準備
6
リファクタリングにより導き出すCompositeパターン
7
デザインパターンの落とし穴

 前回は、プロジェクトの進ちょくを管理するトラッキング・システムの一部のコードを示し、そのコードに対してCompositeパターンを導くための準備段階のリファクタリングを行った。引き続き、今回はさらに本格的なリファクタリングを施していこう。

■リファクタリング1:オブジェクトとして表現可能な「じか書きされた子要素」の識別

 今回まず着目するのは、前回のリストで示した、次のようなコードで記述している、じか書きされたツリー構造の「じか書きされた子要素」である。

xml.Append("<task");
xml.Append(" no=\"");
xml.Append(task.No.ToString());
xml.Append("\"");
xml.Append(" storyNo=\"");
xml.Append(task.StoryNo.ToString());
xml.Append("\">");

 これを、オブジェクトによって構造化されたツリー構造へ置き換えるためには、段階を踏んでリファクタリングを行っていく必要がある。

 最初のステップとして、このじか書きされたツリー構造の「じか書きされた子要素」をオブジェクトとして識別することから始めよう。ここで、識別されたオブジェクトはCompositeパターンのLeaf(単純要素)として振る舞うことになる。

 XML文書の各エレメントは、名前と値を持ち、属性を指定できることが分かっている。そこでまずは、各エレメントを表現するElementクラスを想定し、それをテストする次のようなシンプルなテスト・コードを作成してみた。これはテスト駆動開発のアプローチであり、ここではいつものように、新しく追加するクラスが必要とする機能の設計をテスト・ファーストで行っていく。

 なお本稿のコードは、前回と同様に、新しく追加した部分は青色、削除した部分は赤色で示している。

using System;
using NUnit.Framework;
using DesignPatterns.Core.Composite;

namespace DesignPatterns.Tests.Composite
{
  // Elementのテスト・クラス
  [TestFixture] public class ElementTest
  {
    private Element target;

    [Test] public void 属性が1つで値が1つのエレメントを出力する()
    {
      target = new Element("actualPoint");
      target.AddAttribute("isOver", "True");
      target.Content = "2.0";

      string expected = "<actualPoint isOver=\"True\">2.0</actualPoint>";

      Assert.AreEqual(expected, target.ToString(), "結果が違います。");
    }
  }
}
新しく作成されたElementクラスのテスト・クラス(C#)

 そして、このテスト・コードを記述した段階で、ElementクラスがLeaf(単純要素)として振る舞えるように、次のような設計を行った。

(1)コンストラクタで名前を指定する
(2)AddAttributeメソッドで属性を追加できる
(3)Contentプロパティで値を設定できる
(4)ToStringメソッドで内容を出力できる

 上記のテスト・コードをパスさせるためのElementクラスの実装コードはこのようになる。

using System;
using System.Text;

namespace DesignPatterns.Core.Composite
{
  // Elementクラス
  public class Element
  {
    private string name = string.Empty;
    private string content= string.Empty;
    private StringBuilder attributes = new StringBuilder();

    // コンストラクタ
    public Element(string name)
    {
      this.name = name;
    }

    // 属性を追加する
    public void AddAttribute(string attribute, string attributeValue)
    {
      attributes.Append(" ");
      attributes.Append(attribute);
      attributes.Append("=\"");
      attributes.Append(attributeValue);
      attributes.Append("\"");
    }

    // 内容

    public string Content
    {
      set { content = value; }
    }

    // Elementの状態を表す文字列を取得する
    public override string ToString()
    {
      string result = "<" + name + attributes.ToString() + ">";
      result += content;
      result += "</" + name + ">";

      return result;
    }
  }
}
新しく作成されたElementクラス(C#)

 ここでコンパイルしてテストを実行してみよう。正常にコンパイルが完了し、テストも通るはずだ。

 これでElementクラスがLeaf(単純要素)として振る舞えることを確認できた。

■リファクタリング2:クラスのインスタンスによる「じか書きされた子要素」の置き換え

 次のステップとして、「じか書きされた子要素」を探し出し、新しく作成したElementクラスのインスタンスへと置き換えよう。

 前回のリストのStoriesWriterクラスには、以下のようなじか書きされた子要素が存在している。

  • WriteStoryContentToメソッド内のcontentエレメント
  • WriteTaskActualPointToメソッド内のactualPointエレメント
  • WriteTaskEstimatedPointToメソッド内のestimatedPointエレメント
  • WriteTaskContentToメソッド内のcontentエレメント

 そこで、これらのじか書きされた子要素をそれぞれElementクラスのインスタンスに置き換え、情報を蓄積するためのコレクティング・パラメータ(StringBuilder型の変数xml)にはElementクラスのToStringメソッドの戻り値を格納するように変更する。

 子要素のインスタンスへの置き換えごとにコンパイルしてテストを実行する。どの段階でも正常にコンパイルが完了し、テストも通るはずだ。

using System;
using System.Text;

namespace DesignPatterns.Core.Composite
{
  public class StoriesWriter
  {
    ……中略……

    // Elementのインスタンスによるじか書きされた子要素の置き換え
    private void WriteStoryContentTo(StringBuilder xml, Story story)
    {
      xml.Append("<content>");
      xml.Append(story.Content);
      xml.Append("</content>");
      Element contentElement = new Element("content");
      contentElement.Content = story.Content;

      xml.Append(contentElement.ToString());
    }

    ……中略……

    // Elementのインスタンスによるじか書きされた子要素の置き換え
    private void WriteTaskActualPointTo(StringBuilder xml, Task task)
    {
      xml.Append("<actualPoint");
      xml.Append(" isOver=\"");
      xml.Append(task.IsOverPoint().ToString());
      xml.Append("\">");
      xml.Append(task.ActualPoint.ToString("##.0"));
      xml.Append("</actualPoint>");
      Element actualPointElement = new Element("actualPoint");
      actualPointElement.AddAttribute("isOver", task.IsOverPoint().ToString());
      actualPointElement.Content = task.ActualPoint.ToString("##.0");

      xml.Append(actualPointElement.ToString());
    }

    // Elementのインスタンスによるじか書きされた子要素の置き換え
    private void WriteTaskEstimatedPointTo(StringBuilder xml, Task task)
    {
      xml.Append("<estimatedPoint>");
      xml.Append(task.EstimatedPoint.ToString("##.0"));
      xml.Append("</estimatedPoint>");
      Element estimatedPointElement = new Element("estimatedPoint");
      estimatedPointElement.Content = task.EstimatedPoint.ToString("##.0");

      xml.Append(estimatedPointElement.ToString());
    }

    // Elementのインスタンスによるじか書きされた子要素の置き換え
    private void WriteTaskContentTo(StringBuilder xml, Task task)
    {
      xml.Append("<content>");
      xml.Append(task.Content);
      xml.Append("</content>");
      Element contentElement = new Element("content");
      contentElement.Content = task.Content;

      xml.Append(contentElement.ToString());
    }
  }
}
じか書きされた子要素がElementクラスのインスタンスに置き換えられたStoriesWriterクラス(C#)

■リファクタリング3:すべての「じか書きされた子要素」の置き換えと共通インターフェイスの抽出

 CompositeパターンではLeaf(単純要素)とComposite(複合要素)をポリモーフィズムによって同一視するため、要素間で共通のインターフェイスを実装していることを保証する必要がある。つまり、すべてのLeaf(単純要素)においても共通のインターフェイスを実装していることを保証する必要がある。

 そこで通常は、リファクタリング1とリファクタリング2を繰り返して、すべてのじか書きされた子要素を識別してクラスのインスタンスに置き換えた後に「インターフェイスの抽出(F)」もしくは「スーパー・クラスの抽出(F)」を行い、要素間で共通のインターフェイスを抽出する。

 しかし、今回のリファクタリングでは、XML文書のツリー構造におけるすべてのLeaf(単純要素)はすでにElementクラスによってモデル化されており、インターフェイスが共通であることも保証されているので、わざわざインターフェイスを抽出する必要がない。

 つまり、ここでは実際のリファクタリング作業は必要ない。従って、次のステップへと進むことにする。


 INDEX
  .NETで始めるデザインパターン
  第6回 リファクタリングにより導き出すCompositeパターン
  1.Compositeパターンを導き出すリファクタリング
    2.GoFのCompositeパターンの構造との比較
    3.Compositeパターンの導出手順とまとめ
 
インデックス・ページヘ  「.NETで始めるデザインパターン」

TechTargetジャパン

Insider.NET フォーラム 新着記事
  • Kinectが切り開く“夢の近未来” (2012/2/2)
     日本を含めた世界中でKinect for Windowsセンサー商用版とSDK正式版がリリース。未来のコンピューティングはどう変化するのか?
  • 3つの視点でネイティブと.NETの適材適所を考察 (2012/1/31)
     アプリ開発は「ネイティブ」と「.NET」、どちらが最良? その問いには「適材適所」と答えるしかない。では、“適所”は一体どこかを考察する
  • SQL Azure Data Sync入門 (2012/1/30)
     SQL Azure/SQL Serverデータベース間のデータ同期を簡単に実現するサービスとは? その仕組みや使用手順を解説
  • Windows Phoneアプリ市場の現状を分析する (2012/1/27)
     Windows Phone のアプリ・ストアに日々登録されている多種多様なアプリ。カテゴリ別のアプリ数は? 市場の現状を明らかにする

@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

RSSフィード

キャリアアップ

- PR -
@IT Sepcial

イベントカレンダー

PickUpイベント

- PR -
もっと見る
- PR -

お勧め求人情報

ホワイトペーパーTechTargetジャパン

@IT Sepcial
ソリューションFLASH