ProtoType (プロトタイプ) パターン
あらかじめ、材料となる原型をコピーしてインスタンスを生成するデザインパターンです。
似たようなクラスを沢山作る場合がある時や、複雑な実装が内部に組み込まれていたりするクラスであり、
それを沢山作りたい場合に向いています。
サンプルでは、ある Form をプロトタイプとし、それを材料としてカスタマイズし、表示することにしました。
業務で応用するとしたら、マスタが沢山あって、各マスタ毎に簡単なメンテナンス画面を作成したい、
という場合でしょうか。
■クラス図
ここで用いるサンプルのクラス図です。
準備中...
■サンプルの説明

StartUpForm にて、設定した条件のフォームを、上記設定のフォームを表示するボタンをクリックすると表示します。
右の図は、フォームのタイトル「 FavoriteGame 」にし、背景色を青にして、データソースを「プログラムより取得したData」にした場合です。
サンプルのプロジェクトダウンロード
■コード
アプリケーションのエントリポイント StartUpForm です。背景色については
ColorDialog コントロールを使用しています。これについての詳細はじゃんぬさんのページを参照下さい。
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 TextBox1 As System.Windows.Forms.TextBox Friend WithEvents Label2 As System.Windows.Forms.Label Friend WithEvents NumericUpDown1 As System.Windows.Forms.NumericUpDown Friend WithEvents NumericUpDown2 As System.Windows.Forms.NumericUpDown Friend WithEvents Label3 As System.Windows.Forms.Label Friend WithEvents Label4 As System.Windows.Forms.Label Friend WithEvents Label5 As System.Windows.Forms.Label Friend WithEvents Button1 As System.Windows.Forms.Button Friend WithEvents Label6 As System.Windows.Forms.Label Friend WithEvents ComboBox1 As System.Windows.Forms.ComboBox Friend WithEvents Button2 As System.Windows.Forms.Button <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() Me.Label1 = New System.Windows.Forms.Label Me.TextBox1 = New System.Windows.Forms.TextBox Me.Label2 = New System.Windows.Forms.Label Me.NumericUpDown1 = New System.Windows.Forms.NumericUpDown Me.NumericUpDown2 = New System.Windows.Forms.NumericUpDown Me.Label3 = New System.Windows.Forms.Label Me.Label4 = New System.Windows.Forms.Label Me.Label5 = New System.Windows.Forms.Label Me.Button1 = New System.Windows.Forms.Button Me.Label6 = New System.Windows.Forms.Label Me.ComboBox1 = New System.Windows.Forms.ComboBox Me.Button2 = New System.Windows.Forms.Button CType(Me.NumericUpDown1, System.ComponentModel.ISupportInitialize).BeginInit() CType(Me.NumericUpDown2, System.ComponentModel.ISupportInitialize).BeginInit() Me.SuspendLayout() ' 'Label1 ' Me.Label1.Location = New System.Drawing.Point(8, 16) Me.Label1.Name = "Label1" Me.Label1.TabIndex = 0 Me.Label1.Text = "Label1" ' 'TextBox1 ' Me.TextBox1.Location = New System.Drawing.Point(120, 16) Me.TextBox1.Name = "TextBox1" Me.TextBox1.TabIndex = 1 Me.TextBox1.Text = "TextBox1" ' 'Label2 ' Me.Label2.Location = New System.Drawing.Point(8, 48) Me.Label2.Name = "Label2" Me.Label2.TabIndex = 2 Me.Label2.Text = "Label2" ' 'NumericUpDown1 ' Me.NumericUpDown1.Location = New System.Drawing.Point(120, 48) Me.NumericUpDown1.Name = "NumericUpDown1" Me.NumericUpDown1.TabIndex = 3 ' 'NumericUpDown2 ' Me.NumericUpDown2.Location = New System.Drawing.Point(120, 72) Me.NumericUpDown2.Name = "NumericUpDown2" Me.NumericUpDown2.TabIndex = 5 ' 'Label3 ' Me.Label3.Location = New System.Drawing.Point(8, 72) Me.Label3.Name = "Label3" Me.Label3.TabIndex = 4 Me.Label3.Text = "Label3" ' 'Label4 ' Me.Label4.Location = New System.Drawing.Point(8, 104) Me.Label4.Name = "Label4" Me.Label4.TabIndex = 6 Me.Label4.Text = "Label4" ' 'Label5 ' Me.Label5.Location = New System.Drawing.Point(120, 104) Me.Label5.Name = "Label5" Me.Label5.Size = New System.Drawing.Size(32, 23) Me.Label5.TabIndex = 7 Me.Label5.Text = "Label5" ' 'Button1 ' Me.Button1.Location = New System.Drawing.Point(152, 104) Me.Button1.Name = "Button1" Me.Button1.TabIndex = 8 Me.Button1.Text = "Button1" ' 'Label6 ' Me.Label6.Location = New System.Drawing.Point(8, 136) Me.Label6.Name = "Label6" Me.Label6.TabIndex = 9 Me.Label6.Text = "Label6" ' 'ComboBox1 ' Me.ComboBox1.Location = New System.Drawing.Point(120, 136) Me.ComboBox1.Name = "ComboBox1" Me.ComboBox1.Size = New System.Drawing.Size(121, 20) Me.ComboBox1.TabIndex = 10 Me.ComboBox1.Text = "ComboBox1" ' 'Button2 ' Me.Button2.Location = New System.Drawing.Point(8, 208) Me.Button2.Name = "Button2" Me.Button2.TabIndex = 11 Me.Button2.Text = "Button2" ' 'StartUpForm ' Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12) Me.ClientSize = New System.Drawing.Size(292, 266) Me.Controls.Add(Me.Button2) Me.Controls.Add(Me.ComboBox1) Me.Controls.Add(Me.Label6) Me.Controls.Add(Me.Button1) Me.Controls.Add(Me.Label5) Me.Controls.Add(Me.Label4) Me.Controls.Add(Me.NumericUpDown2) Me.Controls.Add(Me.Label3) Me.Controls.Add(Me.NumericUpDown1) Me.Controls.Add(Me.Label2) Me.Controls.Add(Me.TextBox1) Me.Controls.Add(Me.Label1) Me.Name = "StartUpForm" Me.Text = "StartUpForm" CType(Me.NumericUpDown1, System.ComponentModel.ISupportInitialize).EndInit() CType(Me.NumericUpDown2, System.ComponentModel.ISupportInitialize).EndInit() Me.ResumeLayout(False) End Sub #End Region 'ドロップダウンリストの値 Private Const DATA_FROM_DB As String = "ACCESSより取得したData" Private Const DATA_FROM_PG As String = "プログラムより取得したData" 'プロトタイプのキー名 Private Const PROTOTYPE_NAME As String = "prototype" 'プロトタイプ管理クラス Private m_manager As PrototypeManager '画面ロード時のイベント Private Sub StartUpForm_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load Me.Text = "ProtoType" Me.Label1.Text = "フォームのタイトル" Me.TextBox1.Text = "" Me.Label2.Text = "フォームのサイズ(幅)" Me.NumericUpDown1.Minimum = Me.Width Me.NumericUpDown1.Maximum = 1000 Me.NumericUpDown1.Value = Me.Width Me.Label3.Text = "フォームのサイズ(高さ)" Me.NumericUpDown2.Minimum = Me.Height Me.NumericUpDown2.Maximum = 1000 Me.NumericUpDown2.Value = Me.Height Me.Label4.Text = "背景色" Me.Label5.Text = "" Me.Label5.BackColor = Me.BackColor Me.Label5.BorderStyle = BorderStyle.FixedSingle Me.Label6.Text = "表示するデータソース" Me.Button1.Text = "参照" Me.Button2.Text = "上記設定のフォームを表示する" Me.Button2.Width = 180 'ComboBoxのリスト生成 Me.ComboBox1.DropDownStyle = ComboBoxStyle.DropDownList Me.ComboBox1.Items.Clear() Me.ComboBox1.Items.Add(Me.DATA_FROM_DB) Me.ComboBox1.Items.Add(Me.DATA_FROM_PG) Me.ComboBox1.SelectedIndex = 0 Me.ComboBox1.Width = 160 'プロトタイプの作成と登録 Me.m_manager = New PrototypeManager Dim prototypeForm As ClonableForm prototypeForm = New ClonableForm 'プロトタイプの作成 Me.m_manager.Register(Me.PROTOTYPE_NAME, prototypeForm) '登録 End Sub '参照ボタンクリック時のイベント Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click Dim cd As ColorDialog = New ColorDialog Try cd.Color = Me.Label5.BackColor cd.FullOpen = True cd.AnyColor = True cd.SolidColorOnly = False cd.ShowHelp = True If cd.ShowDialog(Me) = DialogResult.OK Then Me.Label5.BackColor = cd.Color End If cd.Dispose() Finally If Not cd Is Nothing Then cd.Dispose() End Try End Sub '上記設定のフォームを表示するボタンクリック時のイベント Private Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click '表示するDataTableを設定 Dim displayDataTable As DataTable If Me.ComboBox1.Text = Me.DATA_FROM_DB Then displayDataTable = DisplayDataTableCreator.GetDataTableFromDatabase() ElseIf Me.ComboBox1.Text = Me.DATA_FROM_PG Then displayDataTable = DisplayDataTableCreator.GetDataTableFromProgram() End If 'プロトタイプの取得 Dim createdForm As IComponent = Me.m_manager.Create(Me.PROTOTYPE_NAME) 'プロトタイプをカスタマイズして表示する createdForm.Display(Me.TextBox1.Text, _ New System.Drawing.Size(CType(Me.NumericUpDown1.Text, Integer), _ CType(Me.NumericUpDown2.Text, Integer)), _ Me.Label5.BackColor, _ displayDataTable, _ Me) End Sub End Class
複製を可能にするインターフェースです。
ICloneable インターフェースを継承しています。
'複製を可能にするインターフェース Public Interface IComponent Inherits ICloneable '表示するメソッド定義 Sub Display(ByVal titleString As String, _ ByVal formSize As Size, _ ByVal formBackColor As Color, _ ByVal displaydatatable As DataTable, _ ByVal owner As Form) '複製を作成するメソッド定義 Function CreateClone() As IComponent '自身を破棄するメソッド定義 Sub Dispose(ByVal disposing As Boolean) End Interface
複製が可能な Form です。
IComponent インターフェースを実装しています。
'複製が可能なFormクラス Public Class ClonableForm Inherits System.Windows.Forms.Form Implements IComponent #Region " Windows フォーム デザイナで生成されたコード " Public Sub New() MyBase.New() ' この呼び出しは Windows フォーム デザイナで必要です。 InitializeComponent() ' InitializeComponent() 呼び出しの後に初期化を追加します。 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 フォーム デザイナを使って変更してください。 ' コード エディタを使って変更しないでください。 <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() components = New System.ComponentModel.Container Me.Text = "ClonableForm" End Sub #End Region '自身の複製を作成する Private Function Clone() As Object Implements IComponent.Clone Return Me 'Shallow Copyを返却する End Function '表示 Public Sub Display(ByVal titleString As String, _ ByVal formSize As Size, _ ByVal formBackColor As Color, _ ByVal displaydatatable As DataTable, _ ByVal owner As Form) Implements IComponent.Display Me.Text = titleString Me.Size = formSize Me.BackColor = formBackColor 'DataGridを描画する Dim grid As DataGrid = New DataGrid grid.Location = New System.Drawing.Point(10, 25) grid.Size = New System.Drawing.Size(200, 200) grid.DataSource = displaydatatable Me.Controls.Add(grid) Me.ShowDialog(owner) End Sub '複製を作成する(IComponent型で返却する) Public Function CreateClone() As IComponent Implements IComponent.CreateClone Dim iformObjecet As IComponent iformObjecet = DirectCast(Me.Clone(), IComponent) Return iformObjecet End Function End Class
StartUpForm のヘルパークラス PrototypeManager です。
欲しいオブジェクトのコピーを登録・作成してくれます。
'IComponentインターフェースを利用し、インスタンスの複製を行う Public Class PrototypeManager 'IComponent型が格納されているHashTable Private m_components As Hashtable = New Hashtable 'HashTableに、IComponent型のオブジェクトを登録する Public Sub Register(ByVal name As String, ByVal createdComponent As IComponent) Me.m_components.Add(name, createdComponent) End Sub 'HashTableから、IComponent型のオブジェクトを取得する Public Function Create(ByVal name As String) As IComponent Dim createdForm As IComponent = DirectCast(Me.m_components(name), IComponent) Return createdForm.CreateClone() End Function '破棄 Public Sub Dispose() For Each dic As DictionaryEntry In Me.m_components DirectCast(dic.Value, IComponent).Dispose(True) Next End Sub End Class
StartUpForm のヘルパークラス DisplayDataTableCreator です。
画面に表示したいデータを作成してくれます。
'表示用データ作成 Public NotInheritable Class DisplayDataTableCreator 'Accessよりデータの取得 Public Shared Function GetDataTableFromDatabase() As DataTable Dim mdbpath As String = System.IO.Path.Combine(Application.StartupPath, "MyPrototype.mdb") Dim connection_string As String = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" _ + mdbpath + ";" 'SELECT文 Const SELECT_SCRIPT As String = "SELECT ID, Name FROM wankuma" 'Accessよりデータの取得 Dim myDa As CommonDataAccess.AbstructDataAccess myDa = New CommonDataAccess.OleDataAccess Try myDa.Open(connection_string) Return myDa.DataSelect(SELECT_SCRIPT) Finally myDa.Close() End Try End Function 'プログラム内よりデータの取得 Public Shared Function GetDataTableFromProgram() As DataTable Dim dt As DataTable = New DataTable("ゲーム") dt.Columns.Add("ID", GetType(String)) dt.Columns.Add("Name", GetType(String)) dt.Rows.Add(New String() {"1", "アトランチスの謎"}) dt.Rows.Add(New String() {"2", "けっきょく南極大冒険"}) dt.Rows.Add(New String() {"3", "マッピー"}) dt.Rows.Add(New String() {"4", "エキサイトバイク"}) dt.Rows.Add(New String() {"5", "ロードファイター"}) Return dt End Function End Class
■ひとこと
サンプルでは、StartUpForm の Load 時等に使用するプロトタイプを登録しています。
そして、StartUpForm 自身の Dispose メソッドが呼ばれた時、登録しているプロトタイプのインスタンスを破棄しています。
サンプルでは 1 つしかプロトタイプがありませんが、
業務で画面を作るとき、全ての画面は大体何パターンかに分けられるでしょう。
そのパターン毎にプロトタイプを用意し、使いまわして、独自の部分はデリゲート等で実装したりすると、
ちょっと実装が楽になりそうですね。