본문 바로가기
golang/design pattern

golang design pattern #11 Chain of Responsibility

by PudgeKim 2021. 11. 16.

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)" 의 결과가 나오게 됩니다.

'golang > design pattern' 카테고리의 다른 글

golang design pattern #13 Observer  (0) 2021.11.23
golang design pattern #10 Proxy  (0) 2021.11.15
golang design pattern #9 Flyweight  (0) 2021.11.14
golang design pattern #8 Decorator  (0) 2021.11.13
golang design pattern #7 Composite  (0) 2021.11.12