adapter patternが本で出てきたのでこんな感じかなぁと書いてみました。
デザインパターンはストーリ仕立てがわかりやすいと思っているので
しばしお付き合いください
本文
僕は動物対戦ゲームの開発者。
対戦ゲームなので、動物を定義して戦わせる必要があります
動物のinterfaceを定義して、ゲーム内での振る舞いを定義し
それぞれの構造体にロジックを実装していきました。
type Animal interface {
Attack() int
// その他たくさんの定義
}
type Gorilla struct {
attackPoint int
}
func (a *Gorilla) Attack() int {
return a.attackPoint
}
// その他の振る舞い
// func (a *Gorilla) othrerBehaviour() {}
type Lion struct {
attackPoint int
}
func (a *Lion) Attack() int {
return a.attackPoint
}
// func (a *Lion) othrerBehaviour() {}
しかし、ここで特定の動物を合体させたものを登場させたいと要望が入りました。
合体させたものには、それぞれの動物がもつ 攻撃力(Attack()) に合体ボーナスを加算したものを
attackPointととして持ちたいとのこと
ex.)
func (a *Lion) Transform() int {
return a.Attack() + 5 // 動物ごとに異なる合体ボーナス
}
合体できない動物も存在させたいらしく、Animal interface に振る舞いを追加も変だなぁと考えます
今まで作り込んできた構造体の振る舞いは変更したくないし、拡張もしたくない
しかし、既存のクラスのAttack() は流用したい。
そんなときにどうするかというと、2つの作業を行えばスマートに解決できます
- 要望を実現できるinterfaceを定義する
- ↑のinterfaceを実装しつつ、 既存のクラスを継承した構造体を作る
まず、interfaceを定義します
type Parts interface {
Transform() int
}
そして、↑interfaceを実装しつつ、既存のクラスを継承した構造体を作ります
(goでは、継承を構造体への埋め込みによって実現します)
type GorillaParts struct {
*Gorilla
}
func (a *Gorilla) Transform() int {
return a.Attack() + 2
}
type LionParts struct {
*Lion
}
func (a *LionParts) Transform() int {
return a.Attack() + 5
}
そして、動物を合体させたものですが、ここでは Chimera としましょう(かっこいい)
Chimeraは 攻撃ポイントと自身が何で作られているかを持っておきます
type Chimera struct {
parts []Parts
attackPoint int
}
Chimeraを登場させて見ましょう
func NewChimera(parts ...Parts) Animal {
c := &Chimera{
parts: make([]Parts, 0, len(parts)),
}
for _, item := range parts {
c.parts = append(c.parts, item)
c.attackPoint += item.Transform()
}
return c
}
Chimeraはゲーム内で使われるので、Animal を実装させます
func (a *Chimera) Attack() int {
return a.attackPoint
}
ここまで作業すると、こんな感じになります
playground
package main
import "fmt"
func main() {
g := &Gorilla{
attackPoint: 1,
}
l := &Lion{
attackPoint: 3,
}
fmt.Printf("gorilla %v\n", g.Attack())
fmt.Printf("lion %v\n\n", l.Attack())
gp := &GorillaParts{
g,
}
lp := &LionParts{
l,
}
fmt.Printf("gorilla parts %v\n", gp.Transform())
fmt.Printf("lion parts %v\n\n", lp.Transform())
c := NewChimera(gp, lp)
fmt.Printf("chimera %v\n\n", c.Attack())
}
type Parts interface {
Transform() int
}
type Animal interface {
Attack() int
}
type Gorilla struct {
attackPoint int
}
func (a *Gorilla) Attack() int {
return a.attackPoint
}
type GorillaParts struct {
*Gorilla
}
func (a *Gorilla) Transform() int {
return a.Attack() + 2
}
type Lion struct {
attackPoint int
}
func (a *Lion) Attack() int {
return a.attackPoint
}
type LionParts struct {
*Lion
}
func (a *LionParts) Transform() int {
return a.Attack() + 5
}
type Chimera struct {
parts []Parts
attackPoint int
}
func NewChimera(parts ...Parts) Animal {
c := &Chimera{
parts: make([]Parts, 0, len(parts)),
}
for _, item := range parts {
c.parts = append(c.parts, item)
c.attackPoint += item.Transform()
}
return c
}
func (a *Chimera) Attack() int {
return a.attackPoint
}
余談ですが、今後は特定の合体動物が出てくるかもしれないので
合体動物用のinterfaceを定義して、Chimeraに実装しておくとよいかもしれませんね
type CombinedAnimal interface {
Animal
// 合体した動物を解除する
Release() []Parts
}
func NewChimera(parts ...Parts) CombinedAnimal {
c := &Chimera{
parts: make([]Parts, 0, len(parts)),
}
for _, item := range parts {
c.parts = append(c.parts, item)
c.attackPoint += item.Transform()
}
return c
}
func (a *Chimera) Attack() int {
return a.attackPoint
}
func (a *Chimera) Release() []Parts {
return a.parts
}
これで、今まで作り込んできた 動物たちの挙動を保ったまま新しい登場人物を作ることができました
まとめ
既存のクラスの振る舞いを利用して、なにか新しいことをやりたいときには
継承したクラスに新しい振る舞いをもたせて、その中で利用してやると既存のクラスが膨れずに済む