DailyCode
스트레티지 패턴(Strategy Pattern) 본문
정의
로직을 인터페이스(프로토콜)로 사전에 정의하고, 각 로직을 캡슐화하여 상호 호환할 수 있도록 만드는 데 사용되는 패턴
전략패턴을 통해 클라이언트는 컴파일 타임이 아닌 런타임 알고리즘을 선택할 수 있으며,(부가설명: 기존에 소스를 정의(하드코딩)하는 것이 아닌 작업중 교체)
그로인해 클라이언트의 영향을 미치지 않고 로직을 변경합니다.
이 패턴은 코드 재사용성, 유지보수성 및 유연성을 촉진하기 때문에, 클래스의 동일한 방법을 다른 방식으로 수행하는 로직이 많을 때 유용합니다.
구성요소
- 전략 인터페이스: 구현해야 하는 모든 전략에 대한 공통 인터페이스
- 구현부: 전략 인터페이스를 상속받아 실제 구현
- 연결부: 전략 인터페이스를 사용하여 구현부와 통신. 구현부 중 하나에 대한 참조를 보유하고 알고리즘을 실행하는 작업을 선택한 전략에 위임
- 클라이언트(사용자): 필요한 전략을 사용
장점
- 로직을 캡슐화하여 코드 재사용성과 유지보수성을 높입니다.
- 각 로직을 별도의 클래스로 구현할 수 있어 새로운 로직 추가에 용이합니다.
- 클라이언트의 코드에 영향을 주지 않고 런타임에 알고리즘 변경할 수 있습니다.
나쁜 예제
Golden Hammer Pattern
친숙한 도구에 지나치게 의존하는 것, 가지고 있는것이 망치뿐이라면, 모든것이 못으로 보인다 라는 패턴
개발자가 마주한 문제에 대한 최선의 해결책인지 관계없이, 당면한 문제를 해결하기 위해 단일 솔루션을 사용하는 패턴
let data = [1, 2, 3, 4, 5]
class Calculator {
static let shared = Calculator()
func calculate(_ data: [Int], operation: String) -> Int {
switch operation {
case "+":
return data.reduce(0, +)
case "AVG":
return data.reduce(0, +) / data.count
case "*":
return data.reduce(1, *)
}
}
}
print(Calculator.shared.calcuate(data, "+"))
이 패턴은 단지 계산이라는 문제만 해결하기 위해 메서드 안에 스위치문으로 구현됩니다.
이로 인해 새로운 연산이 추가될 때 마다 스위치가 추가되어 메서드가 복잡해지고, 휴먼 에러가 발생할 확률이 높아집니다.
Strategy Pattern을 적용한 예제
// Interface
protocol CalcuationInterface {
func calculate(data: [Int]) -> CGFloat
}
// Implementation
class SumCalculate: CalcuationInterface {
func calculate(data: [Int]) -> CGFloat {
return CGFloat(data.reduce(0, +))
}
}
class AverageCalculate: CalcuationInterface {
func calculate(data: [Int]) -> CGFloat {
return CGFloat(data.reduce(0, +)) / CGFloat(data.count)
}
}
class MultiplyCalcuate: CalcuationInterface {
func calculate(data: [Int]) -> CGFloat {
return CGFloat(data.reduce(1, *))
}
}
//Connection
class DataProvider {
var feature: CalcuationInterface
init(feature: CalcuationInterface) {
self.feature = feature
}
func processData(data: [Int]) {
let result = feature.calculate(data: data)
print(result)
}
}
//Client
let data = [1, 2, 3, 4, 5]
let sumCalculate = SumCalculate()
let averageCalculate = AverageCalculate()
let multiplyCalcuate = MultiplyCalcuate()
let sum = DataProvider(feature: sumCalculate)
sum.processData(data: data)
let average = DataProvider(feature: averageCalculate)
average.processData(data: data)
let multiply = DataProvider(feature: multiplyCalcuate)
multiply.processData(data: data)
인터페이스를 사전에 구현하고, 클래스에 실제 인터페이스의 구현부를 정의합니다.
DataProvider는 계산 인터페이스와 클라이언트의 연결부 역할을 하며 인터페이스를 초기화로 받고 실행하는 역할을 합니다.
마지막으로 클라이언트는 필요한 기능에 대한 클래스를 가져오고, 커넥션에 전달하여 요청 기능을 수행합니다.
- 목표: 사전에 로직을 정의하고 각 로직을 캡슐화하여 상호교환이 가능하게 만드는 것이 목표
'디자인 패턴' 카테고리의 다른 글
[SOLID] 리스코프 치환 법칙(LSP) (0) | 2023.03.25 |
---|---|
[SOLID] 개방 폐쇄 원칙(OCP) (0) | 2023.03.24 |
[SOLID] 단일 책임 원칙(SRP) (0) | 2023.03.16 |
템플릿 메서드 패턴(TemplateMethod Pattern) (0) | 2023.03.09 |
커맨드 패턴(Command Pattern) (0) | 2023.03.07 |