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

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

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

■リファクタリング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への置き換え)と呼ばれているものだ。

 その手順を簡単に振り返ってみよう。

  1. オブジェクトとして表現可能な「じか書きされた子要素」の識別
     テスト駆動開発のアプローチで、CompositeパターンのLeaf(単純要素)として振る舞うElementクラスを作成する。

  2. クラスのインスタンスによる「じか書きされた子要素」の置き換え
     じか書きされた子要素をElementクラスのインスタンスに置き換える。

  3. すべての「じか書きされた子要素」の置き換えと共通インターフェイスの抽出
     (今回は必要がなかったのでスキップ)

  4. オブジェクトとして表現可能な「じか書きされた親要素」の識別
     テスト駆動開発のアプローチで、ElementクラスがCompositeパターンのComposite(複合要素)として振る舞えるようにする。

  5. クラスのインスタンスによる「じか書きされた親要素」の置き換え
     じか書きされた親要素をElementクラスのインスタンスに置き換える。

  6. すべての「じか書きされた親要素」の置き換え
     4と5を繰り返し、すべてのじか書きされた親要素を識別してElementクラスのインスタンスに置き換える。

 ツリー構造が、基本データ型(今回の例ではString型)で表現されているような場合には、このようなリファクタリングを実施する。これによってCompositeパターンが導かれ、手続き的処理がオブジェクトによって適切に構造化される。その結果として、ツリー構造が変化するような場合でも柔軟に対応できるようになる。

●まとめ

 前回では「長すぎるメソッド」に対するリファクタリングからCollecting Parameterパターンを導くための実例を、今回は「基本データ型への執着」を除去するためのリファクタリングからCompositeパターンが導かれる実例を、それぞれコードを中心に解説した。

 最後に補足しておくと、前回および今回のサンプル・プログラムをSystem.Xml.XmlTextWriterクラスを使用して記述した場合には、Compositeパターンを導入する必要はないだろう。

 XmlTextWriterクラスには、適切な形式でXMLを出力するためのメソッドやプロパティが豊富に用意されており、「Move Accumulation to Collecting Parameter(K)」(情報蓄積ロジックのCollecting Parameterへの移行)のリファクタリングまでで十分だ。無理やりCompositeパターンを導入することは可能だが、それでは不必要な複雑性を作り込んでしまうことにほかならない。

 このようにリファクタリングからデザインパターンを導く場合でも、パターン導入のトレードオフをしっかりと考慮する必要がある。しかし、このようなトレードオフはリファクタリングを終了すべき段階を見極めることで実現できるため、あらかじめパターンを導入する場合のトレードオフよりは比較的容易である。

 さて次回は、本連載の最終回として、デザインパターンを実践するうえでの注意点、特にデザパタ初心者が陥りがちな「わな」について説明する予定である。デザインパターン利用者に必要な知識なのでお見逃しなく。End of Article


中西 庸文
Microsoft MVP for Visual Developer - Solutions Architect

1998年 太陽システム株式会社入社。Microsoft MVP for Visual Developer - Solutions Architect。オブジェクト指向、アジャイル開発プロセスの啓蒙活動に従事。現在は「協調」と「自主的な行動」を基盤としたチーム開発を行うために、プロジェクトファシリテーション(PF)の重要性を強く感じている。
VB&C#デザインパターンINETA Japan 加盟コミュニティ)を運営中。



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

本日 月間