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

GoFのデザインパターンをGolangで学習してみたいと思います。
今回は、Qiita記事: "[Pythonで、デザインパターン「Mediator」を学ぶ](https://qiita.com/ttsubo/items/6b48497f5e1854017cc5)"で取り上げた、Pythonベースの”Mediator”のサンプルアプリをGolangで実装し直してみました。

# ■ Mediator（メディエーター・パターン）
「Mediator」という英単語は、「仲介者」を意味します。
このパターンは、複雑に絡み合った複数のオブジェクト間の関係を、必ず「仲介者」を介して処理を行う様にすることで単純かつ明快なインタフェースを提供するパターンです。つまり、「Mediator」パターンとは、管轄下にある複数のオブジェクト各々からの問い合わせを受け、適宜判断を行い、管轄下にあるオブジェクト全体、または一部へ指示を出す「仲介人」の役割を果たすクラスを利用するパターンです。

## UML class and sequence diagram
![W3sDesign_Mediator_Design_Pattern_UML.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/01c6c40b-fff7-eb3b-2680-82a10aaf672e.jpeg)
## UML class diagram
![designpattern-mediator01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/87aaa2b4-ea46-97b7-d8ad-3f59c2d46d37.gif)
[(以上、「ITエンジニアのための技術支援サイト by IT専科」より引用)](http://www.itsenka.com/contents/development/designpattern/mediator.html)

# ■ "Mediator"のサンプルプログラム
実際に、Mediatorパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。ここでは、<font color="blue">"なんちゃって、ユーザ認証画面"</font>を想像してください。

- ユーザ名"hoge", パスワード"fuga"のユーザが存在するとする
- ユーザ名とパスワードが入力されると、**"login button"**がアクティブになる
- **"login button"**をクリックして、ユーザ認証が成功したか、どうか判定する


なお、サンプルプログラムでは、第一引数：ユーザ名、第二引数：パスワードを指定することにより、<font color="blue">"なんちゃって、ユーザ認証画面"</font>に、ユーザ名とパスワードを入力したものとします。

## (事例1) 誤ったパスワードを指定して、ユーザ認証が失敗する
ユーザ名は正しく入力されたので、**"login button"**が有効になりましたが、ユーザ認証は失敗しました。

```console
$ go run Main.go hoge huga
(Active login button)
(ID/PW is incorrect)
Login Failed!!
```

## (事例2) パスワードが未指定だったので、ユーザ認証が失敗する
**"login button"**が有効ならずに、ユーザ認証は失敗しました。

```console
$ go run Main.go hoge
Login Failed!!
```

## (事例3) 正しいユーザ名, パスワードを指定して、ユーザ認証が成功する
ユーザ名は正しく入力されたので、**"login button"**が有効になり、ユーザ認証も成功しました。

```console
$ go run Main.go hoge fuga
(Active login button)
(ID/PW is confirmed)
Login Succeed!!
```

以上で、想定どおり、サンプリプログラムが動作しました。

# ■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Mediator

- ディレクトリ構成

```console
.
├── Main.go
└── mediator
    ├── colleague.go
    └── mediator.go
```
## (1) Mediator（調停者、仲介者）の役
`Mediator`役は、`Colleague`役と通信を行なって、調整を行うためのインタフェースを定めます。
サンプルプログラムでは、`Mediator`インタフェースが、この役を努めます。

```go:mediator/mediator.go
package mediator

import "fmt"

// Mediator is interface
type Mediator interface {
	onChange(component *colleague)
	SetColleagues(inputIDObj, inputPwObj *ConcreteColleagueTextArea, buttonObj *ConcreteColleagueButton)
	getAuthentication() bool
}
```
## (2) ConcreteMediator（具体的な調停者、仲介者）の役
`ConcreteMediator`役は、`Mediator`役のインタフェースを実装し、実際の調整を行います。
サンプルプログラムでは、`ConcreteMediator`構造体が、この役を努めます。

```go:mediator/mediator.go
// ConcreteMediator is struct
type ConcreteMediator struct {
	authentication         bool
	inputIDObj, inputPwObj *ConcreteColleagueTextArea
	buttonObj              *ConcreteColleagueButton
}

// NewConcreteMediator func for initializing ConcreteMediator
func NewConcreteMediator() Mediator {
	return &ConcreteMediator{
		authentication: false,
	}
}

// SetColleagues func for setting Objects
func (c *ConcreteMediator) SetColleagues(inputIDObj, inputPwObj *ConcreteColleagueTextArea, buttonObj *ConcreteColleagueButton) {
	c.inputIDObj = inputIDObj
	c.inputPwObj = inputPwObj
	c.buttonObj = buttonObj
}

func (c *ConcreteMediator) onChange(component *colleague) {
	if component.name == "ID" || component.name == "PW" {
		c.refreshButton()
	} else if component.name == "Login" {
		c.tryAuthentication()
	}
}

func (c *ConcreteMediator) refreshButton() {
	if c.inputIDObj.text != "" && c.inputPwObj.text != "" {
		fmt.Println("(Active login button)")
		c.buttonObj.active = true
	}
}

func (c *ConcreteMediator) tryAuthentication() {
	if c.inputIDObj.text == "hoge" && c.inputPwObj.text == "fuga" {
		fmt.Println("(ID/PW is confirmed)")
		c.authentication = true
	} else {
		fmt.Println("(ID/PW is incorrect)")
	}
}

func (c *ConcreteMediator) getAuthentication() bool {
	return c.authentication
}
```
## (3) Colleague（同僚）の役
`Colleague`役は、`Mediator`役と通信を行うインタフェースとを紐付けます。
サンプルプログラムでは、`colleague`構造体が、この役を努めます。

```go:mediator/colleague.go
package mediator

type colleague struct {
	mediator Mediator
	name     string
}

func (c *colleague) onChange() {
	if c.mediator != nil {
		c.mediator.onChange(c)
	}
}
```
## (4) ConcreteColleague（具体的な同僚）の役
`ConcreteColleague`役は、`Colleague`役と紐付けます。
サンプルプログラムでは、`ConcreteColleagueButton`構造体と、`ConcreteColleagueTextArea`構造体が、この役を努めます。

```go:mediator/colleague.go
// ConcreteColleagueButton is struct
type ConcreteColleagueButton struct {
	*colleague
	active bool
}

// NewConcreteColleagueButton func for initializing ConcreteColleagueButton
func NewConcreteColleagueButton(mediatorObj Mediator, name string) *ConcreteColleagueButton {
	return &ConcreteColleagueButton{
		colleague: &colleague{
			mediator: mediatorObj,
			name:     name},
		active: false,
	}
}

// ClickButton func for detecting whether button is active or not
func (c *ConcreteColleagueButton) ClickButton() bool {
	if c.active {
		c.onChange()
	}
	return c.mediator.getAuthentication()
}

// CheckButtonStatus func for detecting whether button is active or not
func (c *ConcreteColleagueButton) CheckButtonStatus() bool {
	return c.active
}
```
```go:mediator/colleague.go
// ConcreteColleagueTextArea is struct
type ConcreteColleagueTextArea struct {
	*colleague
	text string
}

// NewConcreteColleagueTextArea func for initializing ConcreteColleagueTextArea
func NewConcreteColleagueTextArea(mediatorObj Mediator, name string) *ConcreteColleagueTextArea {
	return &ConcreteColleagueTextArea{
		colleague: &colleague{
			mediator: mediatorObj,
			name:     name},
		text: "",
	}
}

// InputText func for putting text
func (c *ConcreteColleagueTextArea) InputText(text string) {
	c.text = text
	c.onChange()
}
```
## (5) Client（依頼人）の役
サンプルプログラムでは、`startMain`関数が、この役を努めます。

```go:Main.go
package main

import (
	"fmt"
	"os"

	"./mediator"
)

func startMain(userid, password string) {
	m := mediator.NewConcreteMediator()
	inputIDObj := mediator.NewConcreteColleagueTextArea(m, "ID")
	inputPwObj := mediator.NewConcreteColleagueTextArea(m, "PW")
	pushButtonObj := mediator.NewConcreteColleagueButton(m, "Login")
	m.SetColleagues(inputIDObj, inputPwObj, pushButtonObj)

	inputIDObj.InputText(userid)
	inputPwObj.InputText(password)
	if pushButtonObj.ClickButton() {
		fmt.Println("Login Succeed!!")
	} else {
		fmt.Println("Login Failed!!")
	}
}

func checkInputData(params []string) (userid, password string) {
	if len(params) == 3 {
		userid = params[1]
		password = params[2]
	} else if len(params) == 2 {
		userid = params[1]
		password = ""
	} else if len(params) == 1 {
		userid = ""
		password = ""
	}
	return userid, password
}

func main() {
	userid, password := checkInputData(os.Args)
	startMain(userid, password)
}
```

# ■ 参考URL
- [Pythonで、デザインパターン「Mediator」を学ぶ](https://qiita.com/ttsubo/items/6b48497f5e1854017cc5)