GKEでGoアプリを動かすためのCI/CDパイプラインを構築する
Kubernetes入門がてらGKEでGoのアプリを動かすためのCI/CDパイプラインを組んでみたのでその作業ログ。これはあくまで入門記事なので本番で運用するレベルではない。
GCPの設定
まずはGCPの設定から。準備としてやることは大体こんな感じ。
- GCPプロジェクトの作成 - 使用するAPIの有効化 - CI用のサービスアカウント作成、権限付与
※以下gcloudコマンドではターミナルのクライアントにて$ gcloud auth login
で認証済みであることを前提とする
GCPプロジェクト作成
コマンドでさくっと作る
$ gcloud projects create <project_name> --set-as-default
<project_name>
は任意のプロジェクト名に置き換える。グローバル?で一意な名前じゃないとプロジェクトIDが少し変わるので面倒。できれば一意なプロジェクト名にしたい。
--set-as-default
を指定することでこれ以降叩くコマンドにいちいちプロジェクトを指定する必要がなくなる。
設定を確認
$ gcloud config list
あと念のため^で作ったプロジェクトが請求先アカウントと接続済みであることを確認しておく
gcr apiを有効にする
CircleCIでビルドしたコンテナイメージをGCRにpushする必要があるので、gcr apiを有効にする
$ gcloud services enable containerregistry.googleapis.com
有効になったか確認
$ gcloud services list
サービスアカウントを作成する
CircleCIからGCRへコンテナイメージをpushするためのサービスアカウントを用意
$ gcloud iam service-accounts create <service_account_name> --display-name "Account for upload docker image from circleci"
<service_account_name>
はそれっぽいアカウント名に置き換える。circleci
とかで一旦はよさそう
サービスアカウントに権限を付与する
$ gcloud projects add-iam-policy-binding <project_id> --member serviceAccount:<service_account_name>@gke-go-sample.iam.gserviceaccount.com --role roles/storage.admin
<project_id>
は先ほど作成したプロジェクトのプロジェクトIDに置き換える。プロジェクトIDはGCPコンソール画面で確認できる
Goアプリケーション
単純にHello, worldを返すだけのAPIを用意する
package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, world!") }) log.Fatal(http.ListenAndServe(":8080", nil)) }
Dockerfileとかは以下 github.com
CircleCI
CIrcleCIではlintとtest, イメージのbuildとpushを行う
以下.circleci/config.yml
version: 2.1 executors: default: docker: - image: circleci/golang:1.13 working_directory: /go/src/github.com/saekis/gke-go-sample environment: - GO111MODULE: "on" gcloud: docker: - image: google/cloud-sdk working_directory: /go/src/github.com/saekis/gke-go-sample environment: - GO111MODULE: "on" commands: restore_module: steps: - restore_cache: name: Restore go modules cache key: go-mod-{{ .Branch }}-{{ checksum "go.mod" }} save_module: steps: - save_cache: name: Save go modules cache key: go-mod-{{ .Branch }}-{{ checksum "go.mod" }} paths: - /go/pkg/mod/cache jobs: setup: executor: name: default steps: - checkout - restore_module - run: go mod download - save_module lint: executor: name: default steps: - checkout - restore_module - run: name: Download lint tool command: go get -u golang.org/x/lint/golint - run: name: golint command: golint -set_exit_status -min_confidence=1.1 ./... - run: name: go vet command: go vet ./... test: executor: name: default steps: - checkout - restore_module - run: name: test command: go test -v ./... build: parameters: env: type: string executor: name: gcloud steps: - checkout - setup_remote_docker - run: name: Authenticate gcloud service account command: | echo $GCP_SERVICE_KEY > gcloud-service-key.json gcloud auth activate-service-account --key-file gcloud-service-key.json gcloud auth configure-docker --quiet - run: name: Build golang app image command: | docker build -t asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-go-app:${CIRCLE_BUILD_NUM} -f docker/golang/Dockerfile . docker tag asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-go-app:${CIRCLE_BUILD_NUM} asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-go-app:latest if [ -n "${CIRCLE_TAG}" ]; then docker tag asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-go-app:${CIRCLE_BUILD_NUM} asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-go-app:${CIRCLE_TAG} fi - run: name: Build nginx image command: | docker build -t asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-nginx:${CIRCLE_BUILD_NUM} -f docker/nginx/Dockerfile . docker tag asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-nginx:${CIRCLE_BUILD_NUM} asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-nginx:latest if [ -n "${CIRCLE_TAG}" ]; then docker tag asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-nginx:${CIRCLE_BUILD_NUM} asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-nginx:${CIRCLE_TAG} fi - run: name: Push golang image to GCR command: docker push asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-go-app - run: name: Push nginx image to GCR command: docker push asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-nginx workflows: build_and_deploy: jobs: - setup - lint: requires: - setup - test: requires: - setup - build: name: build_dev env: dev requires: - lint - test filters: branches: ignore: - stage - master - build: name: build_stg env: stg requires: - lint - test filters: branches: only: stage - build: name: build_prod env: prod requires: - lint - test filters: branches: only: master
プロジェクト名とサービスアカウントのアクセスキーはCircleCIの環境変数にセットする
一応ここまででGitHubにpushされるとCircleCIが動いてGCRにコンテナイメージがpushされるようになる。
GKEの設定
クラスタ作成
$ gcloud container clusters create <cluster_name>
<cluster_name>
は任意のクラスタ名を指定する
クラスタ作成は結構時間かかるので少し待つ
確認
$ kubectl config get-contexts
マニフェスト作成
deplayment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: gke-go-sample-backend labels: app: gke-go-sample-backend spec: replicas: 1 selector: matchLabels: app: gke-go-sample-backend template: metadata: labels: app: gke-go-sample-backend spec: containers: - name: nginx image: asia.gcr.io/gke-go-sample/prod-nginx:latest imagePullPolicy: Always ports: - containerPort: 80 - name: golang image: asia.gcr.io/gke-go-sample/prod-app:latest imagePullPolicy: Always ports: - containerPort: 8080
nginxをリバースプロキシに設定してるので、nginxのportを80、コンテナ間通信するためにgolangのコンテナのポートを8080にする
またコンテナを公開するのでingressも設定する ingress.yaml
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: gke-go-sample-backend-ingress spec: rules: - http: paths: - path: /* backend: serviceName: gke-go-sample-backend servicePort: 80
全てのマニフェストファイルは以下
GKEへデプロイ
マニフェストファイルが用意できたらGKEにデプロイする
$ kubectl apply -f ./k8s
これでマニフェストファイルで設定した項目がGKEに反映される。 コンソールでingressのexternal ipにアクセスしてHello, worldが表示されれば成功。
GoFデザインパターン ~Compositeパターン~
Compositeパターンとは
ファイルシステムのような、中身(ファイル)とその入れ子(ディレクトリ)を再帰的に処理するためのパターン。容器と中身を同一視し、再帰的な構造を作る。
Compositeパターンにおける役割
name | description |
---|---|
Leaf | 容器の中身を表すもの。ファイルシステムの例ではファイルがこの役割。 |
Composite | 容器を表すもの。ファイルシステムの例ではディレクトリ。 |
Component | LeafとCompositeを同一視するためのもの。今回書いたサンプルコードにおけるEntryインターフェースがこれ。 |
Client | Compositeパターンの利用者。 |
コード例
Entryインターフェース
package main type Entry interface { GetName() string GetSize() int64 Add(Entry) error }
ファイルとディレクトリを同一のものとして扱えるようにするためのインターフェース。ファイルクラスとディレクトリクラスがそれぞれこのインターフェースを実装するが、この方法のデメリットは、ファイルクラスでもAdd(Entry) error
のようなディレクトリクラスのためのメソッドを実装しなくてはいけなくなる(ファイルクラスにおけるAdd(Entry) error
はただエラーを返すためだけのメソッド)。
File
package main import "github.com/pkg/errors" type file struct { name string size int64 } func NewFile(name string, size int64) Entry { return &file{ name: name, size: size, } } func (f *file) GetName() string { return f.name } func (f *file) GetSize() int64 { return f.size } func (f *file) Add(Entry) error { return errors.New("This is not directory") }
ファイルを表す構造体。Add()
はディレクトリにおいてのみ必要だが、Entryインターフェースを満たすためにここでも実装する
Directory
package main type directory struct { name string size int64 entries []Entry } func NewDirectory(name string, size int64) Entry { return &directory{ name: name, size: size, } } func (d *directory) GetName() string { return d.name } func (d *directory) GetSize() int64 { return d.size } func (d *directory) Add(entry Entry) error { d.entries = append(d.entries, entry) return nil }
ディレクトリを表す構造体。
Client
package main import ( "flag" "fmt" "io/ioutil" "os" "path/filepath" "strings" ) func main() { os.Exit(run()) } func run() int { flag.Parse() for _, v := range walk(getPath()) { fmt.Printf("%#v", v) } return 0 } func walk(path string) []Entry { files, err := ioutil.ReadDir(path) if err != nil { panic(err) } var entries []Entry for _, file := range files { var entry Entry if file.IsDir() { entry := NewDirectory(file.Name(), file.Size()) entries = append(entries, walk(filepath.Join(path, file.Name()))...) for _, entryInDir := range entries { err := entry.Add(entryInDir) if err != nil { panic("panic!") } } } else { entry = NewFile(file.Name(), file.Size()) } entries = append(entries, entry) } return entries } func getPath() string { var path string if args := flag.Args(); len(args) > 0 { path = args[0] if !strings.HasSuffix(path, "/") { path = path + "/" } } else { path = "./" } return path }
Compositeを利用するClientの実装。walk()
はディレクトリを探索するための関数で、ディレクトリに対しては walk()
の中で walk()
を再帰的に呼び出す。
GoFデザインパターン ~Strategyパターン~
Strategyパターンとは
ある問題に対して複数の解法が存在する場合、コンテキストに合わせてアルゴリズムのスイッチングを簡単に行えるようにするためのパターン。Strategyは「戦略」という意味。
Strategyパターンにおける役割
name | description |
---|---|
Strategy | アルゴリズムを利用するためのインターフェース。 |
ConcreteStrategy | Strategyインターフェースを継承し、アルゴリズムの具体的な実装を行う。 |
Context | 文脈に沿ってStrategyを呼び出す役。Strategyは外部からContextに注入し、ContextとStrategyは疎結合になるようにする。 |
GoでStrategyパターンの簡単な枠組みを実装してみる
$ tree ./ ├── context.go ├── main.go └── strategy ├── strategy.go ├── strategy_alpha.go └── strategy_beta.go
Strategyパッケージ
package strategy type Strategy interface { Call() } type AlphaStrategy struct{} func (*AlphaStrategy) Call() { // Do something } type BetaStrategy struct{} func (*BetaStrategy) Call() { // Do something }
Strategy interface
がアルゴリズムを実行するための窓口になる。アルゴリズムを利用する側は Strategy interface
にのみ依存する。
AlphaStrategy
BetaStrategy
はそれぞれ Strategy interface
を継承していて、アルゴリズムの具体的な実装を行う。
Mainパッケージ
package main import ( "os" "github.com/saekis/go-design-pattern/8_strategy/strategy" ) func main() { var s strategy.Strategy switch os.Args[1] { case "alpha": s = &strategy.AlphaStrategy{} case "beta": s = &strategy.BetaStrategy{} default: return } Context{s}.Exec() } type Context struct { strategy strategy.Strategy } func (c *Context) Exec() { c.strategy.Call() }
Context.Exec()
はアルゴリズムを利用するが、実際のアルゴリズムの処理は注入されたStrategyに委譲する。こうすることで、アルゴリズムを利用する文脈とアルゴリズムの実装を疎結合にすることができるので、アルゴリズムのスイッチングが容易になり、テスタビリティの向上が期待できる。
GoFデザインパターン ~Builderパターン~
こちらを読みながら進める。
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (397件) を見る
Builderパターンとは
複雑なインスタンスなどの生成において、骨組みと部品の生成を分離するパターン。
Builderが各部品を作るインターフェースを持ち、監督者であるDirectorがBuilderのインターフェースを使って部品を組み立てていく。
ここでのポイントは、DirectorはBuilderのインターフェースのみ着目するためBuilderの具体的な実装には依存せず、Builderが交換可能であるということ。
クラス図あまり綺麗に書けなかった..
実装
増補改訂版Java言語で学ぶデザインパターン入門で使われてる例をGoで実装してみる。
ディレクトリ構成は以下
./7_builder ├── builder │ ├── builder.go │ ├── html_builder.go │ └── text_builder.go ├── director ├── director.go │ └── document_builder.go └── main.go
Director
構造の生成過程で監督者となるDirector。Builderインターフェスを使って構造を作っていく。コードが複雑になると複数のDirectorが必要になりそうなので、Director-ConcreteDirectorの関係を作りConcreteDirectorはDirectorインターフェースを実装するようにした。こうすることでClientにおいてDirectorも交換可能となる。
package director import "github.com/saekis/go-design-pattern/7_builder/builder" type Director interface { Build() string } type documentDirector struct { builder builder.Builder } func NewDocumentDirector(builder builder.Builder) Director { return &documentDirector{builder: builder} } func (director *documentDirector) Build() string { director.builder.MakeTitle("title") director.builder.MakeString("string") director.builder.MakeItems([]string{ "item1", "item2", }) director.builder.Close() return director.builder.GetResult() }
Builder
Builderで各部品を生成する具体的な実装をおこなう。
Builderインターフェース
package builder type Builder interface { MakeTitle(string) MakeString(string) MakeItems([]string) Close() GetResult() string }
ConcreteBuilder
TextBuilder
package builder import ( "bytes" "fmt" ) type textBuilderImple struct { buf bytes.Buffer } func NewTextBuilder() Builder { return &textBuilderImple{buf: bytes.Buffer{}} } func (b *textBuilderImple) MakeTitle(title string) { b.buf.WriteString(fmt.Sprintf("「%s」\n\n", title)) } func (b *textBuilderImple) MakeString(str string) { b.buf.WriteString(fmt.Sprintf("%s\n", str)) } func (b *textBuilderImple) MakeItems(items []string) { for _, item := range items { b.buf.WriteString(fmt.Sprintf("・%s\n", item)) } } func (b *textBuilderImple) Close() { // Do nothing } func (b *textBuilderImple) GetResult() string { return b.buf.String() }
HtmlBuilder
package builder import ( "bytes" "fmt" ) type htmlBuilderImple struct { buf bytes.Buffer } func NewHtmlBuilder() Builder { return &htmlBuilderImple{buf: bytes.Buffer{}} } func (b *htmlBuilderImple) MakeTitle(title string) { b.buf.WriteString(fmt.Sprintf("<html><head><title>%s</title></head><body>", title)) b.buf.WriteString(fmt.Sprintf("<h1>%s</h1>", title)) } func (b *htmlBuilderImple) MakeString(str string) { b.buf.WriteString(fmt.Sprintf("<p>%s</p>", str)) } func (b *htmlBuilderImple) MakeItems(items []string) { b.buf.WriteString("<ul>") for _, item := range items { b.buf.WriteString(fmt.Sprintf("<li>%s</li>", item)) } b.buf.WriteString("</ul>") } func (b *htmlBuilderImple) Close() { b.buf.WriteString("</body></html>") } func (b *htmlBuilderImple) GetResult() string { return b.buf.String() }
Client
ClientがBuilderを初期化してそれをDirectorに食わせる。
ClientではDirectorインターフェースのみ着目するため、DirectorとBuilderの具体的な処理は知らない。
package main import ( "fmt" "github.com/saekis/go-design-pattern/7_builder/director" "github.com/saekis/go-design-pattern/7_builder/builder" ) func main() { // Build text b := builder.NewTextBuilder() d := director.NewDocumentDirector(b) result := d.Build() fmt.Println(result) // Build html b = builder.NewHtmlBuilder() d = director.NewDocumentDirector(b) result = d.Build() fmt.Println(result) }
実行結果
$ go run main.go # text 「title」 string ・item1 ・item2 # html <html><head><title>title</title></head><body><h1>title</h1><p>string</p><ul><li>item1</li><li>item2</li></ul></body></html>
実装したもの
GoFデザインパターン ~Prototypeパターン~
こちらを読みながら進める。
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (397件) を見る
Prototypeパターンとは
クラスからインスタンスを生成せず、既存のインスタンスをコピーすることで新しいインスタンスを生成するというのがPrototypeパターン。本書によると、使い所はインスタンス生成が複雑な場合やインスタンスの内容がユーザーによる入力依存でコードによる再現が難しい場合とのこと。それだけだといまいち理解しづらいので本書の例題を試してみる。
実装
ディレクトリ構成は以下
6_prototype ├── framework │ ├── manager.go │ ├── null_product.go │ └── product.go ├── main.go └── product ├── message_box.go └── underline_pen.go
frameworkパッケージのmanager.goがproductインターフェースを使いproductの複製処理を行う。productパッケージにはproductインターフェースを実装した実際のproductを置く。
frameworkパッケージ
Productインターフェース
コピーの対象であるProductのインターフェース。インスタンスのクローン生成の詳細は各Productで実装される。
package framework type Product interface { Use(string) CreateClone() Product }
Manager
Productのコピーを生成する責務を持つManager。ClientはManagerのCreateClone()
を呼ぶことで実際の複製処理が隠蔽された状態でインスタンスのコピーを生成することができる。
またmap型のshowcaseフィールドでクローンの原型となるProductを保持する。インスタンスの複製で呼ばれる Create()
では要求されたProductが存在しない場合にNullProductを返すことでManagerインターフェースを満たすようにする。NullProductは実行されても何も処理が走らないようにする。
package framework type Manager interface { Register(string, Product) Create(string) Product } type managerImple struct { showcase map[string]Product } func NewManager() Manager { return &managerImple{showcase: map[string]Product{}} } func (m *managerImple) Register(name string, proto Product) { m.showcase[name] = proto } func (m *managerImple) Create(protoname string) Product { if p, ok := m.showcase[protoname]; ok { return p.CreateClone() } return NewNullProduct() }
productパッケージ
Goで構造体のクローンをシュッと生成する方法がわからなかったので愚直に原型となる構造体のフィールド値を受け継いだ構造体を生成するようにした。
package product import ( "fmt" "github.com/saekis/go-design-pattern/6_prototype/framework" ) type messageBox struct { charDecorated string } func NewMessageBox(str string) framework.Product { return &messageBox{str} } func (mb *messageBox) Use(s string) { len := len(s) for i := 0; i < len+4; i++ { fmt.Print(mb.charDecorated) } fmt.Println("") fmt.Println(fmt.Sprintf("%s %s %s", mb.charDecorated, s, mb.charDecorated)) for i := 0; i < len+4; i++ { fmt.Print(mb.charDecorated) } fmt.Println("") } func (mb *messageBox) CreateClone() framework.Product { return &messageBox{mb.charDecorated} }
Main
Managerを利用するClient
package main import ( "github.com/saekis/go-design-pattern/6_prototype/framework" "github.com/saekis/go-design-pattern/6_prototype/product" ) func main() { manager := framework.NewManager() up := product.NewUnderlinePen("~") mb1 := product.NewMessageBox("*") mb2 := product.NewMessageBox("/") manager.Register("Strong message", up) manager.Register("Warning box", mb1) manager.Register("Slash box", mb2) p1 := manager.Create("Strong message") p1.Use("Hello World.") p2 := manager.Create("Warning box") p2.Use("Hello World.") p3 := manager.Create("Slash box") p3.Use("Hello World.") }
実行結果
$ go run main.go "Hello World." ~~~~~~~~~~~~ **************** * Hello World. * **************** //////////////// / Hello World. / ////////////////
うーん、なんかいまいちPrototypeパターンのメリットが実感できない。もうちょっとProductが複雑であればメリットが実感できるのだろうか。
ただ良いなと思ったのが、インスタンスのクローン生成処理がProductインターフェースにより抽象化されたことで、それに依存するClientと具体的なProductクラスが分離され疎結合になったこと。もしProductのクローン生成の仕様が変わったとしても、変更はproductパッケージだけになるし、新しい種類のProductを追加したくなってもproductパッケージにProductを追加するだけで済む。のでオープン・クローズドの原則にも準拠できるようになる。
実装したもの
GoFデザインパターン ~Singletonパターン~
こちらを読みながら進める。
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (397件) を見る
Singletonパターンとは
アプリケーションのスコープにおいてインスタンスがたった一つであることを保証するパターン。直接的なインスタンス生成を制限し、インスタンス生成を一箇所で管理することでSingletonパターンを実現する。
実装
Singletonクラス
増補改訂版Java言語で学ぶデザインパターン入門 のJavaの例ではSingletonクラスのコンストラクタのスコープをprivateにすることで直接的なインスタンス生成を制限してるが、Goでは構造体のスコープをパッケージに限定することでこれを実現する。
インスタンスの取得は GetInstance()
でのみ可能となる。
またインスタンス生成の関数を外部に公開せず、パッケージの変数初期化時にインスタンスを生成するようにしている。これはGoにおいて複数スレッドからSingletonクラスを生成される可能性を防ぐためで、他にはinit関数でインスタンスを生成してこれを実現することもできる。
package singleton type singleton struct{} var instance = newInstance() func newInstance() *singleton { return &singleton{} } func GetInstance() *singleton { return instance }
main
package main import ( "fmt" "github.com/saekis/go-design-pattern/5_singleton/singleton" ) func main() { for i := 1; i <= 5; i++ { go func() { singleton.GetInstance() }() } fmt.Scanln() }
実行結果
$ go run main.go Create new instance Exist instance Exist instance Exist instance Exist instance Exist instance
実装したもの
GoFデザインパターン ~Factory Methodパターン~
こちらを読みながら進める。
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (397件) を見る
Factory Methodパターンとは
Template Methodパターンではスーパークラスで処理の枠組みを決め具体的な処理は子クラスで実装したが、それをインスタンス生成に適用したのがFactory Methodパターン。Factory Methodパターンによって、インスタンス生成の枠組みとインスタンス生成の実装を分けることができる。
実装
増補改訂版Java言語で学ぶデザインパターン入門で使われてる例をGoで実装してみる。
ディレクトリ構成は以下
./4_factory_method ├── framework │ ├── creater.go │ ├── factory.go │ └── product.go ├── idcard │ ├── idcard.go │ └── idcard_factory.go └── main.go
frameworkパッケージ
package framework type Creater interface { CreateProduct(string) Product RegisterProduct(Product) } type Product interface { Use() string GetOwner() string } type Factory struct {} func (*Factory) Create(creater Creater, owner string) Product { p := creater.CreateProduct(owner) creater.RegisterProduct(p) return p }
Goで実装しようとするとImplementFactoryにFactoryを継承させることは難しいため、Factory.Create()
にCreaterインターフェースを実装したImplementFactoryを渡すことでポリモーフィズムを実現するようにした
idcardパッケージ
このパッケージがインスタンス生成の具体的な処理を担う
package idcard import ( "fmt" "github.com/saekis/go-design-pattern/4_factory_method/framework" ) type IdCard struct { owner string } func NewIdCard(owner string) framework.Product { return &IdCard{owner: owner} } func (ic *IdCard) Use() string { return fmt.Sprintf("Use %s's card", ic.owner) } func (ic *IdCard) GetOwner() string { return ic.owner } type IdCardFactory struct { owners []string } func NewIdCardFactory() framework.Creater { return &IdCardFactory{owners: []string{}} } func (*IdCardFactory) CreateProduct(owner string) framework.Product { return NewIdCard(owner) } func (factory *IdCardFactory) RegisterProduct(p framework.Product) { factory.owners = append(factory.owners, p.GetOwner()) }
IdCardFactory
構造体がFactory Methodパターンにおけるインスタンス生成の具体的な処理を実装するクラス
Main
Main実装
package main import ( "fmt" "github.com/saekis/go-design-pattern/4_factory_method/framework" "github.com/saekis/go-design-pattern/4_factory_method/idcard" ) func main() { factory := framework.Factory{} creater := idcard.NewIdCardFactory() card1 := factory.Create(creater, "たろう") card2 := factory.Create(creater, "はなこ") card3 := factory.Create(creater, "たかし") fmt.Println(card1.Use()) fmt.Println(card2.Use()) fmt.Println(card3.Use()) }
実行結果
$ go run main.go Use たろう's card Use はなこ's card Use たかし's card