Abstract Factory (アブストラクトファクトリ) パターン

互いに関連や依存があるクラス達の具象なものは隠蔽し、あくまでも抽象な親同士でインスタンス生成する窓口を用意するデザインパターンです。
抽象的なものを材料にして抽象的なものを作る。でもじつは抽象的なものの中には具象的なものが入ってる。
何のことだかさっぱり解りませんw
サンプルをざーっと読んでいただければ、イメージが沸いてくる...といいなぁ...と思って書いています。


サンプルのアウトプットとして Microsoft Excel および Microsoft Word を用いていますが、
Microsoft Excel 11.0 Object Library および Microsoft Word 11.0 Object Library をプロジェクトの参照設定に追加しています。

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

■クラス図

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

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

■サンプルの説明

サンプルの見た目1
データソース(テーブル名)を選択し、 出力形式(Excel または Word)を選択し、
タイトル(ファイル名)を入力します。
出力ボタンをクリックすると、アプリケーションの開始ディレクトリに、指定文書が出力されます。
サンプルのプロジェクトダウンロード

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

■コード

アプリケーションのエントリポイント StartUpForm です。
Button1_Click メソッドの中では、抽象クラスの型で宣言したオブジェクトを使って処理を行っています。
実際に何が入っているのかは、実行時にブレークポイントを付けてウォッチ ウィンドウ等で確認する事ができます。

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)
    'プロトタイプの破棄
    If Not Me.m_manager Is Nothing Then
      Me.m_manager.Dispose()
    End If
  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 ComboBox2 As System.Windows.Forms.ComboBox
  Friend WithEvents Button1 As System.Windows.Forms.Button
  Friend WithEvents Label3 As System.Windows.Forms.Label
  Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
  <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.ComboBox2 = New System.Windows.Forms.ComboBox
    Me.Button1 = New System.Windows.Forms.Button
    Me.Label3 = New System.Windows.Forms.Label
    Me.TextBox1 = New System.Windows.Forms.TextBox
    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"
    '
    'ComboBox2
    '
    Me.ComboBox2.Location = New System.Drawing.Point(112, 32)
    Me.ComboBox2.Name = "ComboBox2"
    Me.ComboBox2.Size = New System.Drawing.Size(121, 20)
    Me.ComboBox2.TabIndex = 3
    Me.ComboBox2.Text = "ComboBox2"
    '
    'Button1
    '
    Me.Button1.Location = New System.Drawing.Point(112, 160)
    Me.Button1.Name = "Button1"
    Me.Button1.TabIndex = 4
    Me.Button1.Text = "Button1"
    '
    'Label3
    '
    Me.Label3.Location = New System.Drawing.Point(8, 56)
    Me.Label3.Name = "Label3"
    Me.Label3.TabIndex = 5
    Me.Label3.Text = "Label3"
    '
    'TextBox1
    '
    Me.TextBox1.Location = New System.Drawing.Point(112, 56)
    Me.TextBox1.Name = "TextBox1"
    Me.TextBox1.TabIndex = 6
    Me.TextBox1.Text = "TextBox1"
    '
    'StartUp
    '
    Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12)
    Me.ClientSize = New System.Drawing.Size(292, 266)
    Me.Controls.Add(Me.TextBox1)
    Me.Controls.Add(Me.Label3)
    Me.Controls.Add(Me.Button1)
    Me.Controls.Add(Me.ComboBox2)
    Me.Controls.Add(Me.Label2)
    Me.Controls.Add(Me.ComboBox1)
    Me.Controls.Add(Me.Label1)
    Me.Name = "StartUp"
    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 = "AbstractFactory"

    Me.Label1.Text = "データソース"
    Me.Label2.Text = "出力形式"
    Me.Label3.Text = "タイトル"
    Me.Button1.Text = "出力"

    Me.ComboBox1.DropDownStyle = ComboBoxStyle.DropDownList
    Me.ComboBox1.Items.Clear()
    Me.ComboBox1.Items.Add(ConstStrings.DATA_SRC_AUTHOR)
    Me.ComboBox1.Items.Add(ConstStrings.DATA_SRC_EMPLOYEE)
    Me.ComboBox1.SelectedIndex = 0

    Me.ComboBox2.DropDownStyle = ComboBoxStyle.DropDownList
    Me.ComboBox2.Items.Clear()
    Me.ComboBox2.Items.Add(ConstStrings.OUTPUT_EXCEL)
    Me.ComboBox2.Items.Add(ConstStrings.OUTPUT_WORD)
    Me.ComboBox2.SelectedIndex = 0

    Me.TextBox1.Text = ""
  End Sub

  '出力ボタンをクリックした時のイベント
  Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
    Dim appPath As String = Application.StartupPath
    Dim myDocument As AbstractDocument
    Dim myFactory As AbstractFactory

    If Me.TextBox1.Text = "" Then
      MessageBox.Show("タイトルを入力して下さい", _
              Me.Text, _
              MessageBoxButtons.OK, _
              MessageBoxIcon.Exclamation)
      Exit Sub
    End If

    '具体的なFactoryの決定
    myFactory = AbstractFactory.GetFactory(Me.ComboBox2.Text)

    '出力形式の設定
    myDocument = myFactory.GetDocument(Me.ComboBox2.Text, appPath, Me.TextBox1.Text)
    myDocument.AddHeader(myFactory.GetHeader(Me.TextBox1.Text))
    myDocument.AddBody(myFactory.GetBody())
    myDocument.OutPut()

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

