Builder (ビルダー) パターン

Builder とは構築者という意味で、複雑な手順を経て何かを構築するのですが、それを使う人に隠蔽するデザインパターンです。
似たような手順を踏む、かつ、複雑な手順で構築されるオブジェクトを複数種類作りたい場合に向いています。


今回はちょっと趣向を変えて、サンプルを Web アプリケーションにしてみました。
Windows アプリケーションであっても、Builder(構築者)を操作する過程は同じです。
なお、Global.asax および Web.config ファイルはデフォルトのままなのでコードの掲載は省略しています。

このカテゴリーの先頭へ このページの先頭へ

■クラス図

ここで用いるサンプルのクラス図です。
準備中...

このカテゴリーの先頭へ このページの先頭へ

■サンプルの説明

サンプルの見た目1  サンプルの見た目2
表示された ページの 1 つ目の DropDownList でテーブルを選択し、 2 つ目の DropDownList で出力形式を選択し、
TextBox にファイル名を拡張子なしで入力します。
ダウンロードボタンをクリックすると、上記で設定した情報に基づくデータが含まれているファイルを指定出力形式で
ダウンロードできます。
サンプルのプロジェクトダウンロード

このカテゴリーの先頭へ このページの先頭へ

■コード

アプリケーションのスタートページの WebForm1.aspx です。

WebForm1.aspx
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="WebForm1.aspx.vb" Inherits="MyBuilder.WebForm1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
  <HEAD>
    <title>WebForm1</title>
    <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1">
    <meta name="CODE_LANGUAGE" content="Visual Basic .NET 7.1">
    <meta name="vs_defaultClientScript" content="JavaScript">
    <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
  </HEAD>
  <body MS_POSITIONING="GridLayout">
    <form id="Form1" method="post" runat="server">
      <FONT face="MS UI Gothic">
        <asp:Label id="Label1" style="Z-INDEX: 100; LEFT: 16px; POSITION: absolute; TOP: 16px" runat="server"> </asp:Label>
        <asp:Label id="Label2" style="Z-INDEX: 106; LEFT: 16px; POSITION: absolute; TOP: 72px" runat="server"> </asp:Label>
        <asp:Label id="Label3" style="Z-INDEX: 107; LEFT: 16px; POSITION: absolute; TOP: 136px" runat="server"> </asp:Label>
        <asp:DropDownList id="DropDownList1" style="Z-INDEX: 105; LEFT: 16px; POSITION: absolute; TOP: 96px"
          runat="server" tabIndex="1"></asp:DropDownList>
        <asp:DropDownList id="DropDownList2" style="Z-INDEX: 104; LEFT: 16px; POSITION: absolute; TOP: 40px"
          runat="server"></asp:DropDownList>
        <asp:TextBox id="TextBox1" style="Z-INDEX: 102; LEFT: 16px; POSITION: absolute; TOP: 160px" runat="server"
          tabIndex="2"></asp:TextBox>
        <asp:Button id="Button1" style="Z-INDEX: 103; LEFT: 16px; POSITION: absolute; TOP: 192px" runat="server"
          Text="Button"></asp:Button></FONT>
    </form>
  </body>
</HTML>
このカテゴリーの先頭へ このページの先頭へ

プログラムのスタートページのコードビハインド WebForm1.aspx.vb です。

WebForm1.aspx.vb
Public Class WebForm1
  Inherits System.Web.UI.Page

#Region " Web フォーム デザイナで生成されたコード "

  ' この呼び出しは Web フォーム デザイナで必要です。
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()

  End Sub
  Protected WithEvents Label1 As System.Web.UI.WebControls.Label
  Protected WithEvents Label2 As System.Web.UI.WebControls.Label
  Protected WithEvents Label3 As System.Web.UI.WebControls.Label
  Protected WithEvents DropDownList1 As System.Web.UI.WebControls.DropDownList
  Protected WithEvents DropDownList2 As System.Web.UI.WebControls.DropDownList
  Protected WithEvents TextBox1 As System.Web.UI.WebControls.TextBox
  Protected WithEvents Button1 As System.Web.UI.WebControls.Button

  'メモ : 次のプレースホルダ宣言は Web フォーム デザイナで必要です。
  '削除および移動しないでください。
  Private designerPlaceholderDeclaration As System.Object

  Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init
    ' CODEGEN: このメソッド呼び出しは Web フォーム デザイナで必要です。
    ' コード エディタを使って変更しないでください。
    InitializeComponent()
  End Sub

