Golangのデザインパターン

Golangのデザインパターン
Golang
2021年10月10日 更新

Factory Method

仕様:

お支払い方法はお客様を選ばせるように、市場の支払い方法を全部サポートしたいです。

問題:

各支払い方法が、ぞれぞれSDKやドキュメントあり、必要とされているパラメータはバラバラで統一化できません。

回答:

package factorymethod

import "fmt"

type Pay interface {
  Pay(string) int
}

type PayReq struct {
  OrderId string
}

type CreditPayReq struct {
  PayReq
  CardNum string
}

func (p *CreditPayReq) Pay() string {
  fmt.Println(p.OrderId)
  fmt.Println(p.CardNum)
  ...
  // クレカ支払いロジック
  ...
  return "クレカお支払い成功"
}

type XOPayReq struct {
  PayReq
  Uid int64
}

func (p *XOPayReq) Pay() string {
  fmt.Println(p.OrderId)
  fmt.Println(p.Uid)
  ...
  // XO支払いロジック
  ...
  return "XOPay支払い成功"
}

Builder Method

仕様:

ワークフローを定義したい、各ステップは分けることができ、順番通りに進められるようにしたい

問題:

コードを複雑化しそう、ステップ毎が必要とされているデータ式も違ってくる

回答:

package buildermethod

import "fmt"

type Builder interface {
  Step1()
  Step2()
  Step3()
}

type Director struct {
  builder Builder
}

func NewDirector(builder Builder) *Director {
  return &Director{
    builder: builder,
  }
}

func (d *Director) Construct() {
  d.builder.Step1()
  d.builder.Step2()
  d.builder.Step3()
}

type Builder struct {}

func (b *Builder) Step1() {
  fmt.Println("Step1")
}

func (b *Builder) Step2() {
  fmt.Println("Step2")
}

func (b *Builder) Step3() {
  fmt.Println("Step3")
}

Singleton Method

仕様:

サーバーや全体的にアクセスできる変数を作りたい、ですが、管理し辛い点はを避けたい

問題:

グローバル変数はいつ変更されるのはおかしくない

回答:

package singletonmethod

import (
  "sync"
)

type singleton struct {
  Value int
}

type Singleton interface {
  getValue() int
}

func (s singleton) getValue() int {
  return s.Value
}

var (
  instance *singleton
  once     sync.Once
)

func GetInstance(v int) Singleton {
  once.Do(func() {
    instance = &singleton{Value: v}
  })

  return instance
}

Adapter Method

仕様:

倉庫Aは人で確認して商品を運ぶ仕様に対し、

倉庫Bは完全AIで走っています。

ネットで買い物するユーザーに対して、運営側は倉庫Aから出たものか、それとも倉庫Bから出たものが心配することなくなりたい

問題:

倉庫Aと倉庫Bはそれぞれ実行方法違います

回答:

package adaptermethod

type SoukoA interface {
  getItem(string) string
}

type SoukoB interface {
  getItem(string) string
}

func NewSoukoB() M {
  return &getItemFromB{}
}

type getItemFromB struct{}

func (*getItemFromB) getItem(name string) string {
  // ロジック
  return name
}

func NewSoukoA() Cm {
  return &getItemFromA{}
}

type getItemFromA struct{}

func (*getItemFromA) getItem(name string) string {
  // ロジック
  return name
}

type SoukoAdapter interface {
  getItem(string, string) string
}

func NewSoukoAdapter() SoukoAdapter {
  return &NewSoukoAdapter{}
}

type getItemAdapter struct{}

func (*getItemAdapter) getItem(isType string, name string) string {
  if isType == "A" {
    return NewSoukoA().getItem(name)
  }
  return NewSoukoB().getItem(name)
}

Bridge Method

仕様:

現在のシステムにメッセージとメールの二つの機能APIが備えています、ビジネス拡大のため、新しいシステム1作って、こちらのメッセージとメールの機能APIを利用したい。

問題:

今後新しいシステムXに対しても、スムーズに対応できる。

回答:

package bridgemethod

import "fmt"

type SendMessage interface {
  send(text, to string)
}

type sms struct{}

func NewSms() SendMessage {
  return &sms{}
}

