티스토리 뷰
오리 시뮬레이션 게임
SimUduck이라는 오리 시뮬레이션 게임에는 매우 다양한 오리가 등장한다.
이 시스템을 처음 디자인한 사람은 Duck이라는 슈퍼클래스를 만든 다음,
그 클래스를 확장해서 서로 다른 종류의 오리를 만들었다.
시뮬레이션 게임 차별화
다른 오리시뮬레이션 게임 회사와의 차별점을 두기 위해
객체지향 프로그래머 조는 오리가 날 수 있게 하기로 했다.
문제 발생
조는 duck의 몇몇 서브클래스만 날아야 한다는 사실을 까먹었다.
Duck이라는 슈퍼클래스에 fly() 메서드를 추가해
일부 서브클래스에 적합하지 않은 행동이 추가되어버렸다.
이는 유지보수면에서 좋지않은 설계이다.
상속 고려
조는 추가할 서브클래스에 fly()메소드를 오버라이드
함으로써 문제를 해결하기로 했다.
조는 새로운 클래스인 가짜오리 추가하려 했고
가짜오리는 날 수도 소리 낼 수도 없게해야했다.
그러기 위해선 quack()과 fly()를 오버라이드 해야했다.
이러한 상속을 계속 활용한다면
프로그램이 변경될 때마다
Duck의 서브클래스 fly()와 quack() 메소드를
일일이 상황에 맞게 오버라이드 해야했다.
이는 비효율적이다.
Duck의 행동을 상속할 때 단점이 되는 요소들
1. 서브클래스에 코드가 중복된다.
2. 실행 시 특징을 바꾸기 힘들다.
3. 모든 오리의 행동을 알기 힘들다.
4. 코드 변경 시 다른 오리에게 원치않은 영향을 줄 수 있다.
인터페이스 설계
조는 Duck이라는 슈퍼클래스에서 fly()와 quack()이라는 메소드를 제외하고
Flyable과 Quackable()이라는 서브클래스를 만들어서
날 수 있는 오리에게만 fly()메소드를 꽥꽥 거릴수있는 오리에게만 quack()메소드를 넣었다.
하지만 이러한 방식은 코드를 재사용할 수 없다는 단점이 있다.
날 수 있는 오리 중에 날아다니는 방식이 서로 다를 수도 있을 것이다.
즉 한 가지 행동을 바꿀 때마다 그 행동이 정의되어있는 서브클래스를 모두 찾아서
코드를 일일이 고쳐야 하고, 그 과정에서 새로운 버그가 생길 수도 있다.
캡슐화
바뀌는 부분은 따로 뽑아서 '캡슐화'한다.
그러면 나중에 바뀌지 않는 부분에 영향을 미치지 않고
그 부분만 고치거나 확장할 수 있다.
fly()와 quack()을 메소드를 제외하면 Duck클래스는 잘 작동하고 있다.
나머지 부분은 자주 달라지거나 바뀌지 않기 때문이다.
'변화하는 부분과 그대로 있는 부분'을 분리하려면
Duck클래스와 별개로 나는 것에 관련된 집합, 꽥꽥거리는 것에 관련된 집합
이렇게 총 2개의 집합을 만들어야 한다.
fly()와 quack()은 Duck 클래스에 있는 오리종류에 따라 달라진다.
두 메소드를 Duck클래스로부터 분리하려면 2개의 메소드를 모두 Duck 클래스에서 끄집어내
각 행동을 나타낼 클래스 집합을 새로 만들어야 한다.
오리 행동 구현
오리의 나는 행동을 구현한 인터페이스 FlyBehavior,
오리의 꽥꽥거리는 것을 구현한 인터페이스 QuackBehavior
이렇게 2개의 인터페이스를 사용한다.
이런식으로 디자인하면 오리의 행동이 Duck 클래스 안에 숨겨져 있지 않으므로
다른 형식의 객체에서도 두 행동을 재사용할 수 있다.
기존의 행동 클래스를 수정하거나 Duck클래스를 건드리지 않고도
새로운 행동을 추가할 수 있어
상속을 쓸 때의 단점을 극복하고,
재사용의 장점을 그대로 누릴 수 있다.
디자인 활용
1. SimUDuck에 로켓의 추진력으로 날아가는 행동을 추가하려면?
A: FlyBehavior 인터페이스에 FlyRocketPower라는 클래스를 만들면 될것이다.
2. 오리 클래스 외에 다른 클래스에서 Quack을 활용할 방법이 있는가?
A: 오리 인형을 누르면 꽥소리가 나게끔 활용할 수도 있을 것이다.
오리 행동 통합
나는 행동과 꽥꽥거리는 행동을 Duck 클래스와 그 서브클래스에서
정의한 메소드를 써서 구현하지 않고 다른 클래스에 위임한다.
Duck 클래스에 flyBehavior와 quackBehavior라는 인터페이스 형식의 인스턴스 변수를 추가한다.
fly()와 quack() 대신 performQuack()과 performFly()라는 메소드를 넣는다.
performQuack() 구현
꽥꽥거리는 행동을 하고 싶을 땐 quackBehavior에 참조되는 객체에
그 행동을 위임하면 된다.
MallardDuck 클래스 살펴보기
MallardDuck의 인스턴스가 생성될 때 생성자는 Duck으로 부터 상속받은
quackBehavior 인스턴스 변수에 Quack() 이라는 새로운 인스턴스를 대입한다.
나는 행동도 마찬가지로 flyBehavior 변수에 FlyWithWings() 라는 인스턴스를 대입한다ㅣ.
동적으로 행동 지정
메소드 추가
Duck 클래스에 나는 행동과 꽥꽥거리는 행동을 초기화할 수 있는 메소드 2개를 새로 추가한다.
두 메소드를 호출하면 언제든지 오리의 행동을 바꿀 수 있다.
Duck 서브클래스 생성
날지 못하지만 꽥꽥거릴 순 있는 ModelDuck이라는 Duck의 서브클래스를 생성한다.
FlyBehavior 형식 클래스 생성
로켓 추진으로 나는 행동을 나타내는 클래스이다.
인터페이스를 상속할 땐 implements를 사용한다.
테스트코드 작성 및 실행
ModelDuck은 날 수 없는 상태에서
행동 세터 메소드로 나는 행동이 초기화되면서
로켓 추진력을 이용해 날 수 있게 되었다!
캡슐화된 행동 살펴보기
지금까지 했던 일들을 모두 정리한 그림이다.
오리들은 모두 Duck 클래스를 확장해서 만들고,
나는 행동은 FlyBehavior를, 꽥꽥거리는 행동은 QuackBehavior를 구현해 만들었다.
전략패턴이란?
전략 패턴(Strategy Pattern)은 알고리즘군(행동)을 정의하고 캡슐화하여
각각의 알고리즘군을 수정해서 쓸 수 있게 해준다.
전략 패턴을 사용하면 클라이언트로부터 알고리즘을 분리해
독립적으로 변경할 수 있게 된다.
블로그 쓴다고 그린것들