抽象的な文書工場です。

AbstractFactory.vb
'抽象 文書工場
Public MustInherit Class AbstractFactory

  'ヘッダ文字列を取得するメソッド定義
  Public MustOverride Function GetHeader(ByVal headerText As String) As String
  '本文を取得するメソッド定義
  Public MustOverride Function GetBody() As String()

  'ドキュメントを返す
  Public MustOverride Function GetDocument(ByVal doctype As String, _
                       ByVal appPath As String, _
                       ByVal docFileName As String) _
                       As AbstractDocument

  '具体的な工場を返す
  Public Shared Function GetFactory(ByVal name As String) As AbstractFactory
    If name = ConstStrings.DATA_SRC_AUTHOR Then
      Return New AuthorsFactory
    Else
      Return New EmployeeFactory
    End If
  End Function

  'DataTable→String配列に変換
  Protected Function GetBodyTexts(ByVal dt As DataTable) As String()
    Dim arrbuf As ArrayList = New ArrayList

    For Each row As DataRow In dt.Rows
      arrbuf.Add(row(0))
    Next
    Return DirectCast(arrbuf.ToArray(GetType(String)), String())
  End Function

  'SqlServerよりデータの取得
  Protected Function GetDataTable(ByVal SqlScript As String) As DataTable

    Const CONNECTION_STRING As String = "Data Source=(local);Initial Catalog=pubs;Integrated Security=SSPI;"
    Dim myDa As CommonDataAccess.AbstructDataAccess = New CommonDataAccess.SqlServerDataAccess
    Try
      myDa.Open(CONNECTION_STRING)
      Return myDa.DataSelect(SqlScript)
    Finally
      If Not myDa Is Nothing Then
        myDa.Close()
      End If
    End Try
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

具体的な文書工場の 1 つ、SQLServer の pubs データベースの authors テーブルを
文書化する工場です。
AbstractFactory を継承しています。

AuthorsFactory.vb
'authorsのデータを基に文書を生産する
Public Class AuthorsFactory
  Inherits AbstractFactory

  'Select文
  Private Const SQL_SCRIPT As String = "SELECT au_fname + ' ' + au_lname AS author_name FROM authors"
  '文書の原料となるDataTable
  Private m_authorsDataTable As DataTable

  'コンストラクタ
  Public Sub New()
    Me.m_authorsDataTable = MyBase.GetDataTable(Me.SQL_SCRIPT)
  End Sub

  'ヘッダ文字列を取得するメソッド実装
  Public Overrides Function GetHeader(ByVal headerText As String) As String
    Dim strBuf As System.Text.StringBuilder = New System.Text.StringBuilder
    With strBuf
      .Append("※※※ ")
      .Append(headerText)
      .Append(" ※※※")
      .Append("(")
      .Append(Me.m_authorsDataTable.Rows.Count.ToString())
      .Append(" 件)")
    End With
    Return strBuf.ToString()
  End Function

  '本文を取得するメソッド実装
  Public Overrides Function GetBody() As String()
    Return MyBase.GetBodyTexts(Me.m_authorsDataTable)
  End Function

  'ドキュメントを返すメソッド実装
  Public Overrides Function GetDocument(ByVal doctype As String, _
                             ByVal appPath As String, _
                             ByVal docFileName As String) As AbstractDocument
    Dim myDocument As AbstractDocument
    Dim basepath As String = System.IO.Path.Combine(appPath, "Authors_" + docFileName)

    If doctype = ConstStrings.OUTPUT_EXCEL Then
      myDocument = New ExcelDocument(basepath)
    Else
      myDocument = New WordDocument(basepath)
    End If
    Return myDocument
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

