스트레티지 패턴은 OCP를 지킬 수 있도록 도와준다.
Context
만약에 Robot
이란 추상클래스가 있고 Atom
과 Marin
이 이 추상클래스를 상속받아 각각의 공격방법과 이동 방법을 구현한다.
여기까지는 객체지향적인 설계라고 할 수 있는데, 자 그럼 이제부터 Atom
과 Marin
을 업그레이드 시켜보자.
<위의 설명의 모델>
Problem
기능 확장의 문제점
Atom
은 기존엔 걸을 수 밖에 없었다. 하지만 연구원들이 열심히 노력해서 걷기보다는 날 수 있도록 만들었다. 그렇다면 코드상 아래와 같아진다.
class Atom extends Robot{
// written something..
void move() {
// 이전코드
// System.out.println("I'm walking!");
// 수정코드
System.out.println("I'm flying!");
}
}
훌륭하다! OCP은 어떠한 기능이 추가되었을 때 변경없이 확장이 가능해야한다는 것, 하지만 여기서는 이동하는 것에 있어서 걷는 것에서 나는 것으로 기능이 확장되었으나 기존 Atom class의 코드를 수정하였기 때문에 원칙을 위배했다고 할 수 있다.
중복 코드의 문제점
연구원들은 열심히 차세대 로봇(SuperPowerRobot
)을 만들었다. 가장 최근의 기술을 쓰고자 Atom
의 나는 기술과
Marin
의 총쏘는 기술을 선정하여 그 둘의 장점을 합친 최신예 로봇이다. 그 후 몇년이 지나자 아래와 같이 기존의 나는 방법이 바뀌었다.
class Atom extends Robot{
// written something..
void move() {
// 이전코드
// System.out.println("I'm flying!");
// 수정코드
System.out.println("I'm flying using SuperPowerEngine!");
}
}
하지만 동일하게 SuperPowerRobot
에도 적용해줘야한다.
class SuperPowerRobot extends Robot{
// written something..
void move() {
// 이전코드
// System.out.println("I'm flying!");
// 수정코드
System.out.println("I'm flying using SuperPowerEngine!");
}
}
하나의 기능때문에 두 곳의 코드가 수정되었다. 이는 중복코드로써 유지보수성을 떨어트린다.
그럼 어떻게 해결할 수 있을까?
Solution
위와 같은 설계는 중복 코드와 OCP 위반이란 큰 오점을 남겼다. 잘 생각해보면 move
나 attack
는 추상화의 대상이 될 수 있다.
move
나 attack
는 로봇을 구성하는 많은 부분 중 하나이다, 즉 속성이 될 수 있다. 이를 추상화하여 인터페이스로 생성한 후
이를 상속받아 각각의 기능을 구현하는 클래스를 생성하여 로봇에 장착하면 위 중복코드의 문제점과 OCP 위반을 제거하며 구현할 수
있다(중복코드가 발생하지 않고, 확장에서도 interface를 구현하는 새로운 클래스만 만들어 사용하는 곳에서 set메서드를 통해
의존성을 주입해주니 각 로봇의 클래스 코드에는 아무런 수정이 발생하지 않는다)
<위의 설명의 모델>
abstract class Robot {
private String name;
private AttackStrategy attackStrategy;
private MoveStrategy moveStrategy;
public Robot(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAttackStrategy(AttackStrategy attackStrategy) {
this.attackStrategy = attackStrategy;
}
public void setMoveStrategy(MoveStrategy moveStrategy) {
this.moveStrategy = moveStrategy;
}
public void move() {
moveStrategy.move();
}
public void attack() {
attackStrategy.attack();
}
}
interface AttackStrategy {
public void attack();
}
interface MoveStrategy {
public void move();
}
class Flying implements MoveStrategy {
@Override
public void move() {
System.out.println("I'm flying~~");
}
}
class Missile implements AttackStrategy {
@Override
public void attack() {
System.out.println("Shoot missiles!");
}
}
class Flying implements MoveStrategy {
@Override
public void move() {
System.out.println("I'm flying~~");
}
}
class Punch implements AttackStrategy {
@Override
public void attack() {
System.out.println("Punch to enemy!");
}
}
class Atom extends Robot {
public Atom(String name) {
super(name);
}
}
class Marin extends Robot{
public Marin(String name) {
super(name);
}
}
class SuperPowerRobot extends Robot {
public SuperPowerRobot(String name) {
super(name);
}
}
class Sample {
public static void main(String[] args) {
Robot atom = new Atom("Atom"),
marin = new Marin("Marin"),
superRobot = new SuperPowerRobot("Super");
AttackStrategy
missile = new Missile(),
punch = new Punch();
MoveStrategy
walking = new Walking(),
flying = new Flying();
atom.setAttackStrategy(missile);
atom.setMoveStrategy(walking);
marin.setAttackStrategy(punch);
marin.setMoveStrategy(flying);
superRobot.setAttackStrategy(missile);
superRobot.setMoveStrategy(flying);
}
}
Summary
스트래티지 패턴은 전략을 쉽게 바꿀 수 있도록 해주는 디자인패턴이다, 전략의 의미는 알고리즘, 비즈니스 로직,
일을 수행하는 방식, 장착해야할 아이템 등이 될 수 있다. 롤의 챔피언만 해도 수시로 자신의 아이템 전략을 바꾼다.
이를 단일 클래스로 지정해버리면 바꿀 수 없지만 List<Item>
등과 같이 Item
을 추상화한 인터페이스 또는 추상클래스로
바꾼다면 충분히 지속적인 변경에도 무리가 없다.
'소프트웨어 > 디자인패턴' 카테고리의 다른 글
[소프트웨어] 디자인 패턴에 대해서 (0) | 2017.01.23 |
---|---|
[소프트웨어/디자인패턴] 옵저버 패턴(Observer Pattern) (0) | 2017.01.23 |
[소프트웨어/디자인패턴] 커맨드 패턴(Command Pattern) (2) | 2017.01.21 |
[소프트웨어/디자인패턴] 스테이트 패턴(State Pattern) (0) | 2017.01.19 |
[소프트웨어/디자인패턴] 싱글톤 패턴(Singleton Pattern) (1) | 2017.01.19 |