0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-24

GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Bridge」を学ぶ"

今回は、Pythonで実装した”Bridge”のサンプルアプリをGolangで実装し直してみました。

■ Bridge(ブリッジ・パターン)

Bridgeパターン(ブリッジ・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 「橋渡し」のクラスを用意することによって、クラスを複数の方向に拡張させることを目的とする。

UML class and sequence diagram

W3sDesign_Bridge_Design_Pattern_UML.jpg

UML class diagram

bridge.png
(以上、ウィキペディア(Wikipedia)より引用)

□ 備忘録

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構造体が、この役を努めます。

bridge/display_func.go
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構造体が、この役を努めます。

bridge/display_count_func.go
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()
}
bridge/display_random_func.go
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インタフェースが、この役を努めます。

bridge/display_impl.go
package bridge

type displayImpl interface {
	rawOpen()
	rawPrint()
	rawClose()
}

(4) ConcreteImplementor(具体的な実装者)の役

具体的にImplement役のインタフェースを実装する役です。
サンプルプログラムでは、DisplayStringImpl構造体と、DisplayTextfileImpl構造体が、この役を努めます。

bridge/display_string_impl.go
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)
}
bridge/display_textfile_impl.go
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関数が、この役を努めます。

Main.go
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()
}

■ 参考URL

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?