具体的な文書工場のもう 1 つ、SQLServer の pubs データベースの employee テーブルを
文書化する工場です。
AbstractFactory を継承しています。

EmployeeFactory.vb
'employeeのデータを基に文書を生産する
Public Class EmployeeFactory
  Inherits AbstractFactory

  'Select文
  Private Const SQL_SCRIPT As String = "SELECT fname + ' ' + lname AS employee_name FROM employee"
  '文書の原料となるDataTable
  Private m_employeeDataTable As DataTable

  'コンストラクタ
  Public Sub New()
    Me.m_employeeDataTable = MyBase.GetDataTable(Me.SQL_SCRIPT)
  End Sub

  'ヘッダ文字列を取得するメソッド実装
  Public Overrides Function GetHeader(ByVal headerText As String) As String
    Dim strBuf As System.Text.StringBuilder = New System.Text.StringBuilder
    With strBuf
      .Append("*** ")
      .Append(headerText)
      .Append(" ***")
      .Append("(")
      .Append(Me.m_employeeDataTable.Rows.Count.ToString())
      .Append(" 件)")
    End With
    Return strBuf.ToString()
  End Function

  '本文を取得するメソッド実装
  Public Overrides Function GetBody() As String()
    Return MyBase.GetBodyTexts(Me.m_employeeDataTable)
  End Function

  'ドキュメントを返すメソッド実装
  Public Overrides Function GetDocument(ByVal doctype As String, _
                             ByVal appPath As String, _
                             ByVal docFileName As String) As AbstractDocument
    Dim myDocument As AbstractDocument
    Dim basepath As String = System.IO.Path.Combine(appPath, "Employee_" + docFileName)

    If doctype = ConstStrings.OUTPUT_EXCEL Then
      myDocument = New ExcelDocument(basepath)
    Else
      myDocument = New WordDocument(basepath)
    End If
    Return myDocument
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

抽象的な文書です。

AbstractDocument.vb
'抽象 ドキュメント
Public MustInherit Class AbstractDocument

  'ヘッダ
  Protected m_headerText As String
  '本文
  Protected m_bodyTexts As String()
  'ファイル出力するメソッド定義
  Public MustOverride Function OutPut() As String

  'ヘッダを追加する
  Public Sub AddHeader(ByVal headerText As String)
    Me.m_headerText = headerText
  End Sub

  '本文を追加する
  Public Sub AddBody(ByVal bodyTexts As String())
    Me.m_bodyTexts = bodyTexts
  End Sub

  'COMオブジェクトの解放
  Protected Sub releaseComObject(ByVal comobj As Object)
    Try
      System.Runtime.InteropServices.Marshal.ReleaseComObject(comobj)
    Catch ex As Exception
      'とりあえず握りつぶす
    End Try
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

具体的な文書の 1 つ、Excel 文書です。
AbstractDocument を継承しています。

