디자인 패턴 - Composition

목표

  • 컴포지션 사용

1. 컴포지션

사용 시점

  • 객체 간의 관계를 “has-a” 관계로 표현하고 싶을 때
  • 런타임에 객체의 동작을 변경하고 싶을 때
  • 상속 대신 유연한 구조가 필요할 때

클래스 다이어그램

classDiagram
    class WeaponBehavior {
        <<interface>>
        +useWeapon()
    }
    
    class Character {
        #weaponBehavior: WeaponBehavior
        +setWeaponBehavior(wb: WeaponBehavior)
        +performWeapon()
        +display()*
    }
    
    class Knight {
        +display()
    }
    
    class Archer {
        +display()
    }
    
    class SwordBehavior {
        +useWeapon()
    }
    
    class BowBehavior {
        +useWeapon()
    }
    
    WeaponBehavior <|.. SwordBehavior : implements
    WeaponBehavior <|.. BowBehavior : implements
    Character <|-- Knight : extends
    Character <|-- Archer : extends
    Character *-- WeaponBehavior : has

예시 코드

게임 캐릭터와 무기 시스템을 구현한 예시를 통해 컴포지션을 살펴보겠습니다.

1. 무기 행동 인터페이스

1
2
3
public interface WeaponBehavior {
void useWeapon();
}

2. 구체적인 무기 행동 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 검 사용
public class SwordBehavior implements WeaponBehavior {
@Override
public void useWeapon() {
System.out.println("검으로 베기 공격!");
}
}

// 활 사용
public class BowBehavior implements WeaponBehavior {
@Override
public void useWeapon() {
System.out.println("활로 화살 발사!");
}
}

3. 캐릭터 추상 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class Character {
protected WeaponBehavior weaponBehavior;

public void setWeaponBehavior(WeaponBehavior wb) {
this.weaponBehavior = wb;
}

public void performWeapon() {
if (weaponBehavior != null) {
weaponBehavior.useWeapon();
} else {
System.out.println("무기가 없습니다!");
}
}

public abstract void display();
}

4. 구체적인 캐릭터 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 기사 클래스
public class Knight extends Character {
public Knight() {
// 기본적으로 검을 장착
weaponBehavior = new SwordBehavior();
}

@Override
public void display() {
System.out.println("나는 기사입니다!");
}
}

// 궁수 클래스
public class Archer extends Character {
public Archer() {
// 기본적으로 활을 장착
weaponBehavior = new BowBehavior();
}

@Override
public void display() {
System.out.println("나는 궁수입니다!");
}
}

5. 테스트 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Game {
public static void main(String[] args) {
// 기사 생성
Character knight = new Knight();
knight.display();
knight.performWeapon();

// 궁수 생성
Character archer = new Archer();
archer.display();
archer.performWeapon();

// 무기 교체
System.out.println("기사가 활로 무기를 교체합니다!");
knight.setWeaponBehavior(new BowBehavior());
knight.performWeapon();

System.out.println("궁수가 검으로 무기를 교체합니다!");
archer.setWeaponBehavior(new SwordBehavior());
archer.performWeapon();
}
}

실행 결과

1
2
3
4
5
6
7
8
9
10
나는 기사입니다!
검으로 베기 공격!
나는 궁수입니다!
활로 화살 발사!

기사가 활로 무기를 교체합니다!
활로 화살 발사!

궁수가 검으로 무기를 교체합니다!
검으로 베기 공격!

컴포지션 장점

  1. 유연성: 런타임에 무기를 교체할 수 있습니다.
  2. 확장성: 새로운 무기나 캐릭터를 쉽게 추가할 수 있습니다.
  3. 재사용성: 무기 행동을 다른 클래스에서도 재사용할 수 있습니다.
  4. 캡슐화: 무기 관련 동작이 WeaponBehavior 인터페이스에 캡슐화되어 있습니다.

정리

  • 컴포지션은 객체 간의 관계를 “has-a” 관계로 표현하고 싶을 때 사용합니다.
  • 상속 대신 유연한 구조가 필요할 때 사용합니다.
  • 런타임에 객체의 동작을 변경하고 싶을 때 사용합니다.
  • 상속 대신 유연한 구조가 필요할 때 사용합니다.

ref

  • 헤드퍼스트 디자인 패턴