デザインパターン勉強会④Factory Method

<h1>はじめに</h1>
ZOOM勉強会の議事録です。
<a href="https://www.amazon.co.jp/%E5%A2%97%E8%A3%9C%E6%94%B9%E8%A8%82%E7%89%88Java%E8%A8%80%E8%AA%9E%E3%81%A7%E5%AD%A6%E3%81%B6%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E5%85%A5%E9%96%80-%E7%B5%90%E5%9F%8E-%E6%B5%A9/dp/4797327030">「増補改訂版Java言語で学ぶデザインパターン入門」</a>を読んで、プログラム作成・パターンの理解を行います。
第4回はFactory Methodです。

<h1>Factory Method</h1>
Factory Methodではインスタンス生成の骨組みをスーパークラス側で定め、具体的な肉付けをサブクラスで行います。
第3回で勉強した<a href="https://qiita.com/tonightoo/items/0c0203f01d3ec963cec5">Template Method</a>をインスタンス生成時の処理に適用したものがFactory Methodパターンになります。
登場する役割は以下の通りです。
<ul><li><b>Product</b></li>生成されるインスタンスが持つインタフェース<li><b>ConcreteProduct</b></li>具体的なインスタンス<li><b>Creator</b></li>インスタンスを生成する流れを定義<li><b>ConcreteCreator</b></li>具体的なインスタンスを作るクラス</ul>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1585226/c7585efb-5e83-18ba-06ca-9f0fb74b3712.png)


<h2>リファクタリング前</h2>
Factory Methodを導入する前のコードが以下です。
今回はこのコードにFactory Methodを適用していきます。

```vb:NonDesign
    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つ目以降で、その具象クラスに必要な引数を指定します。
また、具象クラスによって引数の数と型が違うため、チェックする必要があります。

何も考えずにそれぞれの具象クラスのインスタンスを生成すると上記のような複雑で読みにくいコードになります。

<h2>Product</h2>
今回のProduct役は、元コードでも生成されていた`ICommand`インタフェースです。

```vb:Product
Public Interface ICommand

    Sub StartLog()

    Sub CommandMain()

    Sub EndLog()

End Interface
```

<h2>ConcreteProduct</h2>
ConcreteProduct役は、ICommandクラスを実装した具象クラスです。
今回はコマンドライン引数の1つ目でどの具象クラスを生成するかを切り替えています。
以下に例として`Command1`と`Command2`を掲載します。
`Command1`と`Command2`では、コンストラクタに必要な引数が違うため、生成時に確認する必要があります。

```vb:Command1
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
```

```vb:command2
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
```

<h2>Creator</h2>
インスタンス生成の枠組みを定義します。
今回は各コマンドインスタンス生成時の共通部分を抜き出します。

以下がそのコードです。

```vb: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`クラスの中では、抽象メソッドとして以下の２つを用意しています。
<ul><li>`IsOkCommandArgs()`</li>今回はコマンドごとに渡す引数の数と型がちがうため、渡す引数が正しいかチェック<li>`CreateCommand()`</li>コマンドのインスタンスを生成して返す</ul>

`Create()`が今回のファクトリーメソッドとなっています。
`IsOkCommandArgs()`で引数を確認して、問題なければ`CreateCommand()`でコマンドを生成して返しています。
この2つのメソッドの具体的な実装は、ConcreteCreatorで実装されます。

<h2>ConcreteCreator</h2>
Creator役の抽象クラスを継承し、抽象メソッドに肉付けをしていきます。
以下が`Command1`と`Command2`ごとに用意したConcreteFactoryです。

```vb:Command1Factory
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
```

```vb:Comamnd2Factory
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
```

上記のように抽象メソッドの肉付けの仕方で処理を変えることができます。

<h2>修正後Main</h2>

修正後は以下のようになりました。

```vb:RefactorMain
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
```

<h1>利点</h1>
勉強会で出た利点は以下です。
<ul><li>抽象クラスのCreator役により生成の流れを理解しやすくなる</li><li>共通化により差異だけの記述でよくなる</li><li>生成に関する情報をまとめることができる</li><li>インスタンス生成時の名前をNewでなくすことができる</li><li>処理の流れと実装を分離できる</li></ul>

<h1>まとめ</h1>
今回はFactory Methodパターンを学びました。
Template Methodパターンをインスタンス生成に適用するパターンでした。
Factory Methodを学ぶ過程で、Simple Factoryというものがあることも知りました。
また今回は割愛しましたが、抽象度を変えることでFactoryを生成するFactory Methodクラスを作れるという話もあり、自分でコードを書いて理解することでパターンをいろいろな場面に応用できると感じました。