#End Region

  'ページがロードされた時のイベント
  Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

    If Not IsPostBack Then
      '初回ロード時に色々と設定
      Me.Label1.Text = "テーブル名を選択"
      Me.Label2.Text = "出力形式を選択"
      Me.Label3.Text = "ファイル名を入力"

      Me.DropDownList1.Items.Clear()
      Me.DropDownList1.Items.Add(ConstStrings.TBL_GAME)
      Me.DropDownList1.Items.Add(ConstStrings.TBL_WANKUMA)

      Me.DropDownList2.Items.Clear()
      Me.DropDownList2.Items.Add(ConstStrings.FILE_FORMAT_CSV)
      Me.DropDownList2.Items.Add(ConstStrings.FILE_FORMAT_TSV)

      Me.TextBox1.Text = ""
      Me.Button1.Text = "ダウンロード"
    Else
      'PostBackされたら、ダウンロード処理
      Dim myDirector As Director
      If Me.DropDownList2.SelectedValue = ConstStrings.FILE_FORMAT_CSV Then
        myDirector = New Director(New CsvFileBuilder)
      Else
        myDirector = New Director(New TsvFileBuilder)
      End If
      '構築
      myDirector.Construct(Response, Me.TextBox1.Text, Me.DropDownList1.SelectedValue)
    End If
  End Sub

End Class
このカテゴリーの先頭へ このページの先頭へ

文書構築者を監督し、最終的に構築したものをアウトプットするクラスです。

Director.vb
Public Class Director
  '構築者
  Private m_builder As IFileBuilder

  'コンストラクタ
  Public Sub New(ByVal myBuilder As IFileBuilder)
    Me.m_builder = myBuilder
  End Sub

  '構築
  Public Function Construct(ByVal res As System.Web.HttpResponse, _
                ByVal fileName As String, _
                ByVal tableName As String) As String

    'ファイル名の決定
    If fileName = "" Then fileName = "Default"
    fileName = Me.m_builder.SetFileName(fileName)

    '吐き出すデータ部を取得
    Dim datString As String = Me.m_builder.SetDataSource(tableName)

    res.AddHeader("Content-Disposition", "attachment;filename=" + fileName)
    res.ContentType = "application/octet-stream"
    res.BinaryWrite(System.Text.Encoding.Default.GetBytes(datString))
    res.End()
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

何かのファイルを作成するインターフェースです。
Director の中では、このインターフェースを通して、何かのファイルを構築します。

IFileBuilder.vb
'文書構築者インターフェース
Public Interface IFileBuilder

  'ファイル名を取得するメソッド定義
  Function SetFileName(ByVal fileName As String) As String
  '氏名を取得するメソッド定義
  Function SetDataSource(ByVal tableName As String) As String

End Interface
このカテゴリーの先頭へ このページの先頭へ

具体的な構築者の 1 人、カンマ区切りテキストファイル構築者です。
IFileBuilder を実装しています。

