デザインパターン勉強会⑧Abstract Factory

<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>を読んで、プログラム作成・パターンの理解を行います。
第8回はAbstract Factoryパターンです。

<h1>Abstract Factory</h1>
抽象的な向上が抽象的な部品を組み合わせて、抽象的な製品をつくります。
役割は以下です。

<ul><li><b>AbstractProduct</b></li>抽象的な製品<li><b>ConcreteProduct</b></li>具体的な製品<li><b>AbstractFactory</b></li>抽象的な工場<li><b>ConcreteFactory</b></li>具体的な工場</ul>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1585226/82f21547-8b6f-385e-8fbc-2e5d6aa142d4.png)



<h2>AbstractProduct</h2>
抽象的な製品です。
今回は4つの抽象的な製品があります。

`Item`クラスは、キャプション用の文字列をもらって初期化します。
また、`MakeHtml()`というHTMLの文字列を返すメソッドを定義しています。

```vb:Item
Public MustInherit Class Item
    Protected caption As String

    Public Sub New(ByVal caption As String)
        Me.caption = caption
    End Sub

    Public MustOverride Function MakeHtml() As String
End Class
```
`Link`クラスは、`Item`クラスの機能に追加でURLをもらって初期化するようにしています。

```vb:Link
Public MustInherit Class Link : Inherits Item

    Protected url As String

    Public Sub New(ByVal caption As String, ByVal url As String)
        MyBase.New(caption)
        Me.url = url
    End Sub

End Class
```
`Tray`クラスは、子要素にItemクラスのリストをもつItemクラスになっています。

```vb:Tray
Public MustInherit Class Tray : Inherits Item

    Protected tray As New ArrayList()

    Public Sub New(ByVal caption As String)
        MyBase.New(caption)
    End Sub

    Public Sub Add(ByVal item As Item)
        tray.Add(item)
    End Sub

End Class
```
`Page`クラスは、HTMLのページを表します。
こちらも子要素に`Item`クラスのリストを持っています。
また、`Output()`メソッドで`MakeHtml()`によって生成されるHTMLをファイルに出力する処理を持っています。

```vb:Page
Public MustInherit Class Page
    Protected title As String
    Protected author As String
    Protected content As New ArrayList()

    Public Sub New(ByVal title As String, ByVal author As String)
        Me.title = title
        Me.author = author
    End Sub

    Public Sub Add(ByVal item As Item)
        content.Add(item)
    End Sub

    Public Sub Output()
        Try
            Dim filename As String = title & ".html"
            Using sw As New StreamWriter(filename, False, Encoding.UTF8)
                sw.Write(Me.MakeHtml())
            End Using
            Debug.WriteLine(filename & "を作成しました")
        Catch ex As Exception
            Debug.WriteLine(ex.StackTrace())
        End Try
    End Sub

    Public MustOverride Function MakeHtml() As String

End Class
```

<h2>ConcreteProduct</h2>
具体的な製品です。
今回は`Link`と`Tray`と`Page`の3つを継承した製品を作ります。
List用の部品群とTable用の部品群が用意されています。
Listが箇条書きで、Tableが表形式になるようHtmlを組み立てる部品となっています。

今回は、Table用のみを記載します。

`TableLink`クラスは`url`と`caption`を指定して、テーブルの要素がリンクになっているものです。

```vb:TableLink
    Public Class TableLink : Inherits Link
        Public Sub New(ByVal caption As String, ByVal url As String)
            MyBase.New(caption, url)
        End Sub

        Public Overrides Function MakeHtml() As String
            Return "<td><a href=""" & url & """>" & caption & "</a></td>" & vbCrLf
        End Function
    End Class
```

`TableTray`クラスは、1行目に`caption`、2行目に子要素の数だけ列を作成し子要素を羅列します。

```vb:TableTray
    Public Class TableTray : Inherits Tray

        Public Sub New(ByVal caption As String)
            MyBase.New(caption)
        End Sub

        Public Overrides Function MakeHtml() As String
            Dim buffer As New StringBuilder
            buffer.Append("<td>")
            buffer.Append("<table width=""100%"" border=""1""><tr")
            buffer.Append("<td bgcolor=""#cccccc"" align=""center"" colspan=""" & tray.Count & """><b>" & caption & "</b></td>")
            buffer.Append("</tr>" & vbCrLf)
            buffer.Append("<tr>" & vbCrLf)
            For Each item As Item In tray
                buffer.Append(item.MakeHtml())
            Next
            buffer.Append("</tr></table>")
            buffer.Append("</td>")
            Return buffer.ToString()
        End Function
    End Class
```

`TablePage`クラスはHtmlのページを作成します。
子要素を表の行に入れていきます。

```vb:TablePage
    Public Class TablePage : Inherits Page

        Public Sub New(ByVal title As String, ByVal author As String)
            MyBase.New(title, author)
        End Sub

        Public Overrides Function MakeHtml() As String
            Dim buffer As New StringBuilder
            buffer.Append("<html><head><title>" & title & "</title></head>" & vbCrLf)
            buffer.Append("<body>" & vbCrLf)
            buffer.Append("<h1>" & title & "</h1>" & vbCrLf)
            buffer.Append("<table width=""80%"" border=""3""" & vbCrLf)
            For Each item As Item In content
                buffer.Append("<tr>" & item.MakeHtml() & "</tr>")
            Next
            buffer.Append("</table>" & vbCrLf)
            buffer.Append("<hr><address>" & author & "</address>")
            buffer.Append("</body></html>" & vbCrLf)
            Return buffer.ToString()
        End Function
    End Class
```


