golang/design pattern

golang design pattern #11 Chain of Responsibility

PudgeKim 2021. 11. 16. 12:56

Chain of Responsibility 패턴은 어떠한 요청을 다음 대상에게 넘겨주는 패턴으로 여러 이벤트를 체인을 거치면서 처리하게 됩니다.

 

예제 코드를 알아보기 전에 golang의 embedding에 관한 특성 중 하나를 알아야 할게 있습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
type Monster interface {
    Attack()
}
 
type Goblin struct {}
 
func (g *Goblin) Attack() {
    fmt.Println("goblin attacks!")
}
 
type GoblinKing struct {
    Goblin
}
cs

위 코드에서 GoblinKing 구조체에서 Goblin을 embedding 하였습니다.

이렇게 하면 GoblinKing 객체에서 Goblin의 메서드들을 사용할 수 있는데 
이는 Goblin이 정의한 interface도 적용된다는 뜻입니다.

즉, Goblin이 Monster 타입이므로 GoblinKing 역시 Monster 타입이 적용됩니다.

여기서 흥미로운 점은 아래와 같은 코드도 작성할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type GoblinKing struct {
    Goblin
}
 
func (gk *GoblinKing) Attack() {
    fmt.Println("goblinKing attacks!")
    gk.Goblin.Attack()
}
 
func Test(monster Monster) {
    monster.Attack()
}
 
func main() {
    gk := GoblinKing{Goblin{}}
    Test(&gk)
}
cs

GoblinKing의 메서드로 Goblin이 가지고 있는 Attack 메서드를 정의하고
거기서 Goblin의 Attack 메서드도 사용할 수 있습니다.

코드를 실행해보면 "goblinKing attacks!"와 "goblin attacks!" 가 모두 출력됩니다.

 

 

이제 Chain of Responsibility의 예제 코드를 알아보겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
type Monster struct {
    name string
    attack int
    defense int
}
 
func NewMonster(name string, attack, defense int) *Monster {
    return &Monster{name, attack, defense}
}
 
func (m Monster) String() string {
    return fmt.Sprintf("%s stat: (%d %d)", m.name, m.attack, m.defense)
}
cs

Monster 구조체를  정의하고 나중에 Print함수를 이용하여 결과를 보기 쉽게 하기 위하여 String 메서드를 정의하였습니다.

** 여기서 Monster는 맨 위의 코드와는 달리 struct 입니다.

 

1
2
3
4
type Modifier interface {
    AddAbility(m Modifier)
    Apply()
}
cs

Modifier 인터페이스를 정의합니다.

AddAbility는 Monster의 attack 또는 defense를 증가시키고,
Apply는 한번 사용하면 그 때까지 chain들에 있는 Ability들을 적용시켜줍니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type MonsterModifier struct {
    monster *Monster
    next Modifier
}
 
func NewMonsterModifier(monster *Monster) *MonsterModifier {
    return &MonsterModifier{monster: monster}
}
 
func (monsterModifier *MonsterModifier) AddAbility(m Modifier) {
    if monsterModifier.next != nil {
        monsterModifier.next.AddAbility(m)
    } else {
        monsterModifier.next = m
    }
}
 
func (monsterModifier *MonsterModifier) Apply() {
    if monsterModifier.next != nil {
        monsterModifier.next.Apply()
    }
}
cs

위 코드가 핵심이 되는 코드입니다.

MonsterModifier 구조체의 next는 일종의 LinkedList 형태가 될 것입니다.

AddAbility는 공격력 또는 방어력 증가에 대해 계속해서 더해나가고,
Apply함수는 현재 next에 LinkedList 형태로 되어있는 모든 공격력 또는 방어력 증가를 적용시킬 것입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
type MonsterAttackModifier struct {
    MonsterModifier
}
 
func NewMonsterAttackModifier(monster *Monster) *MonsterAttackModifier {
    return &MonsterAttackModifier{MonsterModifier{monster: monster}}
}
 
func (attackModifier *MonsterAttackModifier) Apply() {
    attackModifier.monster.attack += 5
    attackModifier.MonsterModifier.Apply()
}
cs

이제 공격력을 추가시키기 위한 구조체입니다.

MonsterAttackModifier는 MonsterModifier를 embedding하지만
Apply 메서드를 다시 정의해서 공격력을 5 증가시켜줍니다.

그리고는 embedding하고 있는 MonsterModifier의 Apply 메서드를 실행하여 chain에 속한 다른 것들도 적용시켜줍니다.

MonsterDefenseModifier 역시 동일하게 작성하면 되므로 생략하겠습니다.

 

1
2
3
4
5
6
7
8
9
func main() {
    dragon := NewMonster("dragon"1010)
    root := NewMonsterModifier(dragon)
    root.AddAbility(NewMonsterAttackModifier(dragon))
    root.AddAbility(NewMonsterAttackModifier(dragon))
    root.AddAbility(NewMonsterAttackModifier(dragon))
    root.Apply()
    fmt.Println(dragon)
}
cs

위 코드를 실행하면 공격력 증가가 총 3번이 다 적용되어 
"dragon stat: (25 10)" 의 결과가 나오게 됩니다.