GoFのデザインパターンをGolangで学習してみたいと思います。
今回は、Qiita記事: "Pythonで、デザインパターン「Mediator」を学ぶ"で取り上げた、Pythonベースの”Mediator”のサンプルアプリをGolangで実装し直してみました。
■ Mediator(メディエーター・パターン)
「Mediator」という英単語は、「仲介者」を意味します。
このパターンは、複雑に絡み合った複数のオブジェクト間の関係を、必ず「仲介者」を介して処理を行う様にすることで単純かつ明快なインタフェースを提供するパターンです。つまり、「Mediator」パターンとは、管轄下にある複数のオブジェクト各々からの問い合わせを受け、適宜判断を行い、管轄下にあるオブジェクト全体、または一部へ指示を出す「仲介人」の役割を果たすクラスを利用するパターンです。
UML class and sequence diagram
UML class diagram
(以上、「ITエンジニアのための技術支援サイト by IT専科」より引用)
■ "Mediator"のサンプルプログラム
実際に、Mediatorパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。ここでは、"なんちゃって、ユーザ認証画面"を想像してください。
- ユーザ名"hoge", パスワード"fuga"のユーザが存在するとする
- ユーザ名とパスワードが入力されると、**"login button"**がアクティブになる
- **"login button"**をクリックして、ユーザ認証が成功したか、どうか判定する
なお、サンプルプログラムでは、第一引数:ユーザ名、第二引数:パスワードを指定することにより、"なんちゃって、ユーザ認証画面"に、ユーザ名とパスワードを入力したものとします。
(事例1) 誤ったパスワードを指定して、ユーザ認証が失敗する
ユーザ名は正しく入力されたので、**"login button"**が有効になりましたが、ユーザ認証は失敗しました。
$ go run Main.go hoge huga
(Active login button)
(ID/PW is incorrect)
Login Failed!!
(事例2) パスワードが未指定だったので、ユーザ認証が失敗する
**"login button"**が有効ならずに、ユーザ認証は失敗しました。
$ go run Main.go hoge
Login Failed!!
(事例3) 正しいユーザ名, パスワードを指定して、ユーザ認証が成功する
ユーザ名は正しく入力されたので、**"login button"**が有効になり、ユーザ認証も成功しました。
$ 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
- ディレクトリ構成
.
├── Main.go
└── mediator
├── colleague.go
└── mediator.go
(1) Mediator(調停者、仲介者)の役
Mediator
役は、Colleague
役と通信を行なって、調整を行うためのインタフェースを定めます。
サンプルプログラムでは、Mediator
インタフェースが、この役を努めます。
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
構造体が、この役を努めます。
// 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
構造体が、この役を努めます。
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
構造体が、この役を努めます。
// 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
}
// 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
関数が、この役を努めます。
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)
}