<h2>AbstractFactory</h2>
抽象的な工場です。
抽象的な部品を生成するための抽象メソッドと工場のインスタンスを生成するための静的メソッドを用意しています。

```vb:AbstractFactory
Public MustInherit Class Factory

    '工場を生成するためのメソッド
    Public Shared Function GetFactory(ByVal classname As String) As Factory
        Dim factory As Factory = Nothing

        Try
            Dim type = System.Type.GetType(classname)
            factory = CType(System.Activator.CreateInstance(type), Factory)
        Catch ex As Exception
            Debug.WriteLine(ex.StackTrace())
        End Try

        Return factory
    End Function

    Public MustOverride Function CreateLink(ByVal caption As String, ByVal url As String) As Link

    Public MustOverride Function CreateTray(ByVal caption As String) As Tray

    Public MustOverride Function CreatePage(ByVal title As String, ByVal author As String) As Page

End Class
```

`GetFactory()`ではクラス名の文字列を受け取り、`Factory`型に変換し返しています。

`GetFactory()`について、クラス名から`Factory`のインスタンスを生成することで、新しい`Factory`が追加されたとき生成用の分岐を作らなくて済みます。
数字を受け取って、その数字によって具象`Factory`を返すというようなことをしていた場合、新しい`Factory`を追加したときには、新たな分岐処理が必要になります。
クラス名が変更したときには修正の必要がありますが、こういった方法もあるのか～という新しい気づきでした。

<h2>ConcreteFactory</h2>
AbstractFactory役で用意した抽象クラスを実装します。
今回は、Table用・List用といった形で使用する部品群ごとにFactoryを用意します。

```vb:TableFactory
    Public Class TableFactory : Inherits Factory

        Public Overrides Function CreateLink(caption As String, url As String) As Link
            Return New TableLink(caption, url)
        End Function

        Public Overrides Function CreateTray(caption As String) As Tray
            Return New TableTray(caption)
        End Function

        Public Overrides Function CreatePage(title As String, author As String) As Page
            Return New TablePage(title, author)
        End Function
    End Class
```

<h2>Client</h2>
AbstractFactoryを利用する側は以下のようになります。
コマンドライン引数に応じて、ConcreteFactory役が選択されます。
ConcreteFactoryによって、作られるHTMLファイルがごっそり入れ替わります。
また、`Main`の中を見ると具象クラスが出てきておらず、抽象的なクラスにのみ依存しています。


```vb:Main
    Public Sub Main(ByVal args As String())
        If args.Length <> 1 Then
            Exit Sub
        End If

        Dim f = Factory.GetFactory(args(0))

        Dim asahi = f.CreateLink("朝日新聞", "http://www.asahi.com/")
        Dim yomiuri = f.CreateLink("読売新聞", "http://www.yomiuri.co.jp/")
        Dim us_yahoo = f.CreateLink("Yahoo!", "http://www.yahoo.com/")
        Dim jp_yahoo = f.CreateLink("Yahoo!Japan", "http://www.yahoo.co.jp/")
        Dim excite = f.CreateLink("Excite", "http://www.excite.com/")
        Dim google = f.CreateLink("Google", "http://www.google.com/")

        Dim trayNews = f.CreateTray("新聞")
        trayNews.Add(asahi)
        trayNews.Add(yomiuri)

        Dim trayyahoo = f.CreateTray("Yahoo!")
        trayyahoo.Add(us_yahoo)
        trayyahoo.Add(jp_yahoo)

        Dim traySearch = f.CreateTray("サーチエンジン")
        traySearch.Add(trayyahoo)
        traySearch.Add(excite)
        traySearch.Add(google)

        Dim page = f.CreatePage("LinkPage", "tonightoo")
        page.Add(trayNews)
        page.Add(traySearch)
        page.Output()

    End Sub
```





<h2>利点</h2>
<h3>メリット</h3>
<ul><li>工場を抽象化しているので何の部品が必要か理解しやすい</li><li>具象クラスをFactoryまとめることができる</li><li>工場を交換することでプログラムをごっそり入れ替えることができる</li></ul>

<h3>デメリット</h3>
<ul><li>新しい部品を追加するとすべての工場に新たな実装を作る必要がある</li></ul>

<h2>依存関係逆転の法則</h2>
Factoryを導入する前と後で、クラス図は以下のようになります。
形を表す`Shape`インタフェースというものがあり、その具象クラスとして`Square`と`Circle`を定義しています。
Factory導入前は、`Client`が`Square`と`Circle`を生成する必要があるため、2つの具象クラスに依存しています（右図）。
Factory導入後は、`Client`はInterfaceにのみ依存している形になり、依存が解消されています（左図）。
依存関係逆転の法則の実現できる良い方法だと思いました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1585226/b1c42839-8d4f-0d90-397e-2344dd54477b.png)

<h1>まとめ</h1>
今回は、AbstractFactoryパターンを学びました。
今回このパターンを通じて、具象クラスを書く場所を意識するようになりました。
具象クラスを使用しているクラスは依存が発生しているということになります。
AbstractFactoryパターンは、依存を解消できる良い方法と思いました。