전략 패턴이란?

알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 한다
클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있다

처음 정의를 보고 무슨 말인지 이해를 못했다

역시 코드를 직접 쳐보고 예제를 리팩토링하면서 공부하는게 가장 빠르게 습득하고 이해하는 지름길인 것 같다

 

처음엔 각각의 클래스로 구현

public class Bin {
	void eat() {
		System.out.println("돈까스!");
	}
}

public class Jin {
	void eat() {
		System.out.println("돈까스!");
	}
}

public class Anna {
	void eat() {
		System.out.println("돈까스!");
	}
}

상속을 이용한 서브클래스 확장

  • 친구들은 각각 다른 돈까스를 먹고싶다는 문제가 생겼고 이를 해결하기 위해 상속을 이용했다
  • Person 으로 분리하고 추상 메소드를 활용해 각 클래스에서 구현하도록 했다
  • 여기서 문제가 하나 생긴다 코드를 보고 한번씩 생각을 해봤으면 좋겠습니다
public abstract class Person {
	void say() {
		System.out.println("배고파");
	}
	abstract void eat();
}

public class Bin extends Person {
	@Override
	void eat() {System.out.println("그냥 돈까스!");}
}

public class Jin extends Person {
	@Override
	void eat() {System.out.println("치즈 돈까스!");}
}

public class Anna extends Person {
	@Override
	void eat() {System.out.println("고구마 돈까스!");}
}
  • 문제는 Person 클래스의 say() 라는 메소드다 친구들이 항상 배고프다는 문제가 생겨버린다
  • say() 오버라이딩을 해서 각 서브클래스에서 배가 고프지 않다고 구현을 해도 되지만
  • 매번 구현하는 클래스가 어떨지 생각해야하고 어떤 돈까스를 먹는지 고민하고 하는 문제를 또 만나게 될 것이다

 

인터페이스를 활용한 분리

public interface SayStrategy {
	void say();
}

public class HungryStrategy implements SayStrategy {
	@Override
	public void say() {System.out.println("배고파");}
}

public class NotHungryStrategy implements SayStrategy {
	@Override
	public void say() {System.out.println("배 안고파!");}
}

public abstract class Person {
	protected SayStrategy sayStrategy;
	void say() {
		sayStrategy.say();
	}
	abstract void eat();
}
  • 이렇게 분리를 하면 Person 을 상속하는 서브 클래스들 생성자에서 배고픈지 아닌지 전략별로 갈아 끼울 수 있다
public class Anna extends Person {
	public Anna() {
		sayStrategy = new HungryStrategy();
        sayStrategy = new NotHungryStrategy();
	}
    	...
}

 

동적으로 변경하기

  • 결국 각 구현체 생성자 안에서 전략인터페이스를 구현한 클래스를 고정시켜서 사용했다
  • 동적으로 활용할 수 있게끔 리팩토링을 해보자
public abstract class Person {
	void setSayStrategy(SayStrategy sayStrategy) {
		this.sayStrategy = sayStrategy;
	}
}
  • 그리고 서브 클래스들은 처음엔 NotHungryStrategy 를 할당해준다
  • 배가 안고프다가 배가 고파진다면 그때 HungryStrategy 를 할당하는 동적인 코드가 완성되었다
package strategypattern;

public class MainApplication {
	public static void main(String[] args) {
		Person bin = new Bin();
		bin.say();
		System.out.println();

		Person jin = new Jin();
		jin.say();
		jin.setSayStrategy(new HungryStrategy());
		jin.say();
		jin.eat();
		System.out.println();

		Person anna = new Anna();
		anna.say();
		anna.setSayStrategy(new HungryStrategy());
		anna.say();
		anna.eat();
	}
}

출력결과--------------------------------------------------

배 안고파!

배 안고파!
배고파
치즈 돈까스!

배 안고파!
배고파
고구마 돈까스!
Bin 은 처음에 모든 클래스에 NotHungryStrategy를 할당했기에 배가 고프지 않다

Jin 과 Anna 도 처음엔 배가 고프지 않았지만 배고픈 전략을 동적으로 할당해서 배고픈 상태가 되었고
각자가 먹고 싶은 eat() 을 호출한다

 

📝 요약 정리

처음에 언급했던 알고리즘군은 위에서 전략을 구현하는 클래스들로 이해하면 된다. NotHungryStrategy, HungryStrategy 이 클래스들은 각각 알고리즘이고 이 클래스들을 합친 것을 알고리즘군 이라 한다.

 

모든 곳곳에 전략 패턴을 사용하면 좋을까? 당연히 아니라고 본다. 알고리즘군이 작으면 그냥 if-else 로 나눠서 분기처리 하는 것이 더 편하다고 생각한다. 하지만 알고리즘군이 크고 다양하게 구현이 될 수 있다면 전략패턴을 사용할 적절한 기회일 것 같다고 느껴졌다.

 

전략 패턴이란 무엇인가 질문이 들어온다면은 이렇게 대답을 할 것 같다

  1. 요구사항이 바뀌었을 때 기존 코드를 수정하지 않아도 된다는 장점이 있다
  2. 전략 패턴의 의도는 구현체를 요리조리 갈아끼우는 것이다

리팩토링을 하면서 디자인 패턴의 중요성을 깨달았고 패턴을 공부하면서 적용시키니 코딩이 즐겁고 행복하다

여기서 eat 메소드는 따로 패턴을 적용시키지 않았다. 이 글을 읽는 사람들이 직접 돈가스 종류별로 구현하고 만들어보면 좋을 것 같다

 

예제 코드 - https://github.com/wugawuga/design-patterns/tree/main/src/strategypattern

 

복사했습니다!