-
[OOP] 디자인 패턴 - Strategy Pattern(전략 패턴)프로그래밍 2024. 7. 16. 21:48
## 전략 패턴(Strategy Pattern).
알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.
## 오리 클래스를 구현해보자
오리를 프로그래밍으로 구현한다고 했을 때, 아래와 같은 구현을 많이 생각할 것이다.
나도 누군가가 갑작스레 오리 클래스를 구현해봐~ 라고 물었을때
위와 같은 구조를 생각할 것 같은데
이런 구조에는 어떤 문제점이 있을까?
### 문제점 1 - 서브클래스 코드 중복
만약, 위의 RubberDuck처럼 실제로 날지 못하는 오리(NowWingsDuck)가 추가된다고 하자.
NoWingsDuck 추가 해당 오리는 fly() 부분을 날지 못하게 하도록 따로 코드를 구현해야 할 것이고, 이것은 RubberDuck의 코드와 차이가 없는 중복코드 일 것이다.
### 문제점 2 - 코드 변경시 다른 오리들에게 원치 않는 영향을 끼칠 수 있다.
Duck을 부모클래스로 상속받고 있으므로, Duck 클래스수정을 할 때, 자식 클래스의 오리들에게 영향이 갈 수 있다.
### 문제점 3 - 실행시 특징을 바꾸기 힘들다.
업데이트 요구사항 중에 다음과 같은 요청이 있다 가정 해보자.
"몇몇 오리들은 특정 조건에 따라 나는 행동을 바꿔주세요, 예를들어 부상을 입는 이벤트가 발생하면, 그 오리는 날 수 없게되고, 오리가 치료되면 날 수 있게 만들어 주세요."
그렇다면 이런 식으로 바꿀 것인가?
func fly() { if(canFly) { //... } else { //... } }
요구사항이 다시 추가된다면?
"부상을 입어도 가벼운 부상이라면 50%의 속력으로 날 수 있게 해주세요"
그때도 if를 추가해 코드의 구현부를 수정할 것인가?
### 문제점 4 - 모든 오리의 행동을 알기 힘들다.
Duck을 상속받은 서브 오리가 날 수 있는지, 날 수 없는지 파악하려면, 코드의 구현 부분을 봐야한다.
서브 오리들이 1,2개라면 상관 없을 테지만, 만약 수십, 수백개가 있다면 일일이 파악하기는 매우 힘들 것이다.
## 오리 클래스를 변경해보자.
위의 문제점들이 나는 근본적인 이유는,
기본적으로 오리는 날 수 있도록 설계해놓고, 날 수 없는 오리도 만들어야 하고, 날 수 있다가도 없어질 수 있는 오리를 지원해야 하기 때문.
즉 오리의 fly() 메소드가 기능의 변화에서 유연해야 하는데, 위와 같은 구조로는 fly()를 부모클래스로부터 상속받기 때문에 다양한 요구사항들을 받아들이기 어렵다.
### 1. 오리의 나는 행동을 클래스로 - FlyBehavior
오리에게 날 수 있는 기능, 날 수 없는 기능을 각각 클래스화 한다.
### 2. 오리와 행동이 된 클래스를 통합한다.
그리고 performFly() 부분을 구현한다.
func performFly() { flyBehavior.fly() }
### 3. SubDuck들을 구현한다.
간단히 MallardDuck, RubberDuck만 구현해보면,
class Duck { var flyBehavior: FlyBehavior init(flyBehavior: FlyBehavior) { self.flyBehavior = flyBehavior } func performFly() { flyBehavior.fly() } } class MallardDuck: Duck { init() { super.init(flyBehavior: FlyWithWings()) } } class RubberDuck: Duck { init() { super.init(flyBehavior: FlyNoWay()) } }
위와 같이 나는 행동(FlyBehavior)을 클래스로 만들어서 리팩토링 해보았다.
## 문제는 해결 되었을까?
기존 코드의 문제점들이 해결되었는지 한번씩 비교해보자
### 문제점 1 - 서브 클래스 코드 중복 : [해결]
각각 오리들의 나는 행위를 변수로서 가지게 됨으로서, RubberDuck이든, NoWingsDuck이든 동일한 구현부 없이 FlyNoWay만 가지고 있으면 된다.
### 문제점 2 - 코드 변경시 다른 오리들에게 원치 않는 영향을 끼칠 수 있다. : [해결]
날다, 날지 않다의 구현은 FlyBehavior에 의해 캡슐화 되어 넘어갔다.
더이상 Duck 클래스는 나는 행위를 신경쓰지 않을것이며 FlyBehavior가 신경쓸 영역으로 넘어갔으나,
책임이 다양한 Duck 클래스와 달리, FlyBehavior는 날다, 날지 않다로 명확히 구분되어 코드 변경이 될 가능성은 낮다.
### 문제점 3 - 실행시 특징을 바꾸기 힘들다. : [해결]
오리의 flyBehavior 을 다른 객체로 바꿔주기만 하면 오리의 상태에 따라서 날 수 있게도, 날 수 없게도 만들 수 있다.
let mallardDuck = MallardDuck() mallardDuck.performFly() // 잘 난다 // 못 날게 되는 이벤트 발생 mallardDuck.flyBehavior = FlyNoWay() mallardDuck.performFly() // 못 난다
### 문제점 4 - 모든 오리의 행동을 알기 힘들다. : [해결]
기존코드는 fly() 행동을 확인하기 위해 코드의 구현부를 일일이 봐야했으나,
지금은 오리가 어떤 flyBehavior 를 갖고 있는지 알면 된다.
## 정리
전략 패턴(Strategy Pattern)은 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.
이 예제에서는 알고리즘군은 FlyBehavior이 되겠지?
알게 모르게 자주쓰이는 전략패턴. 잘 숙지해두면 좋다.
## Ref
- Head First Design Patterns
'프로그래밍' 카테고리의 다른 글
[OOP] 디자인 패턴 - Decorator Pattern(데코레이터 패턴) (0) 2024.07.17 [OOP] Law of Demeter: 최소 지식 원칙 (0) 2024.07.11