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

第5回 Compositeパターンを導き出すための準備

―― リファクタリングにより導き出すCollecting Parameterパターン ――

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


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

 前回までは、「コードの不吉な匂い」である「重複したコード」を除去するためのリファクタリングからデザインパターンを導き出す方法をいくつか紹介した。

 今回と次回は、「基本データ型への執着」を除去するためのリファクタリングから、デザインパターンを導き出す方法を紹介する。

 基本データ型(C#ではint型やstring型など)への執着とは、いい換えれば「手続き的処理への執着」である。クラスを自分で定義して使用するよりも、言語が提供する基本データ型のみを使用して処理を記述してしまうような傾向がある場合だ。オブジェクト指向を始めたばかりのころは、筆者もこの傾向にあった。もし、長いメソッドや巨大なクラスばかりを作成しているようであれば注意すべきである。関連するデータとそれを操作する手続きをひとまとめとして適切に構造化するように心掛けたい。

 なお本稿の開発手順は、すべてテスト駆動開発(TDD:Test-Driven Development)のスタイルで行う(テスト駆動開発については、「テスト駆動開発ツール最前線」を参照されたい)。本稿のサンプル・コードは、追加したコードは青色、削除したコードは赤色で表現している。

リファクタリング前の設計

 今回と次回で使用するサンプル・コードは、プロジェクトの進ちょくを管理する「トラッキング・システム」の一部である。このシステムには、「ユーザー・ストーリー」と「プログラマ・タスク」が使用される。以降、それぞれ「ストーリー」、「タスク」と記述する。

 まず、ストーリーとタスクの関係を簡単に説明する。

 ストーリーには顧客の要求が記述され、一意の識別番号と優先順位が設定される。ストーリーは開発者によって複数のタスクに分割される。よって1つのストーリーには任意の数のタスクが含まれることになる。

 タスクにはストーリーを実現するために必要なプログラマの作業内容が記述され、一意の識別番号と対象のストーリー番号が設定される。プログラマはタスクを実装するための工数を見積もった予定ポイントを設定し、実装が完了すれば実測ポイントを設定する。

 次にトラッキング・システムを構成するクラスについて説明する。

 タスク、ストーリー、ストーリーの集合はそれぞれTaskクラス、Storyクラス、Storiesクラスという「ドメイン・クラス」(=問題領域を表すクラス)として実装され、StoriesWriterクラスはこれらのドメイン・クラスの情報をXML文書として出力する。

 StoriesWriterクラスによって出力されるXML文書は、以下のようなツリー構造を形成する。

StoriesWriterクラスによって出力されるXML文書のツリー構造

 それでは実際にコードを見ていただこう。

 今回と次回で行うリファクタリングでは、ドメイン・クラス(Taskクラス、Storyクラス、Storiesクラス)に対しての変更は行わず、StoriesWriterクラスに対してのみ変更を行う。

 ドメイン・クラスのテスト・コードと実装コードについては、C#の場合はこちらを、VB.NETの場合はこちらを参照していただきたい。

■StoriesWriterクラスのテスト・コード

 さて、StoriesWriterクラスに対するテスト・コードを記述した段階で、StoriesWriterクラスを次のように設計した。

StoriesWriterクラスのGetContentsメソッドは、コンストラクタのパラメータとして引き渡されたStoriesオブジェクトの内容をXML形式のテキストとして出力する。

 このテスト・コードは以下のようになっている。

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

namespace DesignPatterns.Tests.Composite
{
  // StoriesWriterのテスト・クラス

  [TestFixture] public class StoriesWriterTest
  {
    private StoriesWriter target;

    [Test] public void シンプルなXMLを出力する()
    {
      target = new StoriesWriter(CreateSimpleStories());

      string expected =
        "<stories>" +
          "<story no=\"1\" priority=\"5\">" +
            "<content>ストーリーをXMLで出力する。</content>" +
            "<task no=\"1\" storyNo=\"1\">" +
              "<actualPoint isOver=\"True\">2.0</actualPoint>" +
              "<estimatedPoint>1.5</estimatedPoint>" +
              "<content>StoryWriterを作成する。</content>" +
            "</task>" +
          "</story>" +
        "</stories>";

      Assert.AreEqual(expected, target.GetContents(),
        "出力されたXMLが違います。");
    }

    // シンプルなストーリーの集合を生成する
    private Stories CreateSimpleStories()
    {
      Stories stories = new Stories();

      Story story = new Story(1);
      story.Priority = 5;
      story.Content = "ストーリーをXMLで出力する。";

      Task task = new Task(1);
      task.ActualPoint = 2.0f;
      task.EstimatedPoint = 1.5f;
      task.Content = "StoryWriterを作成する。";

      story.Add(task);
      stories.Add(story);

      return stories;
    }
  }
}
StoriesWriterクラスのテスト・コード(C#)

■StoriesWriterクラスの初期コード(リファクタリング前)

 続いて、テストをパスしたStoriesWriterクラスの実装コードを以下に示す。

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();
      xml.Append("<stories>");

      foreach (Story story in stories)
      {
        xml.Append("<story");
        xml.Append(" no=\"");
        xml.Append(story.No.ToString());
        xml.Append("\"");
        xml.Append(" priority=\"");
        xml.Append(story.Priority.ToString());
        xml.Append("\">");

        xml.Append("<content>");
        xml.Append(story.Content);
        xml.Append("</content>");

        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("\">");

          xml.Append("<actualPoint");
          xml.Append(" isOver=\"");
          xml.Append(task.IsOverPoint().ToString());
          xml.Append("\">");
          xml.Append(task.ActualPoint.ToString("##.0"));
          xml.Append("</actualPoint>");

          xml.Append("<estimatedPoint>");
          xml.Append(task.EstimatedPoint.ToString("##.0"));
          xml.Append("</estimatedPoint>");

          xml.Append("<content>");
          xml.Append(task.Content);
          xml.Append("</content>");

          xml.Append("</task>");
        }

        xml.Append("</story>");
      }

      xml.Append("</stories>");

      return xml.ToString();
    }
  }
}
リファクタリング前のStoriesWriterクラスの実装コード(C#)

■日本語テスト・メソッド名

 ところで、今回のテスト・コードは、前回までのテスト・コードとは異なり、テスト・メソッドが日本語で記述されていることに気付いていただけただろうか。

 日本語テスト・メソッド名をうまく活用すれば、テスト対象のクラスがどのようなテスト仕様に従って実装されているかが一目瞭然(りょうぜん)となる。これは検証可能なドキュメントとしてのテスト・コードの資産的価値の向上につながる。

 以下は、NUnit.GUIで上記のテスト・コードを実行した結果だ。ツリー(画面左側)上部の日本語テスト・メソッド名と下部の英語テスト・メソッド名を比較してみれば、その効果を実感していただけるだろう。

NUnit.GUIのユニット・テスト実行結果

 以上の準備が整ったところで、リファクタリングに進もう。


 INDEX
  .NETで始めるデザインパターン
  第5回 Compositeパターンを導き出すための準備
  1.リファクタリング前の設計
    2.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 記事ランキング

本日 月間