상속과 컴포지션

블랙잭 미션의 요구사항 중에서 딜러와 플레이어가 공통으로 수행하는 부분이 있어 상속으로 해결했다.
딜러만 추가적으로 처음에 받은 2장의 합계가 16이하이면 반드시 1장의 카드를 추가로 받아야 하고, 17점 이상이면 추가로 받을 수 없다. 와 같은 요구사항이 있어서 딜러가 플레이어를 상속하고, 추가 기능을 확장하는 방식으로 구현했다.

2단계에 접어들면서 플레이어의 요구사항이 추가되었다. 배팅하는 기능을 추가해야 됐는데, 이는 딜러에게는 필요 없는 기능이다. 이렇게 점점 요구사항이 추가되면서 딜러와 플레이어는 단지 공통된 기능만 있을 뿐이고 서로 포함되는 관계는 아니였음을 느꼈다.
따라서 hit, stay 같은 공통된 기능을 Participant 클래스에 구현하고, 딜러와 플레이어는 조합으로 이 클래스의 기능을 사용하도록 했다.

이러한 관계를 has-a 관계라고 한다.

프로그래밍을 할 때 완벽히 Is-a 관계를 가지는 경우는 드물 것 이다. 완벽히 Is-a 관계를 가지더라도 요구사항이 변경 되면 쉽게 깨지게 된다. 따라서, 상속은 피하도록 하자.

상태 패턴

블랙잭 게임은 카드 패의 숫자와 어떤 행동을 하냐에 따라 가능한 행동이 달라지는 경우가 꽤 있다.

  • 카드 숫자의 합이 21 이상으로 Bust이면 더 이상 카드를 뽑지 못한다.
  • 블랙잭이면 (두 카드의 숫자의 합이 21) 카드를 그만 뽑는다.
  • Stay는 카드를 그만 뽑는다.
  • Hit는 카드를 뽑는다.

아래는 Hit에서 카드를 뽑는 draw() 메서드를 구현한 것이다.

@Override  
public State draw(final Card card) {  
    hand.add(card);  
    if (hand.isBlackjack()) {  
        return new Blackjack(hand);  
    }  
    if (hand.isBust()) {  
        return new Bust(hand);  
    }  

    return new Hit(hand);  
}

블랙잭일때는 무엇을 할 수 있는지, 버스트일 때 무엇을 할 수 있는지 신경쓰지 않는다. 그저 자기 상태의 행동만 책임지고 결과에 따라 다른 상태로 바꿔버리기만 한다.
즉, 상태에 따라 책임을 분리할 수 있다.

DIP 위반

Running은 추상클래스로 Hit 가 상속하고 있다.

여기서 Running 상태이면 카드를 뽑을 수 있어서 Runningdraw() 를 구현했다. 여기에 대해서 다음과 같은 피드백을 받았다.

Running을 상속받은 Hit를 다시 Running에서 참조한다는 건 DIP 위반 아닐까요?

고수준 모듈인 Running 에서 저수준 모듈인 Hit 를 생성하고 있기 때문에 DIP를 위반하고 있었다! 따라서 draw() 메서드를 Hit 에서 구현하도록 수정했다.

다른 부분에서도 DIP를 위반했었는데 다음 코드를 보자.

public interface State {
    boolean isHit();
    boolean isRunning();
    boolean isStay();
    ...
}

상태가 Hit 인지, Running 인지, Stay 인지 확인하는 메서드이다.
상위 인터페이스에서 하위 구체 클래스에 대한 정보를 가지고 있기 때문에 이는 DIP 위반이라고 볼 수 있다. 상태가 더 늘어난다면 인터페이스에 계속 메서드를 추가해야 할 것이다.

public Enum StateType {
    RUNNING,
    HIT,
    STAY;
}

위 코드와 같이 StateType을 정의해놓고, State 인터페이스에는 getStateType() 메서드를 추가하고 각 구체 클래스가 자신의 Type을 리턴하도록 구현한다.

public class Hit {
    public StateType getStateType() {
        return StateType.HIT;
    }
}

이렇게 상위 계층에서는 하위 계층을 알지 못하도록 하여 DIP 를 위반하지 않도록 주의하자!

'회고 > 우아한테크코스' 카테고리의 다른 글

2023.04.11 일일 회고  (0) 2023.04.12
[레벨 1] 체스 미션 회고  (0) 2023.04.10
[레벨 1] 레벨 로그  (0) 2023.03.28
2023.03.27 일일 회고  (0) 2023.03.28
2023.03.23 일일 회고  (0) 2023.03.24

+ Recent posts