LoginSignup
8
5

More than 3 years have passed since last update.

Golangで、デザインパターン「Abstract Factory」を学ぶ

Last updated at Posted at 2020-02-23

GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Abstract Factory」を学ぶ"

今回は、Pythonで実装した”Abstract Factory”のサンプルアプリをGolangで実装し直してみました。

■ Golangで、Pythonクラスを実装し直す際の備忘録

Golang では、Python等でお馴染みの、Inheritance(継承)ではなく、Composition(合成)のみが使われます。

具体的、以下の2点を気をつけて、サンプルアプリの再実装に取り組みました。

  • golangには、オブジェクトのクラス継承の概念が存在しないので、再利用性(Embedded)と多相性(Interface)の活用で対応する必要がある。
  • Pythonの場合だと、動的型付けのおかげて、引数/戻り値の型を意識することなくポリモーフィズムを実践できていたが、Golangの場合では、引数/戻り値の型を柔軟に扱えるようインタフェースを活用する必要がある。

□ 大変、お世話になったQiita記事

■ Abstract Factory(アブストラクトファクトリ)とは?

Abstract Factory パターン(アブストラクト・ファクトリ・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 関連するインスタンス群を生成するための API を集約することによって、複数のモジュール群の再利用を効率化することを目的とする。日本語では「抽象的な工場」と翻訳される事が多い。Kit パターンとも呼ばれる

□ UML class and sequence diagram

W3sDesign_Abstract_Factory_Design_Pattern_UML.jpg

□ UML class diagram

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3130333539352f62393731323034392d616664392d323330332d653539372d3031393136333530633732382e706e67.png
(以上、ウィキペディア(Wikipedia)より引用)

■ "Abstract Factory"のサンプルプログラム

Abstract Factoryパターンとは、抽象的な工場から、抽象的な部品を組み合わせて抽象的な製品を作るらしいです。
実際に、Abstract FactoryのPython実装コードを動かしてみて、抽象的な工場をイメージを理解したいと思います。ここで取り上げるサンプルプログラムは、階層構造を持ったリンク集をHTMLファイルとして作るものです。

  • ListFactoryモードで、動作させると、リスト形式のリンク集のHTMLファイルが生成されます。
  • TableFactoryモードで、動作させると、テーブル形式のリンク集のHTMLファイルが生成されます。

(1) ListFactoryを動かしてみる

まずは、LinkベースのWebページを作成するコードを動かしてみます。

$ go run Main.go ListFactory
[LinkPage.html] was created.

LinkPage.htmlというファイルが生成されました。
Webブラウザで、見た目を確認してみると、こんな感じでした。
listfactory.png

(2) TableFactoryを動かしてみる

つづいて、TableベースのWebページを作成するコードを動かしてみます。

$ go run Main.go TableFactory
[LinkPage.html] was created.

LinkPage.htmlというファイルが生成されました。
Webブラウザで、見た目を確認してみると、こんな感じでした。
tablefactory.png

■ サンプルプログラムの詳細

Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/AbstractFactory
- ディレクトリ構成

.
├── Main.go
└── factory
    ├── factory.go
    ├── list_factory.go
    └── table_factory.go

(1) AbstractProduct(抽象的な製品)の役

AbstractProduct役は、AbstractFactory役によって、作り出される抽象的な部品や製品のインタフェースを定めます。サンプルプログラムでは、Link構造体、Tray構造体、Page構造体と、各種インタフェースが、この役を努めます。

factory.go
package factory

import (
    "fmt"
    "os"
)

...(snip)

// ItemInterface is interface
type ItemInterface interface {
    makeHTML() string
}

// Item is struct
type Item struct {
    caption string
}

// Link is struct
type Link struct {
    *Item
    url string
}

// TrayInterface is interface
type TrayInterface interface {
    ItemInterface
    Add(item ItemInterface)
}

// Tray is struct
type Tray struct {
    *Item
    tray []ItemInterface
}

// Add func for adding item into Tray
func (t *Tray) Add(item ItemInterface) {
    t.tray = append(t.tray, item)
}

// PageInterface is interface
type PageInterface interface {
    ItemInterface
    Add(item ItemInterface)
    Output(o ItemInterface)
}

// Page is struct
type Page struct {
    title, author string
    content       []ItemInterface
}

// Add func for adding item into Page
func (p *Page) Add(item ItemInterface) {
    p.content = append(p.content, item)
}

// Output func for outputing content
func (p *Page) Output(o ItemInterface) {
    filename := fmt.Sprintf("%s.html", p.title)
    file, _ := os.Create(filename)
    defer file.Close()
    b := []byte(o.makeHTML())
    file.Write(b)
    fmt.Printf("[%s] was created.\n", filename)
}

(2) AbstractFactory(抽象的な工場)の役

AbstractFactory役は、AbstractProduct役のインスタンスを作り出すためのインタフェースを定めます。
サンプルプログラムでは、Factoryインタフェースがこの役を勤めます。

factory.go
package factory

import (
    "fmt"
    "os"
)

// Factory is struct
type Factory interface {
    CreateLink(caption, url string) LinkInterface
    CreateTray(caption string) TrayInterface
    CreatePage(title, author string) PageInterface
}

...(snip)

(3) Client(依頼者)の役

Client役は、AbstractFactory役とAbstractProduct役のインタフェースだけを使って仕事を行います。
Client役は、具体的な部品や製品や工場については、知りません。なお、サンプルプログラムでは、startMain関数が、この役を努めます。

Main.go
package main

import (
    "flag"

    "./factory"
)

func startMain(factoryObject factory.Factory) {
    asahi := factoryObject.CreateLink("Asahi", "http://www.asahi.com")
    yomiuri := factoryObject.CreateLink("Yomiuri", "http://www.yomiuri.co.jp")
    usYahoo := factoryObject.CreateLink("Yahoo", "http://www.yahoo.com")
    jaYahoo := factoryObject.CreateLink("Yahoo!Japan", "http://www.yahoo.co.jp")
    google := factoryObject.CreateLink("Google", "http://www.google.com")
    excite := factoryObject.CreateLink("Excite", "http://www.excite.co.jp")

    traynews := factoryObject.CreateTray("Newspaper")
    traynews.Add(asahi)
    traynews.Add(yomiuri)

    trayyahoo := factoryObject.CreateTray("Yahoo!")
    trayyahoo.Add(usYahoo)
    trayyahoo.Add(jaYahoo)

    traysearch := factoryObject.CreateTray("Search Engine")
    traysearch.Add(trayyahoo)
    traysearch.Add(excite)
    traysearch.Add(google)

    page := factoryObject.CreatePage("LinkPage", "Hiroshi Yuki")
    page.Add(traynews)
    page.Add(traysearch)
    page.Output(page)
}

func main() {
    flag.Parse()
    if flag.Arg(0) == "ListFactory" {
        startMain(&factory.ListFactory{})
    } else if flag.Arg(0) == "TableFactory" {
        startMain(&factory.TableFactory{})
    }
}

(4) ConcreteProduct(具体的な製品)の役

ConcreteProduct役は、AbstractProduct役のインタフェースを実装します。サンプルプログラムでは、以下のクラスが、この役を努めます。

  • ListLink構造体、ListTray構造体、ListPage構造体
  • TableLink構造体、TableTray構造体、TablePage構造体
list_factory.go
// ListLink is struct
type ListLink struct {
    *Link
}

func newListLink(caption, url string) *ListLink {
    return &ListLink{
        Link: &Link{
            Item: &Item{
                caption: caption,
            },
            url: url,
        },
    }
}

func (ll *ListLink) makeHTML() string {
    return fmt.Sprintf("  <li><a href=\"%s\">%s</a></li>\n", ll.url, ll.caption)
}

// ListTray is struct
type ListTray struct {
    *Tray
}

func newListTray(caption string) *ListTray {
    return &ListTray{
        Tray: &Tray{
            Item: &Item{
                caption: caption,
            },
        },
    }
}

func (lt *ListTray) makeHTML() string {
    buf := "<li>\n"
    buf += fmt.Sprintf("%s\n", lt.caption)
    buf += "<ul>\n"
    for _, item := range lt.tray {
        buf += item.makeHTML()
    }
    buf += "</ul>\n"
    buf += "</li>\n"
    return buf
}

// ListPage is struct
type ListPage struct {
    *Page
}

func newListPage(title, author string) *ListPage {
    return &ListPage{
        Page: &Page{
            title:  title,
            author: author,
        },
    }
}

func (lp *ListPage) makeHTML() string {
    buf := "<html>\n"
    buf += fmt.Sprintf("  <head><title>%s</title></head>\n", lp.title)
    buf += "<body>\n"
    buf += fmt.Sprintf("<h1>%s</h1>", lp.title)
    buf += "<ul>"
    for _, item := range lp.content {
        buf += item.makeHTML()
    }
    buf += "</ul>"
    buf += fmt.Sprintf("<hr><adress>%s</adress>", lp.author)
    buf += "</body>\n</html>\n"
    return buf
}
table_factory.go
// TableLink is struct
type TableLink struct {
    *Link
}

func newTableLink(caption, url string) *TableLink {
    return &TableLink{
        Link: &Link{
            Item: &Item{
                caption: caption,
            },
            url: url,
        },
    }
}

func (tl *TableLink) makeHTML() string {
    return fmt.Sprintf("<td><a href=%s>%s</a></td>\n", tl.url, tl.caption)
}

// TableTray is struct
type TableTray struct {
    *Tray
}

func newTableTray(caption string) *TableTray {
    return &TableTray{
        Tray: &Tray{
            Item: &Item{
                caption: caption,
            },
        },
    }
}

func (tt *TableTray) makeHTML() string {
    buf := "<td>\n"
    buf += "<table width=\"100%\" border=\"1\"><tr>\n"
    buf += fmt.Sprintf("<td bgcolor=\"#cccccc\" algin=\"center\" colsapn=\"%d\"><b>%s</b></td>\n", len(tt.tray), tt.caption)
    buf += "</tr>\n"
    buf += "<tr>\n"
    for _, item := range tt.tray {
        buf += item.makeHTML()
    }
    buf += "</tr></table>\n"
    buf += "</td>\n"
    return buf
}

// TablePage is struct
type TablePage struct {
    *Page
}

func newTablePage(title, author string) *TablePage {
    return &TablePage{
        Page: &Page{
            title:  title,
            author: author,
        },
    }
}

func (tp *TablePage) makeHTML() string {
    buf := "<html>\n"
    buf += fmt.Sprintf("  <head><title>%s</title></head>\n", tp.title)
    buf += "<body>\n"
    buf += fmt.Sprintf("<h1>%s</h1>", tp.title)
    buf += "<table width=\"80%\" border=\"3\">\n"
    for _, item := range tp.content {
        buf += fmt.Sprintf("<tr>%s</tr>", item.makeHTML())
    }
    buf += "</table>"
    buf += fmt.Sprintf("<hr><adress>%s</adress>", tp.author)
    buf += "</body>\n</html>\n"
    return buf
}

(5) ConcreteFactory(具体的な工場)の役

ConcreteFactory役は、AbstractFactory役のインタフェースを実装します。サンプルプログラムでは、以下の構造体が、この役を努めます。

  • ListFactory構造体
  • TableFactory構造体
list_factory.go
// ListFactory is struct
type ListFactory struct {
}

// CreateLink func for creating Link
func (lf *ListFactory) CreateLink(caption, url string) ItemInterface {
    return newListLink(caption, url)
}

// CreateTray func for creating Tray
func (lf *ListFactory) CreateTray(caption string) TrayInterface {
    return newListTray(caption)
}

// CreatePage func for creating page
func (lf *ListFactory) CreatePage(title, author string) PageInterface {
    return newListPage(title, author)
}
table_factory.go
// TableFactory is struct
type TableFactory struct {
}

// CreateLink func for creating Link
func (tf *TableFactory) CreateLink(caption, url string) ItemInterface {
    return newTableLink(caption, url)
}

// CreateTray func for creating Tray
func (tf *TableFactory) CreateTray(caption string) TrayInterface {
    return newTableTray(caption)
}

// CreatePage func for creating page
func (tf *TableFactory) CreatePage(title, author string) PageInterface {
    return newTablePage(title, author)
}

■ 終わりに...

Golangで、Pythonの継承クラスを実装し直してみると、「Golang では、Python等でお馴染みの、Inheritance(継承)ではなく、Composition(合成)のみが使われる。」という概念が、鮮明に理解できるようになりました。
この学びは、今度も、大切な経験になりそうです。

■ 参考URL

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5