Facade (ファサード) パターン

複数のクラスを束ねて、シンプルな窓口を提供するデザインパターンです。
『ふぁけーど』ではなく、『ファサード』と読みます。(昔、身近に読み間違えてる人がいたもので(^^;)
プログラムがある程度の規模になってくると、「あれを呼んで、次にあれを呼んで、それからあれから値を取得して...」
なんていう、暗黙の呼び出し順序や手順のお約束ができてきます。
ドキュメント化されていれば、まだ後から見た人が解る可能性はありますが、
このデザインパターンの適用により、クラスとして残しておけば、呼び出し順序の間違えやパラメータの誤りも無くなります。


サンプルでは、『指定された条件で SQL 文を作って、DataBase に投げて、結果セットをテキストファイルに吐き出す』
という部分を Facade にやってもらい、使う人(画面)は「ファイルを出力するディレクトリ と 出力の条件」を
Facade に渡すだけです。

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

■クラス図

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

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

■サンプルの説明

サンプルの見た目1
SQL Server の NorthWind データベースの Employees テーブルのデータをタブ区切りテキスト形式で出力します。
ComboBox で出力の対象を選択し、テキストファイルの出力先のフォルダを指定して、実行ボタンをクリックします。
サンプルのプロジェクトダウンロード

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

■コード

アプリケーションのエントリポイント StartUpForm です。

StartUpForm.vb
Public Class StartUpForm
  Inherits System.Windows.Forms.Form

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

  Public Sub New()
    MyBase.New()

    ' この呼び出しは Windows フォーム デザイナで必要です。
    InitializeComponent()

    ' InitializeComponent() 呼び出しの後に初期化を追加します。
    AddHandler Application.ThreadException, AddressOf CommonExceptionAndHandler.ExceptionHandler.MyHandler
  End Sub

  ' Form は、コンポーネント一覧に後処理を実行するために dispose をオーバーライドします。
  Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
    If disposing Then
      If Not (components Is Nothing) Then
        components.Dispose()
      End If
    End If
    MyBase.Dispose(disposing)
  End Sub

  ' Windows フォーム デザイナで必要です。
  Private components As System.ComponentModel.IContainer

  ' メモ : 以下のプロシージャは、Windows フォーム デザイナで必要です。
  'Windows フォーム デザイナを使って変更してください。  
  ' コード エディタを使って変更しないでください。
  Friend WithEvents Label1 As System.Windows.Forms.Label
  Friend WithEvents ComboBox1 As System.Windows.Forms.ComboBox
  Friend WithEvents Label2 As System.Windows.Forms.Label
  Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
  Friend WithEvents Button1 As System.Windows.Forms.Button
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
    Me.Label1 = New System.Windows.Forms.Label
    Me.ComboBox1 = New System.Windows.Forms.ComboBox
    Me.Label2 = New System.Windows.Forms.Label
    Me.TextBox1 = New System.Windows.Forms.TextBox
    Me.Button1 = New System.Windows.Forms.Button
    Me.SuspendLayout()
    '
    'Label1
    '
    Me.Label1.Location = New System.Drawing.Point(8, 8)
    Me.Label1.Name = "Label1"
    Me.Label1.TabIndex = 0
    Me.Label1.Text = "Label1"
    '
    'ComboBox1
    '
    Me.ComboBox1.Location = New System.Drawing.Point(112, 8)
    Me.ComboBox1.Name = "ComboBox1"
    Me.ComboBox1.Size = New System.Drawing.Size(121, 20)
    Me.ComboBox1.TabIndex = 1
    Me.ComboBox1.Text = "ComboBox1"
    '
    'Label2
    '
    Me.Label2.Location = New System.Drawing.Point(8, 32)
    Me.Label2.Name = "Label2"
    Me.Label2.TabIndex = 2
    Me.Label2.Text = "Label2"
    '
    'TextBox1
    '
    Me.TextBox1.Location = New System.Drawing.Point(112, 32)
    Me.TextBox1.Name = "TextBox1"
    Me.TextBox1.TabIndex = 3
    Me.TextBox1.Text = "TextBox1"
    '
    'Button1
    '
    Me.Button1.Location = New System.Drawing.Point(88, 64)
    Me.Button1.Name = "Button1"
    Me.Button1.TabIndex = 4
    Me.Button1.Text = "Button1"
    '
    'StartUpForm
    '
    Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12)
    Me.ClientSize = New System.Drawing.Size(292, 266)
    Me.Controls.Add(Me.Button1)
    Me.Controls.Add(Me.TextBox1)
    Me.Controls.Add(Me.Label2)
    Me.Controls.Add(Me.ComboBox1)
    Me.Controls.Add(Me.Label1)
    Me.Name = "StartUpForm"
    Me.Text = "Form1"
    Me.ResumeLayout(False)

  End Sub

