Abstract Factory (アブストラクトファクトリ) パターン
互いに関連や依存があるクラス達の具象なものは隠蔽し、あくまでも抽象な親同士でインスタンス生成する窓口を用意するデザインパターンです。
抽象的なものを材料にして抽象的なものを作る。でもじつは抽象的なものの中には具象的なものが入ってる。
何のことだかさっぱり解りませんw
サンプルをざーっと読んでいただければ、イメージが沸いてくる...といいなぁ...と思って書いています。
サンプルのアウトプットとして Microsoft Excel および Microsoft Word を用いていますが、
Microsoft Excel 11.0 Object Library および Microsoft Word 11.0 Object Library をプロジェクトの参照設定に追加しています。
■クラス図
ここで用いるサンプルのクラス図です。
準備中...
■サンプルの説明

データソース(テーブル名)を選択し、 出力形式(Excel または Word)を選択し、
タイトル(ファイル名)を入力します。
出力ボタンをクリックすると、アプリケーションの開始ディレクトリに、指定文書が出力されます。
サンプルのプロジェクトダウンロード
■コード
アプリケーションのエントリポイント StartUpForm です。
Button1_Click メソッドの中では、抽象クラスの型で宣言したオブジェクトを使って処理を行っています。
実際に何が入っているのかは、実行時にブレークポイントを付けてウォッチ ウィンドウ等で確認する事ができます。
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
抽象的な文書工場です。
'抽象 文書工場 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 を継承しています。
'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 を継承しています。
'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
抽象的な文書です。
'抽象 ドキュメント 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 を継承しています。
'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 を継承しています。
'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
定数を管理するクラスです。
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
動的にクラスをロードなんて、ちょっと格好よさげなテクニックですが、ロードしたいクラス名を入力し間違えていたとしても、
ビルドエラーにならない(= コンパイル解決できない)ので、私はあまりオススメはしません。