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

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

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

Imports System
Imports NUnit.Framework
Imports DesignPatterns.Core.Composite

Namespace DesignPatterns.Tests.Composite

  ' Elementのテスト・クラス

  <TestFixture()> Public Class ElementTest

    Private _target As Element

    <Test()> Public Sub 属性が1つで値が1つのエレメントを出力する()
      _target = New Element("actualPoint")
      _target.AddAttribute("isOver", "True")
      _target.Content = "2.0"

      Dim expected As String = "<actualPoint isOver=""True"">2.0</actualPoint>"

      Assert.AreEqual(expected, _target.ToString(), "結果が違います。")
    End Sub

  End Class

End Namespace
新しく作成されたElementクラスのテスト・クラス(VB.NET)
 
Imports System
Imports System.Text

Namespace DesignPatterns.Core.Composite

  ' Elementクラス
  Public Class Element

    Private _name As String = String.Empty
    Private _content As String = String.Empty
    Private _attributes As StringBuilder = New StringBuilder

    ' コンストラクタ
    Public Sub New(ByVal name As String)
      Me._name = name
    End Sub

    ' 属性を追加する
    Public Sub AddAttribute(ByVal attribute As String, _
ByVal attributeValue As String)
      _attributes.Append(" ")
      _attributes.Append(attribute)
      _attributes.Append("=""")
      _attributes.Append(attributeValue)
      _attributes.Append("""")
    End Sub

    ' 内容
    Public WriteOnly Property Content() As String
      Set(ByVal value As String)
        _content = value
      End Set
    End Property

    ' Elementの状態を表す文字列を取得する

    Public Overrides Function ToString() As String
      Dim result As String = "<" + _name + _attributes.ToString() + ">"
      result &= _content
      result &= "</" & _name & ">"

      Return result
    End Function

  End Class

End Namespace
新しく作成されたElementクラス(VB.NET)
 
Imports System
Imports System.Text

Namespace DesignPatterns.Core.Composite

  Public Class StoriesWriter

    ……中略……

    ' Elementのインスタンスによるじか書きされた子要素の置き換え

    Private Sub WriteStoryContentTo(ByVal xml As StringBuilder, _
ByVal myStory As Story)
      xml.Append("<content>")
      xml.Append(myStory.Content)
      xml.Append("</content>")
      Dim contentElement As Element = New Element("content")
      contentElement.Content = myStory.Content

      xml.Append(contentElement.ToString())
    End Sub

    ……中略……


    ' Elementのインスタンスによるじか書きされた子要素の置き換え
    Private Sub WriteTaskActualPointTo(ByVal xml As StringBuilder, _
ByVal myTask As Task)
      xml.Append("<actualPoint")
      xml.Append(" isOver=""")
      xml.Append(myTask.IsOverPoint().ToString())
      xml.Append(""">")
      xml.Append(myTask.ActualPoint.ToString("##.0"))
      xml.Append("</actualPoint>")
      Dim actualPointElement As Element = New Element("actualPoint")
      actualPointElement.AddAttribute("isOver", myTask.IsOverPoint().ToString())
      actualPointElement.Content = myTask.ActualPoint.ToString("##.0")

      xml.Append(actualPointElement.ToString())
    End Sub

    ' Elementのインスタンスによるじか書きされた子要素の置き換え
    Private Sub WriteTaskEstimatedPointTo(ByVal xml As StringBuilder, _
ByVal myTask As Task)
      xml.Append("<estimatedPoint>")
      xml.Append(myTask.EstimatedPoint.ToString("##.0"))
      xml.Append("</estimatedPoint>")
      Dim estimatedPointElement As Element = New Element("estimatedPoint")
      estimatedPointElement.Content = myTask.EstimatedPoint.ToString("##.0")

      xml.Append(estimatedPointElement.ToString())
    End Sub

    ' Elementのインスタンスによるじか書きされた子要素の置き換え
    Private Sub WriteTaskContentTo(ByVal xml As StringBuilder, ByVal myTask As Task)
      xml.Append("<content>")
      xml.Append(myTask.Content)
      xml.Append("</content>")
      Dim contentElement As Element = New Element("content")
      contentElement.Content = myTask.Content

      xml.Append(contentElement.ToString())
    End Sub

  End Class

End Namespace
じか書きされた子要素がElementクラスのインスタンスに置き換えられたStoriesWriterクラス(VB.NET)
 
Imports System
Imports NUnit.Framework
Imports DesignPatterns.Core.Composite

Namespace DesignPatterns.Tests.Composite

  <TestFixture()> Public Class ElementTest

     ……中略……

    <Test()> _
Public Sub 子要素を1つ含むコンポジット・エレメントを出力する()
      _target = New Element("task")
      _target.Add(New Element("actualPoint"))

      Dim expected As String = _
        "<task>" & _
          "<actualPoint>" & _
          "</actualPoint>" & _
        "</task>"

      Assert.AreEqual(expected, _target.ToString(), "結果が違います。")
    End Sub

  End Class

End Namespace
新しくテスト・メソッドが追加されたElementクラスのテスト・クラス(VB.NET)
 
Imports System
Imports System.Text
Imports System.Collections

Namespace DesignPatterns.Core.Composite

  Public Class Element : Implements IEnumerable

    ……中略……

    ' 子要素を格納するArrayList
    Private _children As ArrayList = New ArrayList

    ……中略……

    ' 子要素を追加する
    Public Sub Add(ByVal child As Element)
      _children.Add(child)
    End Sub

    ' Elementの状態を表す文字列を取得する
    Public Overrides Function ToString() As String
      Dim result As String = "<" + _name + _attributes.ToString() + ">"

      For Each child As Element In _children
        result += child.ToString()
      Next

      result &= _content
      result &= "</" & _name & ">"

      Return result
    End Function

    '  Elementを反復処理できる列挙子を取得する
    Public Function GetEnumerator() As IEnumerator _
      Implements IEnumerable.GetEnumerator
      Return _children.GetEnumerator()
    End Function

  End Class

End Namespace
Composite(複合要素)としての振る舞いが追加されたElementクラス(VB.NET)
 
Imports System
Imports NUnit.Framework
Imports DesignPatterns.Core.Composite

Namespace DesignPatterns.Tests.Composite

  <TestFixture()> Public Class ElementTest

    ……中略……


    <Test()> _
    Public Sub 子要素と孫要素を含むコンポジット・エレメントを出力する()
      _target = New Element("stories")
      Dim storyElement As Element = New Element("story")
      Dim taskElement As Element = New Element("task")

      _target.Add(storyElement)
      storyElement.Add(taskElement)

      Dim expected As String = _
      "<stories>" & _
        "<story>" & _
          "<task>" & _
          "</task>" & _
        "</story>" & _
      "</stories>"

      Assert.AreEqual(expected, _target.ToString(), "結果が違います。")
    End Sub

  End Class

End Namespace
さらにテスト・メソッドが追加されたElemenクラスのテスト・クラス(VB.NET)
 
Imports System
Imports System.Text

Namespace DesignPatterns.Core.Composite

  Public Class StoriesWriter

    ……中略……

    ' Elementのインスタンスによるじか書きされた親要素の置き換え
    Private Sub WriteTasksTo(ByVal xml As StringBuilder, ByVal myStory As Story)
      For Each myTask As Task In myStory
        xml.Append("<task")
        xml.Append(" no=""")
        xml.Append(myTask.No.ToString())
        xml.Append("""")
        xml.Append(" storyNo=""")
        xml.Append(myTask.StoryNo.ToString())
        xml.Append(""">")
        Dim taskElement As Element = New Element("task")
        taskElement.AddAttribute("no", myTask.No.ToString())
        taskElement.AddAttribute("storyNo", myTask.StoryNo.ToString())

        ' StringBuilderの代わりにElementを引き渡す
        WriteTaskActualPointTo(xml, myTask)<
        WriteTaskActualPointTo(taskElement, myTask)

        WriteTaskEstimatedPointTo(xml, myTask)<
        WriteTaskEstimatedPointTo(taskElement, myTask)

        WriteTaskContentTo(xml, myTask)<
        WriteTaskContentTo(taskElement, myTask)

        ' StringBuilderにはElementの文字列表現を格納する

        xml.Append("</task>")<
        xml.Append(taskElement.ToString())
      Next
    End Sub

    ' 引き渡された親Elementに子Elementを追加する
  Private Sub WriteTaskActualPointTo(ByVal xml As StringBuilder, _
ByVal myTask As Task)
    Private Sub WriteTaskActualPointTo(ByVal taskElement As Element, _
ByVal myTask As Task)
      Dim actualPointElement As Element = New Element("actualPoint")
      actualPointElement.AddAttribute("isOver", myTask.IsOverPoint().ToString())
      actualPointElement.Content = myTask.ActualPoint.ToString("##.0")

      xml.Append(actualPointElement.ToString())
      taskElement.Add(actualPointElement)
    End Sub

    ' 引き渡された親Elementに子Elementを追加する
    Private Sub WriteTaskEstimatedPointTo(ByVal xml As StringBuilder, _
ByVal myTask As Task)
    Private Sub WriteTaskEstimatedPointTo(ByVal taskElement As Element, _
ByVal myTask As Task)
      Dim estimatedPointElement As Element = New Element("estimatedPoint")
      estimatedPointElement.Content = myTask.EstimatedPoint.ToString("##.0")

      xml.Append(estimatedPointElement.ToString())
      taskElement.Add(estimatedPointElement)
    End Sub

    ' 引き渡された親Elementに子Elementを追加する
    Private Sub WriteTaskContentTo(ByVal xml As StringBuilder, _
ByVal myTask As Task)
    Private Sub WriteTaskContentTo(ByVal taskElement As Element, _
ByVal myTask As Task)
      Dim contentElement As Element = New Element("content")
      contentElement.Content = myTask.Content

      xml.Append(contentElement.ToString())
      taskElement.Add(contentElement)
    End Sub

  End Class

End Namespace
じか書きされた親要素がElementのインスタンスに置き換えられたStoriesWriterクラス(VB.NET)
 
Imports System
Imports System.Text

Namespace DesignPatterns.Core.Composite

  ' StoriesWriterクラス
  Public Class StoriesWriter

    Private _stories As Stories

    ' コンストラクタ
    Public Sub New(ByVal myStories As Stories)
      Me._stories = myStories
    End Sub

    ' XMLを文字列で出力する

    Public Function GetContents() As String
      Dim xml As StringBuilder = New StringBuilder
      WriteStoriesTo(xml)
      Return xml.ToString()
    End Function

    ' すべてのストーリーを出力する
    Private Sub WriteStoriesTo(ByVal xml As StringBuilder)
      Dim storiesElement As Element = New Element("stories")

      For Each myStory As Story In _stories
        Dim storyElement As Element = New Element("story")
        storyElement.AddAttribute("no", myStory.No.ToString())
        storyElement.AddAttribute("priority", myStory.Priority.ToString())

        WriteStoryContentTo(storyElement, myStory)
        WriteTasksTo(storyElement, myStory)

        storiesElement.Add(storyElement)
      Next

      xml.Append(storiesElement.ToString())
    End Sub

    ' ストーリーの内容を出力する

    Private Sub WriteStoryContentTo(ByVal storyElement As Element, _
ByVal myStory As Story)
      Dim contentElement As Element = New Element("content")
      contentElement.Content = myStory.Content

      storyElement.Add(contentElement)
    End Sub

    ' すべてのタスクを出力する
    Private Sub WriteTasksTo(ByVal storyElement As Element, _
ByVal myStory As Story)
      For Each myTask As Task In myStory
        Dim taskElement As Element = New Element("task")
        taskElement.AddAttribute("no", myTask.No.ToString())
        taskElement.AddAttribute("storyNo", myTask.StoryNo.ToString())

        WriteTaskActualPointTo(taskElement, myTask)
        WriteTaskEstimatedPointTo(taskElement, myTask)
        WriteTaskContentTo(taskElement, myTask)

        storyElement.Add(taskElement)
      Next
    End Sub

    ' タスクの実測ポイントを出力する
    Private Sub WriteTaskActualPointTo(ByVal taskElement As Element, _
ByVal myTask As Task)
      Dim actualPointElement As Element = New Element("actualPoint")
      actualPointElement.AddAttribute("isOver", myTask.IsOverPoint().ToString())
      actualPointElement.Content = myTask.ActualPoint.ToString("##.0")

      taskElement.Add(actualPointElement)
    End Sub

    ' タスクの予定ポイントを出力する
    Private Sub WriteTaskEstimatedPointTo(ByVal taskElement As Element, _
ByVal myTask As Task)
      Dim estimatedPointElement As Element = New Element("estimatedPoint")
      estimatedPointElement.Content = myTask.EstimatedPoint.ToString("##.0")

      taskElement.Add(estimatedPointElement)
    End Sub

    ' タスクの内容を出力する

    Private Sub WriteTaskContentTo(ByVal taskElement As Element, _
ByVal myTask As Task)
      Dim contentElement As Element = New Element("content")
      contentElement.Content = myTask.Content

      taskElement.Add(contentElement)
    End Sub

  End Class

End Namespace
すべてのじか書きされた親要素がElementのインスタンスに置き換えられたStoriesWriterクラス(VB.NET)
 
インデックス・ページヘ  「.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 記事ランキング

本日 月間