func (*sms) send(text, to string) {
  fmt.Println(fmt.Sprintf("send %s to %s sms", text, to))
}

type email struct{}

func NewEmail() SendMessage {
  return &email{}
}

func (*email) send(text, to string) {
  fmt.Println(fmt.Sprintf("send %s to %s email", text, to))
}

type systemA struct {
  method SendMessage
}

func NewSystemA(method SendMessage) *systemA {
  return &systemA{
    method: method,
  }
}

func (m *systemA) SendMessage(text, to string) {
  m.method.send(fmt.Sprintf("[System A] %s", text), to)
}

type systemB struct {
  method SendMessage
}

func NewSystemB(method SendMessage) *systemB {
  return &systemB{
    method: method,
  }
}

func (m *systemB) SendMessage(text, to string) {
  m.method.send(fmt.Sprintf("[System B] %s", text), to)
}

Decorator Method

仕様:

うどん産業として、一番ベースの面とスープはラーメンできます、その上に、トッピングによって、最後の値段の管理したい。

回答:

package decoratormethod

type udon interface {
  getPrice() int
}

type base struct {}

func (p *base) getPrice() int {
  return 500
}

type tomatoTopping struct {
  udon udon
}

func (c *tomatoTopping) getPrice() int {
  udonPrice := c.udon.getPrice()
  return udonPrice + 100
}

type tenpuraTopping struct {
  udon udon
}

func (c *tenpuraTopping) getPrice() int {
  udonPrice := c.udon.getPrice()
  return udonPrice + 200
}

Facade Method

仕様:

Mysql、PostgreSql、Redisのライブラリをコントロールしたい

回答:

package facademethod

import "fmt"


type APIMysql interface {
  TestMysql() string
}

func NewAPIMysql() APIA {
  return &apiRunMysql{}
}

type apiRunMysql struct{}

func (*apiRunMysql) TestMysql() string {
  return "Mysql test api running"
}

type APIPostgreSql interface {
  TestPostgreSql() string
}

func NewAPIPostgreSql() APIB {
  return &apiRunPostgreSql{}
}

type apiRunPostgreSql struct{}

func (*apiRunPostgreSql) TestPostgreSql() string {
  return "PostgreSql test api running"
}

type APIDB interface {
  Test() string
}

func NewAPIDB() API {
  return &apiRun{
    mysql: NewAPIMysql(),
    postgresql: NewAPIPostgreSql(),
  }
}

type apiRun struct {
  mysql APIMysql
  postgresql APIPostgreSql
}

func (a *apiRun) Test() string {
  mysqlRet := a.mysql.TestMysql()
  postgresqlRet := a.postgresql.TestPostgreSql()
  return fmt.Sprintf("%s\n%s", mysqlRet, postgresqlRet)
}

Proxy Method

仕様:

ユーザーの権限によって、アクセスできるページを分けたい

回答:

package proxymethod

import "fmt"

type Subject interface {
  Proxy() string
}

type Proxy struct {
  real RealSubject
}

func (p Proxy) Proxy() string {
  var res string

  // 処理
  p.real.Pre()

  p.real.Real()

  p.real.After()

  return res
}

type RealSubject struct{}

func (RealSubject) Real() {
  fmt.Print("real")
}

func (RealSubject) Pre() {
  fmt.Print("pre:")
}

func (RealSubject) After() {
  fmt.Print(":after")
}

Command Method

仕様:

スマト家電では、今日はテレビ購入した、明日はエアコンを購入するつもりですが、テレビのスイッチのベースしかないので、二つを影響しないように、リモコンは独自にしたいけど、形式はテレビと一緒にしたい。

回答:

package commandmethod

import "fmt"

type button struct {
  command command
}

func (b *button) press() {
  b.command.execute()
}

type command interface {
  execute()
}

type onCommand struct {
  device device
}

func (c *onCommand) execute() {
  c.device.on()
}

type offCommand struct {
  device device
}

func (c *offCommand) execute() {
  c.device.off()
}

type device interface {
  on()
  off()
}

type tv struct{}

func (t *tv) on() {
  fmt.Println("テレビON")
}

