連載
.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で始めるデザインパターン」


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 記事ランキング

本日 月間