はじめに
ZOOM勉強会の議事録です。
「増補改訂版Java言語で学ぶデザインパターン入門」を読んで、プログラム作成・パターンの理解を行います。
第4回はFactory Methodです。
Factory Method
Factory Methodではインスタンス生成の骨組みをスーパークラス側で定め、具体的な肉付けをサブクラスで行います。
第3回で勉強したTemplate Methodをインスタンス生成時の処理に適用したものがFactory Methodパターンになります。
登場する役割は以下の通りです。
- Product 生成されるインスタンスが持つインタフェース
- ConcreteProduct 具体的なインスタンス
- Creator インスタンスを生成する流れを定義
- ConcreteCreator 具体的なインスタンスを作るクラス
リファクタリング前
Factory Methodを導入する前のコードが以下です。
今回はこのコードにFactory Methodを適用していきます。
Sub Main(ByVal args As String())
If args.Length = 0 Then
Exit Sub
End If
Dim command As ICommand
Select Case args(0)
Case 1
If args.Length <> 2 Then
command = New NullCommand(args)
Exit Select
End If
command = New Command1(args(1))
Case 2
If args.Length <> 3 OrElse Not IsNumeric(args(2)) Then
command = New NullCommand(args)
Exit Select
End If
command = New Command2(args(1), args(2))
Case 3
If args.Length <> 4 OrElse Not IsNumeric(args(2)) OrElse Not IsNumeric(args(3)) Then
command = New NullCommand(args)
Exit Select
End If
command = New Command3(args(1), args(2), args(3))
Case 4
If args.Length <> 5 Then
command = New NullCommand(args)
Exit Select
End If
command = New Command4(args(1), args(2), args(3), args(4))
Case 5
If args.Length <> 2 Then
command = New NullCommand(args)
Exit Select
End If
command = New Command5(args(1))
Case Else
command = New NullCommand(args)
End Select
command.StartLog()
command.CommandMain()
command.EndLog()
End Sub
今回のプログラムでは、コマンドライン引数の1つ目によってICommand
インタフェースを実装する具象クラスが生成されます。
コマンドライン引数の2つ目以降で、その具象クラスに必要な引数を指定します。
また、具象クラスによって引数の数と型が違うため、チェックする必要があります。
何も考えずにそれぞれの具象クラスのインスタンスを生成すると上記のような複雑で読みにくいコードになります。
Product
今回のProduct役は、元コードでも生成されていたICommand
インタフェースです。
Public Interface ICommand
Sub StartLog()
Sub CommandMain()
Sub EndLog()
End Interface
ConcreteProduct
ConcreteProduct役は、ICommandクラスを実装した具象クラスです。
今回はコマンドライン引数の1つ目でどの具象クラスを生成するかを切り替えています。
以下に例としてCommand1
とCommand2
を掲載します。
Command1
とCommand2
では、コンストラクタに必要な引数が違うため、生成時に確認する必要があります。
Public Class Command1 : Implements ICommand
Private _name As String
Public Sub New(ByVal arg1 As String)
Me._name = arg1
End Sub
Public Sub StartLog() Implements ICommand.StartLog
Debug.WriteLine(Me._name & ":start1")
End Sub
Public Sub CommandMain() Implements ICommand.CommandMain
Debug.WriteLine(Me._name)
End Sub
Public Sub EndLog() Implements ICommand.EndLog
Debug.WriteLine(Me._name & ":end1")
End Sub
End Class
Public Class Command2 : Implements ICommand
Private _name As String
Private _num As Integer
Public Sub New(ByVal arg1 As String, ByVal arg2 As Integer)
Me._name = arg1
Me._num = arg2
End Sub
Public Sub tartLog() Implements ICommand.StartLog
Debug.WriteLine(Me._name & "," & Me._num & ":start2")
End Sub
Public Sub CommandMain() Implements ICommand.CommandMain
For i = 0 To Me._num
Debug.WriteLine(Me._name)
Next
End Sub
Public Sub EndLog() Implements ICommand.EndLog
Debug.WriteLine(Me._name & "," & Me._num & ":end2")
End Sub
End Class
Creator
インスタンス生成の枠組みを定義します。
今回は各コマンドインスタンス生成時の共通部分を抜き出します。
以下がそのコードです。
''' <summary>
''' Factory MethodのCreator役
''' インスタンス生成の枠組みを定義
''' </summary>
Public MustInherit Class AbstractCommandFactory
Protected _args As String()
''' <summary>
''' インスタンス生成の流れを定義
''' </summary>
''' <param name="args"></param>
''' <returns></returns>
Public Function Create(ByVal args As String()) As ICommand
Me._args = args
If Not IsOkCommandArgs() Then
Return New NullCommand(Me._args)
End If
Return CreateCommand()
End Function
''' <summary>
''' コマンドの引数があっているか確認
''' 問題なければTrue
''' </summary>
''' <returns></returns>
Protected MustOverride Function IsOkCommandArgs() As Boolean
''' <summary>
''' 返すコマンドを生成
''' </summary>
''' <returns></returns>
Protected MustOverride Function CreateCommand() As ICommand
End Class
今回のCreator役AbstractCommandFactory
クラスの中では、抽象メソッドとして以下の2つを用意しています。
IsOkCommandArgs()
今回はコマンドごとに渡す引数の数と型がちがうため、渡す引数が正しいかチェックCreateCommand()
コマンドのインスタンスを生成して返す
Create()
が今回のファクトリーメソッドとなっています。
IsOkCommandArgs()
で引数を確認して、問題なければCreateCommand()
でコマンドを生成して返しています。
この2つのメソッドの具体的な実装は、ConcreteCreatorで実装されます。
ConcreteCreator
Creator役の抽象クラスを継承し、抽象メソッドに肉付けをしていきます。
以下がCommand1
とCommand2
ごとに用意したConcreteFactoryです。
Public Class Command1Factory : Inherits AbstractCommandFactory
''' <summary>
''' Command1のコマンド引数チェック
''' </summary>
''' <returns></returns>
Protected Overrides Function IsOkCommandArgs() As Boolean
Return Me._args.Length = 2
End Function
''' <summary>
''' Command1のインスタンスを返す
''' </summary>
''' <returns></returns>
Protected Overrides Function CreateCommand() As ICommand
Return New Command1(Me._args(1))
End Function
End Class
Public Class Command2Factory : Inherits AbstractCommandFactory
'Command2のコマンド引数チェック
Protected Overrides Function IsOkCommandArgs() As Boolean
Return Me._args.Length = 3 AndAlso IsNumeric(Me._args(2))
End Function
'Command2のインスタンスを返す
Protected Overrides Function CreateCommand() As ICommand
Return New Command2(Me._args(1), Me._args(2))
End Function
End Class
上記のように抽象メソッドの肉付けの仕方で処理を変えることができます。
修正後Main
修正後は以下のようになりました。
Public Sub Main(ByVal args As String())
If args.Length = 0 Then
Exit Sub
End If
Dim command As ICommand
Dim factory As AbstractCommandFactory
Select Case args(0)
Case 1
factory = New Command1Factory()
Case 2
factory = New Command2Factory()
Case 3
factory = New Command3Factory()
Case 4
factory = New Command4Factory()
Case 5
factory = New Command5Factory()
Case Else
factory = New NullCommandFactory()
End Select
command = factory.Create(args)
command.StartLog()
command.CommandMain()
command.EndLog()
End Sub
利点
勉強会で出た利点は以下です。
- 抽象クラスのCreator役により生成の流れを理解しやすくなる
- 共通化により差異だけの記述でよくなる
- 生成に関する情報をまとめることができる
- インスタンス生成時の名前をNewでなくすことができる
- 処理の流れと実装を分離できる
まとめ
今回はFactory Methodパターンを学びました。
Template Methodパターンをインスタンス生成に適用するパターンでした。
Factory Methodを学ぶ過程で、Simple Factoryというものがあることも知りました。
また今回は割愛しましたが、抽象度を変えることでFactoryを生成するFactory Methodクラスを作れるという話もあり、自分でコードを書いて理解することでパターンをいろいろな場面に応用できると感じました。