func (t *tv) off() {
  fmt.Println("テレビOFF")
}

type airConditioner struct{}

func (t *airConditioner) on() {
  fmt.Println("エアコンON")
}

func (t *airConditioner) off() {
  fmt.Println("エアコンOFF")
}

Observer Method

仕様:

ステータス変更は関連しているクラスに通知を行いたい

回答:

package observer

import "fmt"

type Subject struct {
  observers []Observer
  content   string
}

func NewSubject() *Subject {
  return &Subject{
    observers: make([]Observer, 0),
  }
}

// 関連付け
func (s *Subject) AddObserver(o Observer) {
  s.observers = append(s.observers, o)
}

func (s *Subject) UpdateContext(content string) {
  s.content = content
  s.notify()
}

type Observer interface {
  Do(*Subject)
}

func (s *Subject) notify() {
  for _, o := range s.observers {
    o.Do(s)
  }
}

type Reader struct {
  name string
}

func NewReader(name string) *Reader {
  return &Reader{
    name: name,
  }
}

func (r *Reader) Do(s *Subject) {
  fmt.Println(r.name + " get " + s.content)
}

State Method

仕様:

とある状態になったら、何回やっても、変化を起こさないようにします。例えば、ドアの場合、開いたら、もう一回開けられない、また、壊したドアに対して、操作できないはず。

回答:

package state

import "fmt"

type state interface {
  open(*door)
  close(*door)
}

type door struct {
  opened  state
  closed  state
  damaged state

  currentState state
}

func (d *door) open() {
  d.currentState.open(d)
}

func (d *door) close() {
  d.currentState.close(d)
}

func (d *door) setState(s state) {
  d.currentState = s
}

type opened struct{}

func (o *opened) open(d *door) {
  fmt.Println("ドアすでに開く")
}

func (o *opened) close(d *door) {
  fmt.Println("ドア閉じる")
}

type closed struct{}

func (c *closed) open(d *door) {
  fmt.Println("ドア開く")
}

func (c *closed) close(d *door) {
  fmt.Println("ドアすでに閉めている")
}

type damaged struct{}

func (a *damaged) open(d *door) {
  fmt.Println("ドアが壊した、開けない")
}

func (a *damaged) close(d *door) {
  fmt.Println("ドアが壊した、閉じれない")
}

Strategy Method

仕様:

旅行は、車で行くか、電車で行くか、飛行機で行くのかは3つの方法を選べるようになりたい。

問題:

車、電車、飛行機それぞれ独立しています。

回答:

package strategy

import "fmt"

type Travel struct {
  name     string
  strategy Strategy
}

func NewTravel(name string, strategy Strategy) *Travel {
  return &Travel{
    name:     name,
    strategy: strategy,
  }
}

func (p *Travel) traffic() {
  p.strategy.traffic(p)
}

type Strategy interface {
  traffic(*Travel)
}

type Fly struct{}

func (f *Fly) traffic(t *Travel) {
  fmt.Println(t.name + " 飛行機で旅行")
}

type Train struct{}

func (r *Train) traffic(t *Travel) {
  fmt.Println(t.name + " 電車で旅行")
}

type Drive struct{}

func (w *Drive) traffic(t *Travel) {
  fmt.Println(t.name + " 車で旅行")
}

Visitor Method

仕様:

Structの構造を変えずに、新しい機能を追加したい

回答:

package visitormethod

import "fmt"

type Shape interface {
  accept(visitor)
}

type square struct{}

func (s *square) accept(v visitor) {
  v.visitForSquare(s)
}

type circle struct{}

func (c *circle) accept(v visitor) {
  v.visitForCircle(c)
}

type visitor interface {
  visitForSquare(*square)
  visitForCircle(*circle)
}

type sideCalculator struct{}

func (a *sideCalculator) visitForSquare(s *square) {
  fmt.Println("square side")
}

func (a *sideCalculator) visitForCircle(s *circle) {
  fmt.Println("circle side")
}

type radiusCalculator struct{}

func (a *radiusCalculator) visitForSquare(s *square) {
  fmt.Println("square radius")
}

func (a *radiusCalculator) visitForCircle(c *circle) {
  fmt.Println("circle radius")
}