こんにちは。KOUKIです。
以前、Bridgeパターンについて記事を書きましたが、別パターンでも実装したので紹介します。
<目次>
Bridgeパターン
Bridgeパターンは、機能と実装を分離して、それぞれ独立に拡張できるようにしたパターンです。
Printer APIを題材に、どのように実装するのか学んでいきましょう。
Printer API
Printer APIを実装しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package bridge import "errors" type PrinterAPI interface { PrintMessage(string) error } type PrinterImpl1 struct{} func (p *PrinterImpl1) PrintMessage(msg string) error { return errors.New("not implemented yet") } |
PrinterAPIインターフェースが「機能」、PrinterImpl1構造体のPrintMessageが「実装」と考えてください。
PrinterImpl1は、PrinterAPIのPrintMessage機能をメソッドとして定義しているので、PrinterAPIインターフェースの要件を満たしています。
テストコード1
まだ実装は途中ですが、テストコードで動作確認をしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package bridge import "testing" func TestPrintAPI1(t *testing.T) { api1 := PrinterImpl1{} err := api1.PrintMessage("Hello") if err != nil { t.Errorf( "Error trying to use the API1 implementation: %s\n", err.Error()) } } |
PrinterImpl1構造体をインスタンス化して、PrintMessage機能を呼び出します。
現状だと、このテストコードは失敗します。
1 2 3 4 5 6 7 |
$ go test -v -run=TestPrintAPI1 === RUN TestPrintAPI1 bridge_test.go:9: Error trying to use the API1 implementation: not implemented yet --- FAIL: TestPrintAPI1 (0.00s) FAIL exit status 1 FAIL bridge 0.620s |
実装の拡張
テストコードは失敗したままですが、実装の拡張を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package bridge import ( "errors" "io" ) ... type PrinterImpl2 struct { Writer io.Writer } func (p *PrinterImpl2) PrintMessage(msg string) error { return errors.New("not implemented yet") } |
PrinterImpl2構造体を定義し、インターフェースの要件を満たすようにPrintMessageメソッドを実装しました。
ここで注目して欲しいのが、PrinterImpl2のパラーメーターです。
PrinterImpl1と違って、PrinterImpl2ではio.Writerを所持しています。つまり、機能(インターフェース)を変更せずに、io出力処理を拡張できているのです。
テストコード2
PrinterImpl2も実装の途中ですが、テストコードを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
package bridge import ( "errors" "strings" "testing" ) ... type TestWriter struct { Msg string } /* Writeメソッドを実装させ、、PrinterImpl2のパラメータに渡せるようにする type Writer interface { Write(p []byte) (n int, err error) } */ func (t *TestWriter) Write(p []byte) (int, error) { n := len(p) if n > 0 { t.Msg = string(p) return n, nil } err := errors.New("Content received on Writer was emmpty") return n, err } func TestPrintAPI2(t *testing.T) { api2 := PrinterImpl2{} err := api2.PrintMessage("Hello") if err != nil { expectedErrorMessage := "You need to pass an io.Writer to PrinterImpl2" if !strings.Contains(err.Error(), expectedErrorMessage) { t.Errorf( "Error message was not Correct.\n: Actual: %s\n Expected: %s\n", err.Error(), expectedErrorMessage, ) } } testWriter := TestWriter{} api2 = PrinterImpl2{ Writer: &testWriter, } expectedMessage := "Hello" err = api2.PrintMessage(expectedMessage) if err != nil { t.Errorf("Error trying to use the API2 implementation: %s\n", err.Error()) } if testWriter.Msg != expectedMessage { t.Fatalf( "API2 did not write correctly on the io.Writer.\n: Actual: %s\n Expected: %s\n", testWriter.Msg, expectedMessage, ) } } |
TestWriter構造体を作成し、PrinterImpl2のパラメータとして渡せるようにWriteメソッドを実装しました。
テストコードでは、以下の2点をチェックしています。
- io.Writerを渡せているか
- PrintMessage機能のテスト
テストは失敗しますが、一応実行しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ go test -v -run=TestPrintAPI2 === RUN TestPrintAPI2 bridge_test.go:45: Error message was not Correct. : Actual: not implemented yet Expected: You need to pass an io.Writer to PrinterImpl2 bridge_test.go:60: Error trying to use the API2 implementation: not implemented yet bridge_test.go:63: API2 did not write correctly on the io.Writer. : Actual: Expected: Hello --- FAIL: TestPrintAPI2 (0.00s) FAIL exit status 1 FAIL bridge 0.615s |
実装の拡張2
実装の拡張の別パターンも見てみましょう。
1 2 3 4 5 6 7 8 |
type NormalPrinter struct { Msg string Printer PrinterAPI } func (c *NormalPrinter) Print() error { return errors.New("not implemented yet") } |
今度は、PrinterAPIをパラメータに持たせ、メッセージも同時にセットできるようにしました。
これも実装途中ですが、テストコードを実装して動作を確認しましょう。
テストコード3
テストコードを実装すると、機能の使い方がわかるという側面がありますよね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
func TestNormalPrinter_Print(t *testing.T) { expectedMessage := "Hello io.Writer" normal := NormalPrinter{ Msg: expectedMessage, Printer: &PrinterImpl1{}, } err := normal.Print() if err != nil { t.Errorf(err.Error()) } testWriter := TestWriter{} normal = NormalPrinter{ Msg: expectedMessage, Printer: &PrinterImpl2{ Writer: &testWriter, }, } err = normal.Print() if err != nil { t.Error(err.Error()) } if testWriter.Msg != expectedMessage { t.Errorf( "The expected message on the io.Writer doesn't match actual.\n: Actual: %s\n Expected: %s\n", testWriter.Msg, expectedMessage, ) } } |
NormalPrinter構造体のパラメータにPrinterImpl1およびPrinterImpl2をそれぞれ格納することで、おのおののPrint機能が使えてますね。
このテストも失敗しますが、テストコードを実行します。
1 2 3 4 5 6 7 8 9 10 11 |
$ go test -v -run=TestNormalPrinter_Print === RUN TestNormalPrinter_Print bridge_test.go:79: not implemented yet bridge_test.go:90: not implemented yet bridge_test.go:93: The expected message on the io.Writer doesn't match actual. : Actual: Expected: Hello io.Writer --- FAIL: TestNormalPrinter_Print (0.00s) FAIL exit status 1 FAIL bridge 0.632s |
実装の拡張3
NormalPrinterとパラメータは同じですが、以下の構造体を定義します。
1 2 3 4 5 6 7 8 9 |
type PacktPrinter struct { Msg string Printer PrinterAPI } func (c *PacktPrinter) Print() error { return errors.New("not implemented yet") } |
テストコード4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
func TestPacktPrinter_Print(t *testing.T) { passedMessaage := "Hello io.Writer" expectedMessage := "Message from Packt: Hello io.Writer" packt := PacktPrinter{ Msg: passedMessaage, Printer: &PrinterImpl1{}, } err := packt.Print() if err != nil { t.Errorf(err.Error()) } testWriter := TestWriter{} packt = PacktPrinter{ Msg: passedMessaage, Printer: &PrinterImpl2{ Writer: &testWriter, }, } err = packt.Print() if err != nil { t.Error(err.Error()) } if testWriter.Msg != expectedMessage { t.Errorf( "The expected message on the io.Writer doesn't match actual.\n: Actual: %s\n Expected: %s\n", testWriter.Msg, expectedMessage, ) } } |
今度は、passedMessaage変数にPrinterに渡す文字列を設定し、expectedMessage変数は期待した戻り値という役割にしました。
このテストも失敗します。
1 2 3 4 5 6 7 8 9 10 11 |
$ go test -v -run=TestPacktPrinter_Print === RUN TestPacktPrinter_Print bridge_test.go:109: not implemented yet bridge_test.go:120: not implemented yet bridge_test.go:123: The expected message on the io.Writer doesn't match actual. : Actual: Expected: Message from Packt: Hello io.Writer --- FAIL: TestPacktPrinter_Print (0.00s) FAIL exit status 1 FAIL bridge 0.523s |
Test OK
途中だった処理を実装していきましょう。
TestPrintAPI1をOKにする
1 2 3 4 |
func (p *PrinterImpl1) PrintMessage(msg string) error { fmt.Printf("%s\n", msg) return nil } |
1 2 3 4 5 6 |
$ go test -v -run=TestPrintAPI1 === RUN TestPrintAPI1 Hello --- PASS: TestPrintAPI1 (0.00s) PASS ok bridge 0.512s |
TestPrintAPI2をOKにする
1 2 3 4 5 6 7 |
func (p *PrinterImpl2) PrintMessage(msg string) error { if p.Writer == nil { return errors.New("You need to pass an io.Writer to PrinterImpl2") } fmt.Fprintf(p.Writer, "%s", msg) return nil } |
1 2 3 4 5 |
$ go test -v -run=TestPrintAPI2 === RUN TestPrintAPI2 --- PASS: TestPrintAPI2 (0.00s) PASS ok bridge 0.515s |
TestNormalPrinter_PrintをOKにする
1 2 3 4 5 |
func (c *NormalPrinter) Print() error { c.Printer.PrintMessage(c.Msg) return nil } |
1 2 3 4 5 6 |
$ go test -v -run=TestNormalPrinter_Print === RUN TestNormalPrinter_Print Hello io.Writer --- PASS: TestNormalPrinter_Print (0.00s) PASS ok bridge 0.505s |
TestPacktPrinter_PrintをOKにする
1 2 3 4 |
func (c *PacktPrinter) Print() error { c.Printer.PrintMessage(fmt.Sprintf("Message from Packt: %s", c.Msg)) return nil } |
1 2 3 4 5 6 |
$ go test -v -run=TestPacktPrinter_Print === RUN TestPacktPrinter_Print Message from Packt: Hello io.Writer --- PASS: TestPacktPrinter_Print (0.00s) PASS ok bridge 0.539s |
おまけ: カバレッジ
1 2 3 4 5 6 7 |
$ go test -cover Hello Hello io.Writer Message from Packt: Hello io.Writer PASS coverage: 100.0% of statements ok bridge 0.520s |
「coverage: 100.0% of statements」!
おわりに
インターフェースは奥が深いですね。Bridgeパターンでもインターフェースをフルに活用して柔軟なソフトウェアを実装できています。
インターフェースはこちらの記事が大人気なので、よかったら参考にしてください^^
それでは、また!
Go記事まとめ
ソースコード
bridge.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
package bridge import ( "errors" "fmt" "io" ) type PrinterAPI interface { PrintMessage(string) error } type PrinterImpl1 struct{} func (p *PrinterImpl1) PrintMessage(msg string) error { fmt.Printf("%s\n", msg) return nil } type PrinterImpl2 struct { Writer io.Writer } func (p *PrinterImpl2) PrintMessage(msg string) error { if p.Writer == nil { return errors.New("You need to pass an io.Writer to PrinterImpl2") } fmt.Fprintf(p.Writer, "%s", msg) return nil } type NormalPrinter struct { Msg string Printer PrinterAPI } func (c *NormalPrinter) Print() error { c.Printer.PrintMessage(c.Msg) return nil } type PacktPrinter struct { Msg string Printer PrinterAPI } func (c *PacktPrinter) Print() error { c.Printer.PrintMessage(fmt.Sprintf("Message from Packt: %s", c.Msg)) return nil } |
bridge_test.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
package bridge import ( "errors" "strings" "testing" ) func TestPrintAPI1(t *testing.T) { api1 := PrinterImpl1{} err := api1.PrintMessage("Hello") if err != nil { t.Errorf( "Error trying to use the API1 implementation: %s\n", err.Error()) } } type TestWriter struct { Msg string } /* Writeメソッドを実装させ、、PrinterImpl2のパラメータに渡せるようにする type Writer interface { Write(p []byte) (n int, err error) } */ func (t *TestWriter) Write(p []byte) (int, error) { n := len(p) if n > 0 { t.Msg = string(p) return n, nil } err := errors.New("Content received on Writer was emmpty") return n, err } func TestPrintAPI2(t *testing.T) { api2 := PrinterImpl2{} err := api2.PrintMessage("Hello") if err != nil { expectedErrorMessage := "You need to pass an io.Writer to PrinterImpl2" if !strings.Contains(err.Error(), expectedErrorMessage) { t.Errorf( "Error message was not Correct.\n: Actual: %s\n Expected: %s\n", err.Error(), expectedErrorMessage, ) } } testWriter := TestWriter{} api2 = PrinterImpl2{ Writer: &testWriter, } expectedMessage := "Hello" err = api2.PrintMessage(expectedMessage) if err != nil { t.Errorf("Error trying to use the API2 implementation: %s\n", err.Error()) } if testWriter.Msg != expectedMessage { t.Fatalf( "API2 did not write correctly on the io.Writer.\n: Actual: %s\n Expected: %s\n", testWriter.Msg, expectedMessage, ) } } func TestNormalPrinter_Print(t *testing.T) { expectedMessage := "Hello io.Writer" normal := NormalPrinter{ Msg: expectedMessage, Printer: &PrinterImpl1{}, } err := normal.Print() if err != nil { t.Errorf(err.Error()) } testWriter := TestWriter{} normal = NormalPrinter{ Msg: expectedMessage, Printer: &PrinterImpl2{ Writer: &testWriter, }, } err = normal.Print() if err != nil { t.Error(err.Error()) } if testWriter.Msg != expectedMessage { t.Errorf( "The expected message on the io.Writer doesn't match actual.\n: Actual: %s\n Expected: %s\n", testWriter.Msg, expectedMessage, ) } } func TestPacktPrinter_Print(t *testing.T) { passedMessaage := "Hello io.Writer" expectedMessage := "Message from Packt: Hello io.Writer" packt := PacktPrinter{ Msg: passedMessaage, Printer: &PrinterImpl1{}, } err := packt.Print() if err != nil { t.Errorf(err.Error()) } testWriter := TestWriter{} packt = PacktPrinter{ Msg: passedMessaage, Printer: &PrinterImpl2{ Writer: &testWriter, }, } err = packt.Print() if err != nil { t.Error(err.Error()) } if testWriter.Msg != expectedMessage { t.Errorf( "The expected message on the io.Writer doesn't match actual.\n: Actual: %s\n Expected: %s\n", testWriter.Msg, expectedMessage, ) } } |
コメントを残す
コメントを投稿するにはログインしてください。