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

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

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

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

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

 ここでもじか書きされた子要素の場合と同様に、テスト駆動開発のアプローチを取ることにする。

 XML文書のエレメントは、子エレメントを自分自身に含むことができる。そこで、Elementクラスが自分自身に子エレメントを含められるようにするために、次のようなテスト・コードを追加した。

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

namespace DesignPatterns.Tests.Composite
{
  [TestFixture] public class ElementTest
  {
    ……中略……

    [Test]
    public void 子要素を1つ含むコンポジット・エレメントを出力する()
    {
      target = new Element("task");
      target.Add(new Element("actualPoint"));

      string expected =
        "<task>" +
          "<actualPoint>" +

          "</actualPoint>" +
        "</task>";

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

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

(1)Addメソッドで子エレメントのインスタンスを追加できる
(2)ToStringメソッドは子エレメントの内容も出力する

 このテストをパスさせるために新たな機能を追加したElementクラスの実装コードを以下に示す。

using System;
using System.Text;
using System.Collections;

namespace DesignPatterns.Core.Composite
{
  public class Element : IEnumerable
  {
    ……中略……

    // 子要素を格納するArrayList
    private ArrayList children = new ArrayList();

    ……中略……


    // 子要素を追加する
    public void Add(Element child)
    {
      children.Add(child);
    }

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

      foreach (Element child in children)
        result += child.ToString();

      result += content;
      result += "</" + name + ">";

      return result;
    }

    // Elementを反復処理できる列挙子を取得する
    public IEnumerator GetEnumerator()
    {
      return children.GetEnumerator();
    }
  }
}
Composite(複合要素)としての振る舞いが追加されたElementクラス(C#)

 ここでコンパイルしてテストを実行してみよう。正常にコンパイルが完了し、テストも通るはずだ。これでElementクラスがComposite(複合要素)として振る舞えることを確認できた。

 しかし、実際のXML文書のエレメントは、子エレメントを含んだ親エレメントも自分自身に含むことができる。そこでElementクラスがComposite(複合要素)として確実に振る舞えることを確認するために、さらに以下のようなテストを追加した。

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

namespace DesignPatterns.Tests.Composite
{
  [TestFixture] public class ElementTest
  {
    ……中略……

    [Test]
    public void 子要素と孫要素を含むコンポジット・エレメントを出力する()
    {
      target = new Element("stories");
      Element storyElement = new Element("story");
      Element taskElement = new Element("task");

      target.Add(storyElement);
      storyElement.Add(taskElement);

      string expected =
        "<stories>" +
          "<story>" +
            "<task>" +
            "</task>" +
          "</story>" +
        "</stories>";

      Assert.AreEqual(expected, target.ToString(), "結果が違います。");
    }
  }
}
さらにテスト・メソッドが追加されたElemenクラスのテスト・クラス(C#)

 再度、コンパイルしてテストを実行してみよう。これも正常に実行できるはずだ。これでElementクラスがComposite(複合要素)として振る舞えることが確証できた。

 この段階でElementクラスは、Compositeパターンを構成するComponent、Leaf、Compositeのすべての役割を果たすことができるクラスとなった。

Compositeパターンを形成するElementクラスのクラス図

 GoFのCompositeパターンの構造をご存じの読者の中には、その構造が今回のCompositeパターンの構造とあまりにも異なるために、これは本当にCompositeパターンなのかと思われた方もいることだろう。

 参考までに、GoFのCompositeパターンの構造は以下のようになっている。

GoFのCompositeパターンの構造

 今回のCompositeパターンの実装は、コーディングの段階で必要なことだけを行った結果としての必要最小限のものである。必要最小限のパターンの実装は、最初から設計を行わずにコーディングの過程で設計を進化させていく「進化的設計」のプラクティスの一部だ。

 最初からGoFのパターンの構造をそのまま当てはめて設計を行えば、不必要な継承などの複雑性も導入してしまう場合がある。これは多くの場合GoFのパターンの構造が、現在のコンテキストに適合していないことが原因だ。GoFのパターンの構造は、あくまで例であり参考程度にとどめておくべきである。

 自分たちのコンテキストにあったパターンを導くためには、あらかじめパターンの導入が考慮されたような計画的設計よりも、進化的設計のプラクティスの方が、よりうまく行くことが多い。

 次のステップとして、「じか書きされた親要素」を探し出し、Composite(複合要素)として振る舞うことが可能となった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 記事ランキング

本日 月間