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
□ UML class diagram
■ "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ブラウザで、見た目を確認してみると、こんな感じでした。
(2) TableFactoryを動かしてみる
つづいて、TableベースのWebページを作成するコードを動かしてみます。
$ go run Main.go TableFactory
[LinkPage.html] was created.
LinkPage.html
というファイルが生成されました。
Webブラウザで、見た目を確認してみると、こんな感じでした。
■ サンプルプログラムの詳細
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
構造体と、各種インタフェースが、この役を努めます。
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
インタフェースがこの役を勤めます。
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
関数が、この役を努めます。
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
構造体
// 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
}
// 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
構造体
// 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)
}
// 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(合成)のみが使われる。」**という概念が、鮮明に理解できるようになりました。
この学びは、今度も、大切な経験になりそうです。