Strategy Pattern

알고리즘 군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.

스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.

 


 

구현

 

의자를 만들 때 컴퓨터로 미리 디자인을 하려고 합니다.

의자는 휠이 있는 의자도 있고 휠이 없는 의자도 있습니다.

그리고 의자의 종류는 다양합니다.

사무용 의자, 흔들의자, 무중력 의자 등...

이런 의자를 객체지향 기법을 사용하여 Chair라는 부모 클래스를 만든 다음, 그 클래스를 확장하여 다른 종류의 의자들도 만듭니다.

public abstract class Chair {
    Moves moves;
    Sounds sounds;
}

 

의자는 휠이 있어서 움직이는 의자인지 아니면 휠이 없어서 움직이지 못하는 의자인지 정의하고,

의자의 종류마다 어떤 소리가 나는지 만들 예정입니다.

 

Moves

package cg.park.designpatten.designpatten.strategy;

public interface Moves {
    public void move();
}

 

Sounds

package cg.park.designpatten.designpatten.strategy;

public interface Sounds {
    public void sound();
}

 

의자가 움직이는지와 어떤 소리가 나는지에 대해서 정의합니다.

하지만 움직이지 않은 의자의 경우 소리가 나지 않습니다.

그러면 어떤 방법으로 클래스를 설계하고 구현해야 할까요?

 

 

이렇게 N:N으로 클래스마다 이것저것 만들어 놓는다면 같은 의자라도 어떤 방식으로 구현되었는지 감이 오지 않을 것입니다.

 

*디자인 원칙

애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다.

 

코드에 새로운 요구사항이 있을 때마다 바뀌는 부분이 있다면, 그 행동을 바뀌지 않는 다른 부분으로부터 골라내서 분리해야 한다는 것을 알 수 있습니다.

 

 

바뀌는 부분과 그렇지 않은 부분을 분리하여 설계를 합니다.

*Chair

의자를 추상화해놓고 의자의 종류마다 특성에 맞게 구현하였습니다.

*Moves 

움직임에 대해 의자 종류에 상관없이 휠이 있다/없다 로 분리하였습니다.

*Sounds

소리에 대해서는 의자 종류마다 다른 소리가 나는 것을 표현하였습니다.

 

*디자인 원칙

구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.

 

"인터페이스에 맞춰서 프로그래밍한다"는 것은 "상위 형식에 맞춰서 프로그래밍한다"는 것을 뜻합니다.

의자 종류마다 소리를 알고 싶을 때 구성에 맞춰서 프로그래밍을 한다면 다음과 같이 할 수 있습니다.

//무중력의자 소리
ZeroGravityChair zeroGravityChair = new ZeroGravityChair();
zeroGravityChair.ZeroGravityChairSound();

//사무용의자 소리
OfficeChair officeChair = new OfficeChair();
officeChair.OfficeChairSound();

//흔들의자 소리
RockingChair rockingChair = new RockingChair();
rockingChair.RockingChairSound();

 

하지만 인터페이스/상위 형식에 맞춰서 프로그래밍한다면 다음과 같이 할 수 있습니다.

//무중력의자 소리
Chair zeroGravityChair = new ZeroGravityChair();
zeroGravityChair.eventSounds();

//사무용의자 소리
Chair officeChair = new OfficeChair();
officeChair.eventSounds();

//흔들의자 소리
Chair rockingChair = new RockingChair();
rockingChair.eventSounds();

 

객체 생성이 소리 메서드를 호출할 때 룰이 생깁니다.

 

*동적으로 소리를 지정하는 방법

 

package cg.park.designpatten.designpatten.strategy;

public abstract class Chair {
    Moves moves;
    Sounds sounds;

    public Chair (){}

    public void eventMoves() { moves.move(); }

    public void eventSounds() { sounds.sound(); }

    public void setMoves(Moves moves) {
        this.moves = moves;
    }

    public void setSounds(Sounds sounds) {
        this.sounds = sounds;
    }

}

 

Chair 클래스에 Moves와 Sounds를 동적으로 바꿀 수 있게 setMoves와 setSounds를 생성합니다.

 

//무중력의자 인스턴스 생성
Chair chair = new ZeroGravityChair();
chair.eventMoves();//움직임 여부 호출
chair.eventSounds();//의자 소리 호출

chair.setMoves(new dontMovingChair());//못움직이는 의자로 선언
chair.setSounds(new RockingChairSound());//흔들의자 소리로 변경
chair.eventMoves();//움직임 여부 호출
chair.eventSounds();//의자 소리 호출

 

각 단계에 맞게 움직임과 소리가 호출되고 있습니다.

 

"A에는 B가 있다" 관계에 대해 생각해 봅시다.

Moves와 Sounds가 있으며, 각각 움직임과 소리를 위임받습니다.

두 클래스를 이런 방법으로 합치는 것을 구성을 이용하는 것이라고 부릅니다.

위에 나와있는 의자 클래스에서 행동을 상속받는 대신, 올바른 행동 객체로 구성됨으로써 행동을 부여받게 됩니다.

이 기술은 매우 중요한 기술입니다.

사실 우리가 사용한 이 방법은 세 번째 디자인 원칙입니다.

 

*디자인 원칙

상속보다는 구성을 활용한다.

 

이 구성을 이용하여 시스템을 만들면 유연성을 크게 향상할 수 있습니다.

단순히 알고리즘 군을 별도의 클래스의 집합으로 캡슐화할 수 있도록 만들어주는 것뿐 아니라, 구성요소로 사용하는 객체에서 올바른 행동 인터페이스를 구현하기만 하면 실행 시에 행동을 바꿀 수도 있게 해 줍니다.

 

후기

 

디자인 패턴이라는 말이 처음에는 어렵게 느껴졌다.

하지만 개발 생활을 해보면서 느낀 점은 이러한 방법론은 선배 개발자님들의 시행착오를 바탕으로 더 나은 개발 방법인 것이다.

단어를 암기하듯 디자인 패턴을 암기하는 것이 아니라 직접 구현해보면서 이 패턴이 왜 생겨났고, 그래서 뭐가 더 좋아졌는지를 알게 된다면 더 이상 암기는 하지 않아도 될 것이다.

이번에 구현해본 Strategy Pattern은 실무에서도 많이 볼 수 있는 패턴이다.

의자나 오리가 아닌 각 서비스에 맞는 구현 주제에 관련하여 인터페이스에 맞게 프로그래밍을 하고, 알고리즘들을 캡슐화하여 교환할 수 있게 만드는 방법은 알아둘 필요가 있다. 

감사합니다.

 

Git : https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

+ Recent posts