CsvFileBuilder.vb
'カンマ区切りテキストファイル構築者
Public Class CsvFileBuilder
  Implements IFileBuilder

  'ファイル名を取得するメソッド実装
  Public Function SetFileName(ByVal fileName As String) As String Implements IFileBuilder.SetFileName
    Return fileName + ".csv"
  End Function

  'カンマ区切りのデータを作成
  Public Function SetDataSource(ByVal tableName As String) As String Implements IFileBuilder.SetDataSource
    Dim myDa As CommonDataAccess.AbstructDataAccess
    myDa = New CommonDataAccess.OleDataAccess

    Try
      myDa.Open(ConstStrings.CONNECTION_STRING)
      Dim dt As DataTable = myDa.DataSelect("SELECT * FROM " + tableName)
      Dim buff As System.Text.StringBuilder = New System.Text.StringBuilder
      For Each row As DataRow In dt.Rows
        For i As Integer = 0 To dt.Columns.Count - 1
          '区切り文字を出力
          If i > 0 Then
            buff.Append(","c)
          End If

          '項目を出力
          Dim strFieldData As String
          strFieldData = DirectCast(row(i), String)
          buff.Append("""" + strFieldData + """")
        Next
        buff.Append(ControlChars.NewLine)
      Next
      Return buff.ToString()
    Finally
      If Not myDa Is Nothing Then myDa.Close()
    End Try

  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

具体的な構築者の 1 人、タブ区切りテキストファイル構築者です。
IFileBuilder を実装しています。

TsvFileBuilder.vb
'タブ区切りテキストファイル構築者
Public Class TsvFileBuilder
  Implements IFileBuilder

  'ファイル名を取得するメソッド実装
  Public Function SetFileName(ByVal fileName As String) As String Implements IFileBuilder.SetFileName
    Return fileName + ".txt"
  End Function

  'カンマ区切りのデータを作成
  Public Function SetDataSource(ByVal tableName As String) As String Implements IFileBuilder.SetDataSource
    Dim myDa As CommonDataAccess.AbstructDataAccess
    myDa = New CommonDataAccess.OleDataAccess

    Try
      myDa.Open(ConstStrings.CONNECTION_STRING)
      Dim dt As DataTable = myDa.DataSelect("SELECT * FROM " + tableName)
      Dim buff As System.Text.StringBuilder = New System.Text.StringBuilder
      For Each row As DataRow In dt.Rows
        For i As Integer = 0 To dt.Columns.Count - 1
          '区切り文字を出力
          If i > 0 Then
            buff.Append(ControlChars.Tab)
          End If

          '項目を出力
          Dim strFieldData As String
          strFieldData = DirectCast(row(i), String)
          buff.Append(strFieldData)
        Next
        buff.Append(ControlChars.NewLine)
      Next
      Return buff.ToString()
    Finally
      If Not myDa Is Nothing Then myDa.Close()
    End Try

  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

定数を管理するクラスです。

ConstStrings.vb
Public NotInheritable Class ConstStrings
  '接続文字列
  Public Const CONNECTION_STRING As String = "Provider=Microsoft.Jet.OLEDB.4.0;" + _
                                 "Data Source=D:\wankuma\Builder.mdb;" + _
                                 "User Id=admin;Password=;"
  'テーブル名
  Public Const TBL_GAME As String = "game"
  Public Const TBL_WANKUMA As String = "wankuma"

  'ファイル形式
  Public Const FILE_FORMAT_TSV As String = "タブ区切り"
  Public Const FILE_FORMAT_CSV As String = "カンマ区切り"
End Class
このカテゴリーの先頭へ このページの先頭へ

■ひとこと

スタートページの WebForm は、Director(監督)がどういう手順で文書を構築しているのか、知る必要がありません。
また Director(監督)自身も、具体的には何の文書を構築しているのか、知る必要がありません。


この様に、クラス間の結合を抽象クラスを介して行う事により、
柔軟に仕様変更に対応できます。
仕様変更とは、例えば、固定長ファイル出力用の構築者が欲しい場合、とか Windows アプリケーションに変更したい場合等です。
最初の仕様変更に対応するとすれば、まずそれ用の Builder クラスを作成し、WebForm1 の IsPostBack が True の時に
If 文で判定して、Director クラスのコンストラクタに渡すインスタンスを決定している箇所がありますが、
そこに分岐を 1 つ加えればお終いです。
Windows アプリケーションにする場合は、WebForm1 を Windows Form にし、
Director が Response のストリームに出力している箇所を、ローカルマシンへのファイル出力処理に変更するだけです。

このカテゴリーの先頭へ このページの先頭へ