전략 패턴(strategy pattern) 간단 정리
유튜브로 출퇴근하면서 간간히 개발 관련 얘기들을 듣고는 하는데 언젠가 봐야지 했던 디자인패턴 강의가
알고리즘에 떠서 (강의하시는 분 말로는 디자인 패턴의 꽃이라 더라…) 코드 한번 쳐보고~ 정리하고자 글을 적습니다.
제목에 써있듯이 간단 정리이기에 자세한 패턴 이야기들은 참고 링크의 글을 읽어주길 바랍니다~
내가 이해한 전략 패턴 (strategy pattern)
- 특정 객체의 행위의 변경이 잦을때, 해당 클래스의 행위 함수 구현부를 바꾸는 게 아니라 “행위 클래스”를 구현해서 주입!
- 어떤 행위인지, 행위 상세 내용에 대해서는 주체 클래스는 알지 못함 (ex. Thread.run(), Sort(New Comparator <Object o1, Object o2> { … }) )
- 행위의 변경이 필요 시 해당 행위 클래스를 찾아가 행위만 수정
- 행위의 추가가 필요 시 새로운 행위 클래스를 추가
아래의 예시를 같이 살펴보자.
public class Context {
public int asksOperation(int a, int b, String operation){
if(operation.equals("+"))
return a + b;
else if(operation.equals("-"))
return a - b;
else if(operation.equals("*"))
return a * b;
}
}
이런 연산을 도와주는 Context 클래스가 존재할 때 우리는
public class mainClass {
public static void main(String[] args){
Context context = new Context();
System.out.println("1 + 2 = "+context.asksOperation(1,2,"+"));
}
}
이런 식으로 구현해 잘 사용할 수 있다.
만약 사용자의 요청으로 “/” 연산이 추가되어야 한다면? Context의 클래스를 직접 수정해야 한다.
(게다가 나누기의 경우, divideByZeroException도 필요하다…!)
예시의 경우, 단순 연산이지만 사용자의 요청이 좀 더 복잡하고 Context 클래스를 변경하기 어렵다면…
+,-,* 등의 연산 “행위” 를 클래스로 분리해 추가 사항에 좀 더 유연하고 안전하게 바꿔보자.
바로 아래의 예제 처럼
오른쪽 가장 상단 main 클래스는 Context를 호출해 연산을 호출한다.
Context는 Strategy를 주입받아 해당 Strategy의 연산(excute)을 한다.
- 주입되는 strategy에 따라 연산의 내용은 달라지고,
- Context의 경우, 원하는 대로 Strategy를 주입해 연산을 바꿀 수 있지만 상세 연산 내용은 Strategy만 알고, 세부 내용은 Context 클래스는 신경 쓰지 않는다.
operationStrategy를 만들어준다. 인터페이스로 만들어 구현해야 하는 행위를 정의해준다.
public interface OperaionStartegy {
public int doOperaion(int a, int b);
}
이제 저 행위를 구체화하는 클래스를 만들어 준다.
// 더하기 행위를 구체화한 클래스
public class AddOperaion implements OperaionStartegy{
@Override
public int doOperaion(int a, int b) {
return a + b;
}
}
// 빼기 행위를 구체화한 클래스
public class SubOperation implements OperaionStartegy{
@Override
public int doOperaion(int a, int b) {
return a - b;
}
}
// 곱하기 행위를 구체화한 클래스
public class MultiplyOperaion implements OperaionStartegy{
@Override
public int doOperaion(int a, int b) {
return a * b;
}
}
위 변화로 Context의 경우 코드가 조금 변경된다. Strategy를 주입받고, 주입받은 Strategy의 doOperaion을 실행한다.
public class Context {
private OperaionStartegy operaionStartegy;
public void setOperaionStartegy(OperaionStartegy operaionStartegy){
this.operaionStartegy = operaionStartegy;
}
public int asksOperation(int a, int b){
return operaionStartegy.doOperaion(a,b);
}
}
그에 따라 Main 역시 호출하는 부분이 달라진다. “+”를 직접 넘기는 게 아니라
“+”에 해당하는 Strategy를 생성해 넘겨준다.
public class mainClass {
public static void main(String[] args){
Context context = new Context();
context.setOperaionStartegy(new AddOperaion()); // 전략 생성! & 넘겨주기
System.out.println("1 + 2 = "+context.asksOperation(1,2)); // 연산결과
}
}
위에 적어 놓은 요청사항을 다시 보자.
“/” 나누기 연산이 추가돼야 한다면? 아까처럼 Context를 수정하는 게 아니다. (놀랍게도 Context는 변하는 게 전혀 없다!!!)
새로운 연산(행위)에 대한 클래스를 정의해 추가해 준다.
public class DivideOperaion implements OperaionStartegy{
@Override
public int doOperaion(int a, int b) {
if(b == 0) {
System.out.println("not divede by Zero");
return 0;
}
return a / b;
}
}
main 클래스에서 호출할 때, 새로운 연산을 선언해 넘겨준다.
public class mainClass {
public static void main(String[] args){
Context context = new Context();
context.setOperaionStartegy(new DivideOperaion());
System.out.println("1 / 2 = "+context.asksOperation(1,2));
}
}
Java의 대표적인 전략 패턴으로는 Comparator 가 있다고 한다. 생각해 보면
- Comparator의 비교조건 구현에 따라 객체 비교 달라짐
- 인터페이스이기 때문에 사용자가 직접 구현해야 하는 부분(익명 객체? 이용 가능하다는데)
이라는 점에서 지금까지 나도 모르게 전략패턴을 사용하고 있었다는 걸 알게 됐다.
이제 코드를 보면 “new Comparator 자동 완성을 치면 왜 저렇게 주렁주렁 나오는지”에 대한 이유를 알게 되었다.
- Comparator는 전략 패턴을 이용, 행위가 무엇인지까지는 정의하지 않음 (인터페이스)
- 행위에 대한 구체적 구현은 사용자가 비교하고자 하는 객체에 따라 달라짐
public class mainClass {
public static void main(String[] args){
List<Integer> nums = new ArrayList<>(Arrays.asList(1,3,2,4));
System.out.println("before sort : "+nums);
nums.sort(new Comparator<Integer>() { //
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
System.out.println("after sort : "+nums);
}
}
이건 SOLID 원칙 중 OCP (open-close-principle)에 만족하게 구현하는 것이라고 하는데, 그렇게 세세하게 알고 싶진 않고 이 패턴을 알고 있으니 나중에 연산 복잡한 거 있으면 잘 구별해서 써야겠다.
참고
- 이 글을 쓰게 된 이유) https://www.youtube.com/watch?v=Zx3pt7nSMvI&list=PL93mKxaRDidEhEcufGexy99nAWGesLBch&index=2&ab_channel=메타코딩
- 개인적으로 잘 이해된 블로그) https://victorydntmd.tistory.com/292
- 영어로 적힌 거 읽어보자 하고 들어가서 번역 눌러버림) https://refactoring.guru/ko/design-patterns/strategy
- 그림과 예제를 가져와쌉) https://www.tutorialspoint.com/design_pattern/strategy_pattern.htm
- Comparator는 여기서…) https://st-lab.tistory.com/243