ExcelDocument.vb
'Excel文書
Public Class ExcelDocument
  Inherits AbstractDocument

  'ファイル名
  Private m_fileName As String

  'コンストラクタ
  Public Sub New(ByVal fileName As String)
    Me.m_fileName = fileName + ".xls"
  End Sub

  'Excel文書出力
  Public Overrides Function OutPut() As String
    Dim excelApp As Excel.Application
    Dim excelBooks As Excel.Workbooks
    Dim excelBook As Excel.Workbook
    Dim excelSheets As Excel.Sheets
    Dim excelSheet As Excel.Worksheet
    Dim range As Excel.Range

    Try
      excelApp = New Excel.Application
      excelBooks = excelApp.Workbooks
      excelBook = excelBooks.Add
      excelSheets = excelBook.Worksheets
      excelSheet = DirectCast(excelSheets.Item(1), Excel.Worksheet)

      'ヘッダ出力
      range = DirectCast(excelSheet.Cells(1, 1), Excel.Range)
      range.Value = MyBase.m_headerText

      '本文を追加する
      For i As Integer = 0 To Me.m_bodyTexts.Length - 1
        Dim rowPos As Integer = i + 3
        range = DirectCast(excelSheet.Cells(rowPos, 1), Excel.Range)
        range.Value = MyBase.m_bodyTexts(i)
      Next

      excelApp.DisplayAlerts = False
      excelBook.SaveAs(Me.m_fileName)
      excelApp.DisplayAlerts = True
      excelBook.Close()
      excelBooks.Close()
      excelApp.Quit()
    Finally
      MyBase.releaseComObject(range)
      MyBase.releaseComObject(excelSheet)
      MyBase.releaseComObject(excelSheets)
      MyBase.releaseComObject(excelBook)
      MyBase.releaseComObject(excelBooks)
      MyBase.releaseComObject(excelApp)
    End Try
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

具体的な文書の 1 つ、Word 文書です。
AbstractDocument を継承しています。

WordDocument.vb
'Word文書
Public Class WordDocument
  Inherits AbstractDocument

  'ファイル名
  Private m_fileName As String

  'コンストラクタ
  Public Sub New(ByVal fileName As String)
    Me.m_fileName = fileName + ".doc"
  End Sub

  'Word文書出力
  Public Overrides Function OutPut() As String
    Dim wordApp As Word.Application
    Dim wordDocs As Word.Documents
    Dim wordDoc As Word.Document
    Dim range As Word.Range
    Dim buff As System.Text.StringBuilder = New System.Text.StringBuilder

    Try
      With buff
        'ヘッダを追加する
        .Append(MyBase.m_headerText)
        .Append(ControlChars.NewLine + ControlChars.NewLine)
        '本文を追加する
        For Each fieldvalue As String In MyBase.m_bodyTexts
          .Append(fieldvalue)
          .Append(ControlChars.NewLine)
        Next
      End With
      wordApp = New Word.Application
      wordDocs = wordApp.Documents
      wordDoc = wordDocs.Add()

      range = wordDoc.Range
      range.Text = buff.ToString()

      wordDoc.SaveAs(DirectCast(Me.m_fileName, Object))
      wordDoc.Close()
      wordApp.Quit()

      Return Me.m_fileName
    Finally
      MyBase.releaseComObject(range)
      MyBase.releaseComObject(wordDoc)
      MyBase.releaseComObject(wordDocs)
      MyBase.releaseComObject(wordApp)
    End Try
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

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

ConstStrings.vb
Public NotInheritable Class ConstStrings
  '出力形式
  Public Const OUTPUT_EXCEL As String = "ExcelDocument"
  Public Const OUTPUT_WORD As String = "WordDocument"

  'データソース
  Public Const DATA_SRC_AUTHOR As String = "Authors"
  Public Const DATA_SRC_EMPLOYEE As String = "Employee"
End Class
このカテゴリーの先頭へ このページの先頭へ

■ひとこと

『抽象的なものを材料にして抽象的なものを作る。』という事がお解かりいただけたでしょうか。
抽象クラスを用いずに、具象クラス(WordDocument クラス等)でコーディングしていたとしたら?
サンプルの場合は、StartUpForm の 1 箇所でしか使用していませんが、
複数の画面において、 『 Employee の Excel や Word の文書を出力する』となっていたとして、
もし、WordDocument にバグがあったら、WordDocument だけでなくて、それを用いている全ての画面を修正しなければなりません。


AbstractFactory クラスの GetFactory メソッドで、具体的な Factory を返却する箇所がありますが、
StartUpForm 以外のクラスを別プロジェクトにし、プロジェクトの出力の種類をクラスライブラリにして、
dllから動的にクラスをロードする方法もありますが、私は別プロジェクトにするのが面倒だったのでやりませんでしたw
動的にクラスをロードなんて、ちょっと格好よさげなテクニックですが、ロードしたいクラス名を入力し間違えていたとしても、
ビルドエラーにならない(= コンパイル解決できない)ので、私はあまりオススメはしません。

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