GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Bridge」を学ぶ"
今回は、Pythonで実装した”Bridge”のサンプルアプリをGolangで実装し直してみました。
■ Bridge(ブリッジ・パターン)
Bridgeパターン(ブリッジ・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 「橋渡し」のクラスを用意することによって、クラスを複数の方向に拡張させることを目的とする。
UML class and sequence diagram
UML class diagram
□ 備忘録
Bridgeパターンは、機能のクラス階層と実装のクラス階層を橋渡しするらしいです。
(1) 機能のクラス階層とは?
あるクラスに対して、新しい機能を追加したい場合、新しくサブクラスを定義した上で、メソッドを実装します。
既存のスーパークラスと、新たに定義したサブクラスの関係が、"機能のクラス階層"になります。
一般的には、以下のような関係を想定します。
- スーパークラスは基本的な機能を持っている
- サブクラスで新しい機能を追加する
(2) 実装のクラス階層とは?
新しい実装を追加したい場合、抽象クラスから派生した具体的なサブクラスを定義した上で、メソッドを実装します。
既存の抽象クラスと、新たに派生した具体的なサブクラスの関係が、"実装のクラス階層"になります。
一般的には、以下のような関係を想定します。
- 抽象クラスでは、抽象メソッドによってインタフェースを規定している
- 派生したサブクラスは具象メソッドによってそのインタフェースを実装する
■ "Bridge"のサンプルプログラム
実際に、Bridgeパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
機能のクラス階層と実装のクラス階層を橋渡しを想定したサンプルになります。
-
DisplayFunc
と、DisplayStringImpl
の橋渡しを経て、文字列を表示する -
DisplayCountFunc
と、DisplayStringImpl
の橋渡しを経て、文字列を表示する -
DisplayCountFunc
と、DisplayStringImpl
の橋渡しを経て、文字列を表示する -
DisplayRandomFunc
と、DisplayStringImpl
の橋渡しを経て、文字列を5回繰り返して表示する -
DisplayRandomFunc
と、DisplayStringImpl
の橋渡しを経て、文字列をランダム回数繰り返して表示する -
DisplayFunc
と、DisplayTextfileImpl
の橋渡しを経て、テキストファイルの内容を表示する
$ go run Main.go
+-----------+
|Hello Japan|
+-----------+
+-----------+
|Hello Japan|
+-----------+
+--------------+
|Hello Universe|
+--------------+
+--------------+
|Hello Universe|
|Hello Universe|
|Hello Universe|
|Hello Universe|
|Hello Universe|
+--------------+
+--------------+
|Hello Universe|
|Hello Universe|
+--------------+
aaa
bbb
ccc
ddd
eee
fff
ggg
サンプルプログラムを動かしただけだと、いまいち、何がしたいのかよく分かりませんね。
つづいて、サンプルプログラムの詳細を確認していきます。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Bridge
- ディレクトリ構成
.
├── Main.go
├── bridge
│ ├── display_count_func.go
│ ├── display_func.go
│ ├── display_impl.go
│ ├── display_random_func.go
│ ├── display_string_impl.go
│ └── display_textfile_impl.go
└── test.txt
(1) Abstraction(抽象化)の役
Implement
役のメソッドを使って、基本的な機能だけが実装されているクラスです。
サンプルプログラムでは、DisplayFunc
構造体が、この役を努めます。
package bridge
// DisplayFunc is struct
type DisplayFunc struct {
impl displayImpl
}
// NewDisplayFunc func for initializing DisplayFunc
func NewDisplayFunc(impl displayImpl) *DisplayFunc {
return &DisplayFunc{
impl: impl,
}
}
func (d *DisplayFunc) open() {
d.impl.rawOpen()
}
func (d *DisplayFunc) printBody() {
d.impl.rawPrint()
}
func (d *DisplayFunc) close() {
d.impl.rawClose()
}
//Display func for displaying string
func (d *DisplayFunc) Display() {
d.open()
d.printBody()
d.close()
}
(2) RefinedAbstraction(改善した抽象化)の役
Abstraction
役に対して機能を追加した役です。
サンプルプログラムでは、DisplayCountFunc
構造体と、DisplayRandomFunc
構造体が、この役を努めます。
package bridge
// DisplayCountFunc is struct
type DisplayCountFunc struct {
*DisplayFunc
}
// NewDisplayCountFunc func for initializing DisplayCountFunc
func NewDisplayCountFunc(impl displayImpl) *DisplayCountFunc {
return &DisplayCountFunc{
DisplayFunc: &DisplayFunc{
impl: impl,
},
}
}
//MultiDisplay func for displaying string
func (d *DisplayCountFunc) MultiDisplay(times int) {
d.open()
for i := 0; i < times; i++ {
d.printBody()
}
d.close()
}
package bridge
import (
"math/rand"
"time"
)
// DisplayRandomFunc is struct
type DisplayRandomFunc struct {
*DisplayFunc
}
// NewDisplayRandomFunc func for initializing DisplayRandomFunc
func NewDisplayRandomFunc(impl displayImpl) *DisplayRandomFunc {
return &DisplayRandomFunc{
DisplayFunc: &DisplayFunc{
impl: impl,
},
}
}
//RandomDisplay func for displaying string
func (d *DisplayRandomFunc) RandomDisplay(times int) {
d.open()
rand.Seed(time.Now().UnixNano())
randomTimes := rand.Intn(times)
for i := 0; i < randomTimes; i++ {
d.printBody()
}
d.close()
}
(3) Implementor(実装者)の役
Abstraction
役のインタフェースを実装するためのメソッドを規定する役です。
サンプルプログラムでは、DisplayImpl
インタフェースが、この役を努めます。
package bridge
type displayImpl interface {
rawOpen()
rawPrint()
rawClose()
}
(4) ConcreteImplementor(具体的な実装者)の役
具体的にImplement
役のインタフェースを実装する役です。
サンプルプログラムでは、DisplayStringImpl
構造体と、DisplayTextfileImpl
構造体が、この役を努めます。
package bridge
import (
"fmt"
"strings"
)
// DisplayStringImpl is struct
type DisplayStringImpl struct {
str string
width int
}
// NewDisplayStringImpl func for initializing DisplayStringImpl
func NewDisplayStringImpl(str string) *DisplayStringImpl {
return &DisplayStringImpl{
str: str,
width: len(str),
}
}
func (d *DisplayStringImpl) rawOpen() {
d.printLine()
}
func (d *DisplayStringImpl) rawPrint() {
fmt.Printf("|%s|\n", d.str)
}
func (d *DisplayStringImpl) rawClose() {
d.printLine()
fmt.Println("")
}
func (d *DisplayStringImpl) printLine() {
line := strings.Repeat("-", d.width)
fmt.Printf("+%s+\n", line)
}
package bridge
import (
"fmt"
"os"
)
// DisplayTextfileImpl is struct
type DisplayTextfileImpl struct {
fileName string
f *os.File
}
// NewDisplayTextfileImpl func for initializing DisplayTextfileImpl
func NewDisplayTextfileImpl(str string) *DisplayTextfileImpl {
return &DisplayTextfileImpl{
fileName: str,
}
}
func (d *DisplayTextfileImpl) rawOpen() {
fp, err := os.Open(d.fileName)
if err != nil {
panic(err)
} else {
d.f = fp
}
}
func (d *DisplayTextfileImpl) rawPrint() {
buf := make([]byte, 64)
_, err := d.f.Read(buf)
if err != nil {
panic(err)
}
fmt.Printf("%s\n", buf)
}
func (d *DisplayTextfileImpl) rawClose() {
d.f.Close()
}
(5) Client(依頼人)の役
サンプルプログラムでは、startMain
関数が、この役を努めます。
package main
import (
"./bridge"
)
func startMain() {
d1 := bridge.NewDisplayFunc(bridge.NewDisplayStringImpl("Hello Japan"))
d2 := bridge.NewDisplayCountFunc(bridge.NewDisplayStringImpl("Hello Japan"))
d3 := bridge.NewDisplayCountFunc(bridge.NewDisplayStringImpl("Hello Universe"))
d4 := bridge.NewDisplayRandomFunc(bridge.NewDisplayStringImpl("Hello Universe"))
d5 := bridge.NewDisplayFunc(bridge.NewDisplayTextfileImpl("test.txt"))
d1.Display()
d2.Display()
d3.Display()
d3.MultiDisplay(5)
d4.RandomDisplay(5)
d5.Display()
}
func main() {
startMain()
}