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

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

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

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

●じか書きされたツリー構造のCompositeへの置き換え

 前ページの実装コードを見ると、StoriesWriterクラスのGetContentsメソッドは、出力されるXML文書のツリー構造が基本データ型である文字列によってじか書きされているためにコードが膨れ上がってしまっている。

 もし、文字列によってじか書きされたツリー構造を、オブジェクトによって構造化されたツリー構造へ置き換えることができれば、この膨れ上がってしまったコードをシンプルで意図が明確なコードにすることが可能なはずだ。

 オブジェクトによるツリー構造の表現には、「Compositeパターン」が有効である。書籍『オブジェクト指向における再利用のためのデザインパターン』では、Compositeパターンの目的は次のように記述されている。

部分―全体階層を表現するために、オブジェクトを木構造に組み立てる。Compositeパターンにより、クライアントは、個々のオブジェクトとオブジェクトを合成したものを一様に扱うことができる。

 Compositeとは「複合物」を意味する。Compositeパターンとは、部分―全体を同一視した複合物を使用することで、オブジェクトによる再帰的構造の表現を容易にするものだ。

 それでは、じか書きされたツリー構造からCompositeパターンを導き出すためのリファクタリングの手順を紹介しよう。

■前準備:「Move Accumulation to Collecting Parameter(K)」(情報蓄積ロジックのCollecting Parameterへの移行)の適用

 まずは前準備として、コードの不吉な匂いである「長すぎるメソッド」に対してリファクタリングを行うことにしよう。

 メソッドは長くなるほど理解しづらくなる。長すぎるメソッドは小さく分割して、それぞれに分かりやすい名前を付けることにより、コードの意図を明確にすることができる。これが長すぎるメソッドをリファクタリングする理由だ。コメントを書く必要性を感じた場合は、そうするかわりに分かりやすい名前のメソッドに分割するのだ。また、メソッドの名前付けの指針として「What指向の名前付け」を実践するとよい。つまり、どのような処理を行わせたいのか(How)ではなく、何をさせたいのか(What)に注目してメソッドの名前付けを行うのだ。

 StoriesWriterクラスのGetContentsメソッドは、XML文書の出力に必要とされる多くの情報を、メソッド内のローカル変数(StringBuilder型の変数xml)に(Appendメソッドにより)だらだらと蓄積しているため、かなりの行数になってしまっている。

 このような場合には「Move Accumulation to Collecting Parameter(K)」(情報蓄積ロジックのCollecting Parameterへの移行)を行う。このリファクタリングは「Collecting Parameterパターン」を導くためのものだ。

 Collecting Parameterパターンは、Kent Beck氏による書籍『Smalltalk ベストプラクティス・パターン』の中で、以下のように説明されている。

いくつものメソッドの協調作業の結果としてコレクションを返すためには、すべてのサブメソッドの結果を集めるためのパラメータを追加しよう。

 このリファクタリングは、前回紹介した「Compose Method(K)」(メソッドの組み立て)リファクタリングとともに行われることが多い。これらのリファクタリングの組み合わせは、情報蓄積のために多くの行数を必要としている長すぎるメソッドを、次のように最適化する。

  • 情報を蓄積するためのステップごとにメソッドを小さく分割してメソッドの詳細レベルをそろえ、分割された各メソッドには情報を蓄積するためのパラメータ・コレクションを引き渡す。

  • パラメータ・コレクションを受け取ったメソッド側では、必要な情報をこのパラメータ・コレクションに追加する。

  • パラメータ・コレクションが分割されたすべてのメソッドに対して引き渡されると、必要な情報はすべてパラメータ・コレクション内に蓄積されている。

 それではリファクタリングを開始しよう。このリファクタリングの手順は以下のようになる。

  1. GetContentsメソッドに対して「メソッドの抽出(F)」を行い、すべてのストーリーを出力するためのWriteStoriesToメソッドを抽出し、情報を蓄積するためのパラメータ・コレクション(StringBuilder型のローカル変数xml)を引き渡す

  2. WriteStoriesToメソッドに対して、さらに「メソッドの抽出(F)」を行い、ストーリーの内容を出力するためのWriteStoryContentToメソッド、すべてのタスクを出力するためのWriteTasksToメソッドをそれぞれ抽出し、パラメータ・コレクションを引き渡す

  3. WriteTasksToメソッドに対しては、さらに「メソッドの抽出(F)」を行い、タスクの実測ポイントを出力するためのWriteTaskActualPointToメソッド、タスクの予定ポイントを出力するためのWriteTaskEstimatedPointToメソッド、タスクの内容を出力するためのWriteTaskContentToメソッドをそれぞれ抽出し、パラメータ・コレクションを引き渡す

 (1)から(3)のそれぞれの段階において、コンパイルしてテストを実行する。どの段階でも正常にコンパイルが完了し、テストもパスするはずだ。以下に、すべてメソッドの抽出が完了した最終的な実装コードを示す。

using System;
using System.Text;

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

    // メソッドの抽出が行われたGetContentsメソッド
    public string GetContents()
    {
      StringBuilder xml = new StringBuilder();
      WriteStoriesTo(xml);
      return xml.ToString();
    }

    // すべてのストーリーを出力する
    private void WriteStoriesTo(StringBuilder xml)
    {
      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("\">");

        WriteStoryContentTo(xml, story);
        WriteTasksTo(xml, story);

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

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

    // ストーリーの内容を出力する
    private void WriteStoryContentTo(StringBuilder xml, Story story)
    {
      xml.Append("<content>");
      xml.Append(story.Content);
      xml.Append("</content>");
    }

    // すべてのタスクを出力する
    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("\">");

        WriteTaskActualPointTo(xml, task);
        WriteTaskEstimatedPointTo(xml, task);
        WriteTaskContentTo(xml, task);

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

    // タスクの実測ポイントを出力する
    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>");
    }

    // タスクの予定ポイントを出力する
    private void WriteTaskEstimatedPointTo(StringBuilder xml, Task task)
    {
      xml.Append("<estimatedPoint>");
      xml.Append(task.EstimatedPoint.ToString("##.0"));
      xml.Append("</estimatedPoint>");
    }

    // タスクの内容を出力する
    private void WriteTaskContentTo(StringBuilder xml, Task task)
    {
      xml.Append("<content>");
      xml.Append(task.Content);
      xml.Append("</content>");
    }
  }
}
Collecting Parameterパターンが適用されたStoriesWriterクラス(C#)

 これで「Move Accumulation to Collecting Parameter(K)」(情報蓄積ロジックのCollecting Parameterへの移行)が完了した。

 その結果として、StoriesWriterクラスのGetContentsメソッドはXML文書のツリー構造を構成するエレメント(XML文書内で、タグで囲まれた部分)の単位で適切に分割され、引き続きリファクタリングを行うことが容易になった。

 今回はCompositeパターンを導き出すための準備段階として、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で始めるデザインパターン
  第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 記事ランキング

本日 月間