#End Region

  '画面がロードされた時のイベント
  Private Sub StartUpForm_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Me.Text = "Facade"

    Me.Label1.Text = "国を選択"
    Me.ComboBox1.Items.Clear()
    Me.ComboBox1.DropDownStyle = ComboBoxStyle.DropDownList

    'ComboBoxにDataTableをバインドする
    Dim dt As DataTable = New DataTable
    dt.Columns.Add(New DataColumn("countryName", GetType(String)))
    dt.Columns.Add(New DataColumn("countryid", GetType(String)))
    dt.Rows.Add(New String() {"アメリカ", "USA"})
    dt.Rows.Add(New String() {"全て", ""})

    Me.ComboBox1.DataSource = dt
    Me.ComboBox1.DisplayMember = "countryName"
    Me.ComboBox1.ValueMember = "countryid"
    Me.ComboBox1.SelectedIndex = 0

    Me.Label2.Text = "出力フォルダを入力"
    Me.TextBox1.Size = New Drawing.Size(160, 20)
    Me.TextBox1.Text = ""

    Me.Button1.Text = "実行"
  End Sub

  '実行ボタンをクリックした時のイベント
  Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ 
  Handles Button1.Click
    'ファサードに色々な処理を単純な窓口で依頼する
    Facade.CreateFile(Me.TextBox1.Text, _
              Me.ComboBox1.SelectedValue.ToString())

    MessageBox.Show("実行が終わりました", _
            Me.Text, _
            MessageBoxButtons.OK, _
            MessageBoxIcon.Information)
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

ファイルを出力するクラスです。
今回の仕様では、このクラスだけでは仕様を満たせません。

FileWriter.vb
'ファイル出力
Public NotInheritable Class FileWriter

  '与えられたDataTableの中身をTab区切りで出力するだけのメソッド
  Public Shared Sub Output(ByVal saveFilePath As String, ByVal dt As DataTable)

    Dim sw As System.IO.StreamWriter
    sw = New System.IO.StreamWriter(saveFilePath, _
                    False, System.Text.Encoding.Default)

    Try
      'ヘッダの出力
      For i As Integer = 0 To dt.Columns.Count - 1
        Dim col As DataColumn = dt.Columns(i)
        If i > 0 Then sw.Write(ControlChars.Tab)
        sw.Write(col.ColumnName)
      Next
      sw.WriteLine()

      'データの出力
      For Each row As DataRow In dt.Rows
        For j As Integer = 0 To dt.Columns.Count - 1
          If j > 0 Then sw.Write(ControlChars.Tab)
          sw.Write(DirectCast(row(j), String))
        Next
        sw.WriteLine()
      Next
    Finally
      If Not sw Is Nothing Then sw.Close()
    End Try
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

シンプルな窓口を提供する Facade です。
窓口はシンプルですが、中の人は色々やっています。

Facade.vb
'ファサード
Public NotInheritable Class Facade

  '簡単な窓口を提供するためのメソッド
  Public Shared Sub CreateFile(ByVal saveDirPath As String, ByVal countryName As String)
    Const CONNECTION_STRING As String = "Data Source=(local);Initial Catalog=NorthWind;Integrated Security=SSPI;"

    Dim myDa As CommonDataAccess.AbstructDataAccess
    Dim dt As DataTable
    Dim saveFilePath As String

    'Directroyが存在するかのチェック
    If Not System.IO.Directory.Exists(saveDirPath) Then
      Throw New CommonExceptionAndHandler.MyException("出力フォルダが無効です")
    End If
    '出力ファイル名の決定
    saveFilePath = System.IO.Path.Combine(saveDirPath, countryName + "_Employees.txt")

    'データベースより値の取得
    myDa = New CommonDataAccess.SqlServerDataAccess
    Try
      myDa.Open(CONNECTION_STRING)
      If countryName = "" Then
        dt = myDa.DataSelect("SELECT * FROM Employees ORDER BY Country;")
      Else
        dt = myDa.DataSelect("SELECT * FROM Employees WHERE Country ='" + countryName + "';")
      End If
    Finally
      If Not myDa Is Nothing Then myDa.Close()
    End Try

    'TSVファイル書き出し
    FileWriter.Output(saveFilePath, dt)
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

■ひとこと

あまり複雑な事はやってないんで、ちょっとサンプルがあまり良くないかも知れません...
要は、このプログラムの中で、お約束の手順を踏んでテキストファイルの吐き出しを行っていますが、
それを StartUpForm に対してお約束の手順を隠蔽しているところがミソなわけです。


これまたサンプルのあまり良くない点なのですが、
汎用性のある処理を Facade の public なメソッドとして提供すべきです。
# サンプルは解りやすくする為にデータベースやテーブルを限定しました。(言い訳w)
似たような メソッドが沢山あった場合は、後から見た人は返って混乱してしまいます。
十分に検討してからこのパターンを使ってみましょう。

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