Decoratorパターン

最終更新:2020/05/07


この記事の内容は

java言語で学ぶデザインパターン

を参考にGo言語に置き換えた内容になっております。

わかりにくい箇所は私の責任であり、java言語で学ぶデザインパターンのせいではありません。

1. Decoratorパターンとは

Decoratorパターンは、透過的なインターフェースを保ったまま、オブジェクトを次々にかぶせて機能を追加します。

go言語はJavaやPythonと異なり継承をサポートしていないので、移譲で実装します。

2. Decoratorパターンの構成

ecoratorパターンは以下のオブジェクトで構成します。

Component

機能を追加するベースになるオブジェクトです。

Componentはインターフェース(API)だけを定めます。

ConcreateComponent

Componentのインターフェースを具体的に実装します。

Decorator

Componentと同じインターフェースを持ちます。

さらにDecoratorが飾る対象となるComponentを持ちます。

goの場合はinterfaceにして、ComponentをConcreateDecoratorで持たせる方が良いでしょう。

ConcreateDecorator

具体的なDecoratorです。

注意

ネットで「golang Decorator」を調べると、関数型を定義してDecoratorを実装するサンプルもよく見かけます。

goのhttp.HandlerFuncなどのコードを見ると、関数型を定義するのがgoでのデファクトの利用方法だと言えるでしょう。

一方で、他の言語でも応用できる考え方と設計力を習得できるように、汎用的なDecoratorパターンの実装も理解しておくとよいと思います。

3. 実装

package decorator import ( "fmt" "strings" "unicode/utf8" ) // Component type Display interface { GetColumns() int GetRows() int GetRowText(int) string Show() } // ConcreateComponent type StringDisplay struct { Str string } func NewStringDisplay(str string) Display { display := &StringDisplay{ Str: str, } return display } // 文字数 func (sd *StringDisplay) GetColumns() int { return utf8.RuneCountInString(sd.Str) } func (sd *StringDisplay) GetRows() int { return 1 } func (sd *StringDisplay) GetRowText(row int) string { if row == 0 { return sd.Str } return "" } func (sd *StringDisplay) Show() { for i := 0; i < sd.GetRows(); i++ { fmt.Println(sd.GetRowText(i)) } } // Decorator type Border interface { Display } // ConcreateDecorator type SideBorder struct { Disp Display Str string } func NewSideBorder(display Display, str string) Border { border := &SideBorder{ Disp: display, Str: str, } return border } func (sb *SideBorder) GetColumns() int { return 1 + sb.Disp.GetColumns() + 1 } func (sd *SideBorder) GetRows() int { return sd.Disp.GetRows() } func (sd *SideBorder) GetRowText(row int) string { return sd.Str + sd.Disp.GetRowText(row) + sd.Str } func (sb *SideBorder) Show() { fmt.Println("SideBorder Show") for i := 0; i < sb.GetRows(); i++ { fmt.Println(sb.GetRowText(i)) } } // ConcreateDecorator type FullBorder struct { Disp Display } func NewFullBorder(display Display) Border { border := &FullBorder{ Disp: display, } return border } func (fb *FullBorder) GetColumns() int { return 1 + utf8.RuneCountInString(fb.Disp.GetRowText(0)) + 1 } func (fb *FullBorder) GetRows() int { return 1 + fb.Disp.GetRows() + 1 } func (fb *FullBorder) GetRowText(row int) string { if row == 0 { return fb.makeLine("+", fb.GetColumns()) } else if row == fb.Disp.GetRows()+1 { return fb.makeLine("+", fb.GetColumns()) } else { return "|" + fb.Disp.GetRowText(row-1) + "|" } } func (fb *FullBorder) makeLine(str string, count int) string { var buf = make([]string, 0) for i := 0; i < count; i++ { buf = append(buf, str) } return strings.Join(buf, "") } func (fb *FullBorder) Show() { for i := 0; i < fb.GetRows(); i++ { fmt.Println(fb.GetRowText(i)) } }

4. 説明

上記のコードの構成は以下の通りです。

Component

Displayインターフェースです。 関数を定義しているだけです。

ConcreateComponent

StringDisplay構造体です。

Displayインターフェースを具体的に実装します。

初期化時に文字列Strを設定して、Displayインターフェースを返すのがポイントです。

func NewStringDisplay(str string) Display { display := &StringDisplay{ Str: str, } return display }

Displayインターフェースを返すことによりコードの柔軟性がグンと増します。

Decorator

Borderインターフェースです。

DecoratorとComponentと同じインターフェースを持つので、Embeddingを使ってDisplayインターフェースを埋め込みます。

type Border interface { Display }

ConcreateDecorator

SideBorder構造体とFullBorder構造体です。

DecoratorであるBorderを具体的に実装します。

type SideBorder struct { Disp Display Str string } func NewSideBorder(display Display, str string) Border { border := &SideBorder{ Disp: display, Str: str, } return border }

Displayインターフェースを保持するのがポイントです。 abstractクラスを作成できる言語の場合、DisplayインターフェースはDecoratorで持たせます。

5. Decoratorパターンを動かす

上記のコードをmain関数で呼び出します。

package main import "github.com/java-lang-programming/marsa/decorator" func main() { stringDisplay := decorator.NewStringDisplay("hello, world") stringDisplay.Show() sideBorder := decorator.NewSideBorder(stringDisplay, "#") sideBorder.Show() fullBorder := decorator.NewFullBorder(sideBorder) fullBorder.Show() disp := decorator.NewFullBorder( decorator.NewSideBorder( decorator.NewFullBorder( decorator.NewStringDisplay("aaaaaa bbbb cccc dd ee")), "*")) disp.Show() }

実行すると以下が出力されます。

# go run main.go hello, world #hello, world# ++++++++++++++++ |#hello, world#| ++++++++++++++++ ++++++++++++++++++++++++++++ |*++++++++++++++++++++++++*| |*|aaaaaa bbbb cccc dd ee|*| |*++++++++++++++++++++++++*| ++++++++++++++++++++++++++++

参考文献 java言語で学ぶデザインパターン