Goでデザインパターン (Decoratorパターン)

Goデザインパターン

Decoratorパターンとは

構造に関するデザインパターンの一つ。ラッパーオブジェクトの中にオブジェクトを持たせることによって、オブジェクトに新しい振る舞いを追加することができる。

長所

  • サブクラスを追加せずにオブジェクトに新しい振る舞いを追加することができる。
  • 追加する振る舞いを実行時に選択できる。
  • 複数のデコレーターを組み合わせることができる。
  • いくつもの可能な振る舞いを実装したクラスを、それぞれの振る舞いを定義した小さなクラスに分割可能(単一責任の原則)

短所

  • 追加されうる振る舞いの分クラスを用意するため、似たようなクラスが大量に作られる可能性がある。
  • 一度ラップしたものから特定のラッパーを削除するのが難しい。
  • ラップされたオブジェクトの振る舞いがデコレーターの順番に依存する。

利用場面

  • 利用する側のコードに変更を加えることなく、追加の振る舞いを実行時に与える必要がある場合。
  • 継承を使ってオブジェクトの振る舞いを拡張できない場合。
    (言語によってはクラス拡張を防止するfinalキーワードが用意されている)

クラス図

  • Component (コンポーネント)
    ラッパー(Decorator)とラップされるオブジェクト(ConcreteComponent)のインターフェースを宣言する。
  • Concrete Component (具象コンポーネント)
    ラップされるオブジェクトを定義する。Componentインターフェースを実装する。
    デコレーターによって変更できる基本的な振る舞いを定義する。
  • Base Decorator (基底デコレーター)
    ラップされたComponentオブジェクトを保持する。すべての操作をラップされたComponentオブジェクトに委任する。
  • Concrete Decorator (具象デコレーター)
    Componentオブジェクトに対して動的に追加可能な振る舞いを定義する。基本的にBase DecoratorのExecute()を呼び出すが、その前後に処理を追加することで振る舞いを追加する。

実装例

Component

package main

// Conponentインターフェースではデコレーターで共通して変更可能な処理を宣言
type INotifier interface {
	Send(message string)
}

Concrete Component

package main

import (
	"fmt"
)

// 具象コンポーネントはデフォルトの処理を実装する。
type Notifier struct{}

func NewNotifier() *Notifier {
	return &Notifier{}
}

// デフォルト処理
func (n *Notifier) Send(message string) {
	fmt.Printf("[Mail] Send message: \"%s\"\n", message)
}

Base Decorator

package main

// Conponentインターフェースで宣言されたAPIを実装する。
type NotifierDecorator struct {
	notifier INotifier
}

func NewNotifierDecorator(n INotifier) *NotifierDecorator {
	return &NotifierDecorator{notifier: n}
}

// 基底デコレーターはラップされたコンポーネントオブジェクトにすべての処理を委任する。
// 追加の振る舞いは具象デコレーターで定義する。
func (d *NotifierDecorator) Send(message string) {
	d.notifier.Send(message)

Concrete Decorator

package main

import (
	"fmt"
)

type SlackDecorator struct {
	nd NotifierDecorator
}

func NewSlackDecorator(n INotifier) *SlackDecorator {
	return &SlackDecorator{
		nd: *NewNotifierDecorator(n),
	}
}

// ラップされたオブジェクトの処理を呼び出す。
// 実行前後に追加の処理を実行したり、処理を実行したデータを引数としてラップされたオブジェクトの処理に渡すことができる。
func (d *SlackDecorator) Send(message string) {
	fmt.Printf("[Slack] Send message: \"%s\"\n", message)
	d.nd.Send(message)
}
package main

import (
	"fmt"
)

type TwitterDecorator struct {
	nd NotifierDecorator
}

func NewTwitterDecorator(n INotifier) *TwitterDecorator {
	return &TwitterDecorator{
		nd: *NewNotifierDecorator(n),
	}
}

func (d *TwitterDecorator) Send(message string) {
	fmt.Printf("[Twitter] Send message: \"%s\"\n", message)
	d.nd.Send(message)
}

動作確認

package main

import (
	"fmt"
)

// クライアント側はComponentインターフェースを介してオブジェクトと通信する。
// そのためComponentインターフェースを実装してさえいれば、クライアント側の変更なしに
// 新しいデコレーターや具象コンポーネントを追加可能。
// デコレーターの組み合わせや順番はクライアント側の責任となる。
func main() {
	fmt.Println("====== Send by only mail ======")
	notifier := NewNotifier()
	notifier.Send("不正な利用を検知しました。")

	fmt.Println("====== Send by mail and slack ======")
	notifierWithSlack := NewSlackDecorator(notifier)
	notifierWithSlack.Send("本日は10時より会議の予定です。")

	fmt.Println("====== Send by mail, slack and twitter ======")
	notifierWithSlackAndTwitter := NewTwitterDecorator(notifierWithSlack)
	notifierWithSlackAndTwitter.Send("今日の天気は晴れです。")
}
>> go run .
====== Send by only mail ======
[Mail] Send message: "不正な利用を検知しました。"     
====== Send by mail and slack ======
[Slack] Send message: "本日は10時より会議の予定です。"
[Mail] Send message: "本日は10時より会議の予定です。" 
====== Send by mail, slack and twitter ======
[Twitter] Send message: "今日の天気は晴れです。"      
[Slack] Send message: "今日の天気は晴れです。"        
[Mail] Send message: "今日の天気は晴れです。"

補足

  • 上記例では教科書的に実装しているが、Go言語では関数を引数として渡すことができるため、基本的には下記のようにするのが簡潔でよいと思われる。
package main

import (
	"fmt"
)

// デコレーターは関数を引数として受け取る。
// 受け取った関数の実行前後に追加処理を行う無名関数(引数関数と同じ型)を戻り値として返す。
func decorator1(f func(str string)) func(str string) {
	return func(s string) {
		fmt.Println("<< Started >>")
		f(s)
		fmt.Println("<< Done! >>")
	}
}

func decorator2(f func(str string)) func(str string) {
	return func(s string) {
		fmt.Println("== Waiting ==")
		f(s)
		fmt.Println("== Processing ==")
	}
}

func test(str string) {
	fmt.Println(str)
}

func main() {
	decorator1(decorator2(test))("Hello World")
}
>> go run .
<< Started >>
== Waiting ==
Hello World
== Processing ==
<< Done! >>

コメント

タイトルとURLをコピーしました