最終更新:2020/05/07
この記事の内容は
を参考に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言語で学ぶデザインパターン