전략 패턴은 모든 알고리즘 계열을 수정하고 사용할 수 있도록 알고리즘 계열을 정의하고 캡슐화하는 패턴입니다.
여기서 알고리즘 그룹은 독립적인 논리를 의미하며 일반적으로 OOP에서는 클래스의 동작을 의미하는 메서드로 생각할 수 있습니다. 따라서 임의의 알고리즘 그룹을 수정하여 사용할 수 있다는 의미는 클래스의 동작은 동일하지만 다른 동작으로 대체하여 실행할 수 있음을 의미합니다. 그리고 같은 유형이지만 다른 것은 그들이 같은 프로토콜을 따르지만 다른 클래스라는 것을 의미하며 액션 유형은 이 프로토콜을 일반적으로 -Behavior라고 하는 불문율을 암시합니다.
전략 패턴이 좋은 이유는 무엇입니까?
예를 들어 꽥꽥거리고 헤엄칠 수 있는 Duck이라는 클래스가 있다고 가정해 보겠습니다.
class Duck {
func quack() {}
func swim() {}
}
이 클래스를 상속하는 청둥오리나 붉은 볏 오리를 생성해야 하는 경우 가장 좋은 방법은 무엇입니까? 일반적으로 상속을 통해 구현하거나 Swift를 사용하는 경우 프로토콜을 사용하여 구현합니다. 특히 후자의 경우 저처럼 Quackable 또는 Swimmable과 같은 프로토콜을 개발하는 것을 자랑스럽게 생각할 수 있습니다. 그러나 상속을 통해 구현하고 fly()와 같은 메서드를 최상위 클래스에 추가하면 모두 자식 클래스에 추가해야 합니다. 프로토콜을 직접 적용하는 클래스를 만드는 경우 코드를 단순화하기 위해 메서드 수만큼 프로토콜을 만들어야 합니다. 이것을 시도하면 결국 복잡해집니다. Quakable, Swimmable, Flyable, Walkable, Runnable 등으로 클래스를 만든 후 좋은 코드라고 할 수 있을까요? 그리고 Walkable과 Runnable의 경우, 그 특성이 꽤 겹치는 것 같습니다. 별도로 구현해야 합니까?
전략 패턴은 클래스가 아닌 메서드의 속성을 나타낼 수 있는 프로토콜을 만드는 방법으로, 클래스는 프로토콜에 일치하는 객체를 멤버 변수로 갖고 클래스의 메서드가 멤버 변수를 통해 작동하도록 합니다.
import Foundation
protocol FlyBehavior {
func perform()
}
class FlyWithWings: FlyBehavior {
func perform() {
print("I can fly.")
}
}
class FlyWithJetpacks: FlyBehavior {
func perform() {
print("Fly me to the moon!")
}
}
class Duck {
var flyBehavior: FlyBehavior
init(flyBehavior: FlyBehavior) {
self.flyBehavior = flyBehavior
}
func swim() {}
func setFlyBehavior(with behavior: FlyBehavior) {
self.flyBehavior = behavior
}
func performFly() {
self.flyBehavior.perform()
}
}
let duck: Duck = .init(flyBehavior: FlyWithWings())
duck.performFly()
duck.setFlyBehavior(with: FlyWithJetpacks())
duck.performFly()
Fly가 SuperClass Duck인지 하위 클래스인지 변경되면 FlyBehavior를 교체하고 performFly()로 동일한 실행을 수행합니다. 그렇게 하면 새 FlyBehavior를 추가할 때 클래스를 구현하고 교체할 수 있기 때문에 클래스가 변경되지 않는다는 이점이 있습니다. 같은 유형의 행위이기는 하지만 전략을 바꿔서 실행한다는 의미에서 전략 패턴이라고 하는 것 같다.
전략 패턴이 만병통치약이 아닌 이유는 무엇입니까?
이를 수행하는 한 가지 방법은 복잡성을 높이는 것입니다. FlyBehavior 개체를 멤버 변수로 포함하는 클래스는 변경할 수 없지만 FlyBehavior 호환 동작을 나타내는 클래스는 계속 추가해야 하므로 과도한 복잡성이 발생할 수 있습니다. 액션마다 클래스를 추가하는 것은 소 칼로 닭을 잡는 것과 같을 수 있습니다. 조금 복잡하긴 하지만 클래스에 작업을 나타내는 메서드가 여러 개 있으면 더 좋은지 고려해야 합니다.
그리고 계급의 행태를 심하게 분별할 수 있는 전략이 있어야 한다. 예를 들어 위의 Duck의 경우 액션을 나누기가 쉽습니다. 끽끽, 날기, 점프와 같은 큰 전략이 있고, 끽끽, 끽끽, 날개로 날기, 제트팩으로 날기 등의 액션이 있음이 분명하기 때문이다. 그러나 클래스에 분류하기 어려운 메서드가 있으면 전략 패턴을 사용하기 어려워집니다.
또한 행동 로그를 생성해야 하지만 몇 번 사용하지 않는 경우가 있습니다. 서브 클래스는 멤버 변수로 동작이 있어야 하기 때문에 구현했는데, 대부분 사용하지 않는다면 전략 패턴을 적용할 필요가 없다. 사용하는 소수의 하위 클래스에서 직접 메서드를 구현하는 것이 좋습니다. 디자인 패턴은 코드의 복잡성을 증가시키고 다른 사람이 코드를 보기 어렵게 만들기 때문에 디자인 패턴에 집착하지 않도록 주의하세요.