전략 패턴이란
객체가 할 수 있는 행위(기능)들 각각을 전략으로 만들어 놓고 사용하며, 동적으로 전략 수정이 가능한 패턴
무슨 문제가 있길래?
왜 이런 패턴이 나왔을까 한번 천천히 보겠습니다.
class Subway{
public void take() {
System.out.println("카드를 찍고 들어갑니다.");
}
}
class Bus{
public void take() {
System.out.println("카드를 찍고 들어갑니다.");
}
}
class Airplain{
public void take() {
System.out.println("표를 내고 들어갑니다.");
}
}
class Train {
public void take() {
System.out.println("표를 내고 들어갑니다");
}
}
와 같이 있다고 생각하겠습니다.
public static void main(String[] args) {
Subway subway = new Subway();
Bus bus = new Bus();
subway.take();
bus.take();
}
1. 상속
대중 교통이라는 관점에서 보면 전부 동일하게 탑승할때의 행동이 있습니다. 이러한 경우 또 다른 대중교통 수단이 많아지게 되면 관리하기가 어려워 지기 때문에 상속을 통해서 관리를 하게 됩니다.
public abstract class Transport {
public abstract void take();
}
class Subway extends Transport{
public void take() {
System.out.println("카드를 찍고 들어갑니다.");
}
}
class Bus extends Transport {
public void take() {
System.out.println("카드를 찍고 들어갑니다.");
}
}
class Airplain extends Transport {
public void take() {
System.out.println("표를 내고 들어갑니다.");
}
}
class Train extends Transport {
public void take() {
System.out.println("표를 내고 들어갑니다");
}
}
와 같이 상속을 통해 표현할 수 있습니다.
이렇게 되면 하나의 자료형으로 관리가 가능하여 다형성 측면에서 이점을 얻을 수 있게 되었습니다.
Transport subway = new Subway();
Transport bus = new Bus();
subway.take();
bus.take();
그런데 아직 문제점이 있습니다.
아직 새로운 교통수단이 생겨도 take에 대한 코드가 중복이 되기 때문에 코드가 재사용 되는 곳이 많습니다. 이 말은 결국 코드를 하나 변경하게 되면 다른 것까지 전부 변경해야 하는데 변화에 열려있어 OCP원칙을 지치기 어렵습니다.
또한 상속을 하게 된다는 것은 상위 클래스와 하위 클래스간 결합이 강하다는 것을 의미하기 때문에 새로운 기능을 추가하기가 어렵습니다.
결국 어떤 동작이나 기능에 대해서 전체적으로 동일한 기능을 제공하면 안되는데 객체가 확장성이 있어야 하는 상태에서 사용하게 됩니다.
어떻게 해결했는가?
여기서 사용하는 것이 전략패턴입니다.
변화와 비변화를 구분
먼저 코드에서 변화되는 부분을 찾아서 캡슐화해야합니다. 즉 변화 되는 기능에 대해 다른 클래스로 위임해 준다는 것입니다.
여기서는 탑승한다는 take라고 볼 수 있죠
interface Take {
void take();
}
class Ticket implements Take {
@Override
public void take() {
System.out.println("표를 내고 들어갑니다");
}
}
class Card implements Take {
@Override
public void take() {
System.out.println("카드를 찍고 들어갑니다.");
}
}
따라서 위와 같이 변화하는 코드의 행동에 대한 전략을 따로 만들어 놓고
abstract class Transport {
Take take;
public void take() {
this.take.take();
}
}
class Subway extends Transport {
public Subway() {
this.take = new Card();
}
}
class Bus extends Transport {
public Bus() {
this.take = new Card();
}
}
class Airplain extends Transport {
public Airplain() {
this.take = new Ticket();
}
}
class Train extends Transport {
public Train() {
this.take = new Ticket();
}
}
위와 같이 각 객체가 가지고 있어야하는 기능을 주게 됩니다.
Transport train = new Train();
train.take();
Transport bus = new Bus();
bus.take();
이렇게 된다면 각 기능에 대한 다형성을 잃지 않고도 확장성을 유지할 수 있습니다. 또한 티켓이면 티켓, 카드면 카드와 같이 기능을 위임했으므로 각각의 클래스가 변경될 때에는 하나의 이유만 가지게 될 수 있으므로 SRP의 원칙도 지켰다 볼 수 있습니다.
여기서 끝이 아니죠
만약, 버스가 다시 티켓으로 돌아가고 싶다면
abstract class Transport {
Take take;
String name;
public void take() {
this.take.take(name);
}
public void setTake(Take take) {
this.take = take;
}
}
위와 같이, setTake를 통해 동적으로 행동을 지정해 줄 수 있습니다.
전체 코드
public class Main {
public static void main(String[] args) {
Transport train = new Train();
train.take();
Transport bus = new Bus();
bus.take();
bus.setTake(new Ticket());
bus.take();
}
}
abstract class Transport {
Take take;
String name;
public void take() {
this.take.take(name);
}
public void setTake(Take take) {
this.take = take;
}
}
class Subway extends Transport {
public Subway() {
this.take = new Card();
this.name = "지하철 : ";
}
}
class Bus extends Transport {
public Bus() {
this.take = new Card();
this.name = "버스 : ";
}
}
class Airplain extends Transport {
public Airplain() {
this.take = new Ticket();
this.name = "비행기 : ";
}
}
class Train extends Transport {
public Train() {
this.take = new Ticket();
this.name = "기차 : ";
}
}
interface Take {
void take(String name);
}
class Ticket implements Take {
@Override
public void take(String name) {
System.out.println(name+"표를 내고 들어갑니다");
}
}
class Card implements Take {
@Override
public void take(String name) {
System.out.println(name+"카드를 찍고 들어갑니다.");
}
}
결론
전략 패턴
- 행동에 따른 알고리즘을 분리 및 캡슐화
- 이들을 교환할 수 있는 다형성을 제공하게 해준다.
- 각 인터페이스는 필드변수로 값을 받게 되는데, 이는 동적으로 행동을 변경할 수 있도록 확장성을 줬습니다.
실제 사용
- 소셜 로그인 기능 (카카오, 네이버, 구글, 페이지 로그인)
- Funtional Interface - 람다
전략 패턴 vs Command 패턴
두 패턴 모두 객체에 행동을 위임하여 동작하게끔 하는 방식입니다. 다만 다른 점은
전략 패턴은 위에서도 언급했지만, 행동에 따른 알고리즘 분리인데 좀 더 자세하게 말하면,
- 변화하는 코드를 분리
- 해당 코드를 캡슐화하여
- 인스턴스를 통해 상세 행동 분리
와 같다고 생각할 수 있습니다.
제가 말씀드리고 싶은 것은 결국, 전략 패턴은 같은 목적을 가진 다른 방식입니다. (계산하는 방식 : 표, 카드)
반대로 Command 패턴의 경우
interface BaseCommand {
void execute();
}
class Commaand implements BaseCommand {
private Object argu;
public Commaand(Object arg) {
this.argu = arg;
}
@Override
public void execute() {
}
}
위와 같습니다.
여기서, 명령의 경우 하나의 목적이 아닌 여러 목적이 동시인 경우가 있습니다. 예를 들어 리모콘의 전원 끄기 버튼, 소리 키우기 버튼, 넷플릭스 버튼 등 서로 다른 목적을 가진 버튼을 하나의 행동으로 묶어서 표현한 방식이라고 생각할 수 있습니다.
'디자인패턴' 카테고리의 다른 글
[디자인 패턴 - 번외] 플라이웨이트 (0) | 2023.07.28 |
---|---|
[디자인 패턴] - 컴포지트 패턴 (0) | 2023.06.30 |
[디자인 패턴] - 반복자 패턴 (2) | 2023.06.30 |
[디자인 패턴] 템플릿 메소드 패턴 (0) | 2023.06.28 |