자바 디자인패턴 스트래티지
모든 개발자는 유지 보수가 쉽도록 노력해서 코드를 만들어야 합니다.
가능한 읽기 쉽고 재사용 가능한 코드를 생산하는 노력과 실력을 말합니다.
비록 현실은 쉽지 않은 것도 사실입니다. 짧은 프로젝트 기간과 수시로 변경되는 요구사항 때문이죠.
하지만 그렇다 해도 할 수 있다면 디자인 패턴을 이용해 프로그래밍하는 노력을 게을리해서는 안됩니다.
오늘의 포스팅은 디자인패턴 중 스트래티지 패턴 "strategy" 에 대해 알아봅니다.
소프트웨어 개발에 있어서 바뀌지 않는 것과 바뀌는 것을 구분하기
"변화"
애플리케이션을 아무지 잘 디자인해도 시간이 지나면서 점점 성장하고 변화 되어야 합니다. 변화를 유발하는 것은 수없이 많고 당연한 과정입니다.
변화하는 것과 변화하지 않는 것을 구분해야 합니다. 시간이 지남에 따라 변화자체도 변화합니다.
세상의 모든 변화는 당연하다. 변화를 받아 들이자.
디자인 원칙 - 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다.
즉, 새로운 요구사항이 있을 때마다 바뀌는 부분이 있다면, 그 부분 <행동>을 바뀌지 않는 다른 부분으로부터 골라내어 분리해야 합니다.
"바뀌는 부분은 따로 뽑아서 캡슐화시킨다. 그렇게 하면 나중에 바뀌지 않는 부분에는 영향을 미치지 않은 채로 그 부분만 고치거나 확장할 수 있다."
이 개념은 간단합니다. 모든 디자인 패턴의 기반을 이루는 원칙입니다.
이렇게 하면 코드를 변경하는 과정에서 의도하지 않은 일이 일어나는 것을 줄이면서 시스템의 유연성은 향상할 수 있습니다.
오리 연못 시뮬레이션 게임을 만들어 봅니다.
이 게임에서 오리는 헤엄도 치고 꽥꽥거리는 소리도 내고, 날아다니기도 합니다.
오리는 여러 종류가 있습니다.
물오리, 붉은 머리 오리, 소리는 내지만 날지 못하는 모형 오리 등등.
오리의 종류는 앞으로도 계속해서 추가될 수 있습니다.
이 게임에서 오리가 할 수 있는 행동은 소리 내는 것과 날아다니는 행위입니다.
현재 판단으로는 변화할 수 있는 것은 소리 내는 행위와 날아다니는 방법 정도가 될 수 있겠네요.
오리의 원형이 되는 추상클래스를 하나 만들고
추가될 수 있는 오리들이 생길 때마다 오리원형클래스를 상속받아 구현을 합니다.
이때, 날아다는 행동과 소리 내는 행동은 변할 수 있는 능력이므로 인터페이로 구현해 유연하게 대응해 봅니다.
날기와 소리 내기 기능 만들기
날아다니는 행동을 Interface로 만들고 날아다니는 구체적인 행동(변할 수 있는 것)을 몇 개 구현해 봅니다.
public interface FlyBehavior {
public void fly();
}
일반적인 오리, 물오리, 붉은 머리오리들이 사용할 날아다니는 인터페이스를 구현한 기능. FlyBehavior를 구현합니다
public class FlyWithWings implements FlyBehavior{
@Override
public void fly() {
System.out.println("날고 있어요!");
}
}
고무오리나 모형오리들은 날지 못하죠. 이런 오리들이 사용할 기능. FlyBehavior를 구현합니다
public class FlyNoWay implements FlyBehavior{
@Override
public void fly() {
System.out.println("저는 못 날아요");
}
}
역시, 오리들 별로 낼 수 있는 소리행동(변할 수 있는 것) 클래스들을 만들어 봅니다.
public interface QuackBehavior {
public void quack();
}
대부분의 오리는 소리를 "꽥꽥" 내니까. "꽥꽥" 기능을 구현합니다. QuackBehavior를 구현합니다.
public class Quack implements QuackBehavior{
@Override
public void quack() {
System.out.println("꽥꽥");
}
}
붉은 머리 오리 같은 특이한 오리들은 특별한 소리를 냅니다. 꺼이꺼이 이런 소리요. QuackBehavior를 구현합니다.
public class GGuyGGuyQuack implements QuackBehavior{
@Override
public void quack() {
System.out.println("<<꺼이꺼이~~ >>");
}
}
고무오리는 삑삑 소리를 냅니다. QuackBehavior 를 구현합니다.
public class Squeak implements QuackBehavior{
@Override
public void quack() {
System.out.println("삑삑");
}
}
소리를 못 내는 모형 오리에 사용할 MuteQueck 기능. QuackBehavior 를 구현합니다.
public class MuteQuack implements QuackBehavior{
@Override
public void quack() {
System.out.println("<< 조용 ~~~ >>");
}
}
일반적으로 우리가 아는 오리는 소리를 내고 날아다닐 수 있기에 소리, 날기 등의 기능을 구현했습니다. 하지만 모형 오리나 고무오리는 특이한 오리들이죠.
고무오리의 경우 소리는 낼 수 있지만 날지 못하며, 모형 오리의 경우는 소리도 못 내고 날지도 못합니다.
오리에 따라 행동이 달라지는 겁니다. 변화되는 부분을 분리시켰습니다.
이제 모든 오리들이 갖는 공통기능 추상 클래스를 만들고, 없어지기도 하고 추가될 수 있는 오리들을 구현하면 됩니다.
- 물오리 - 꽥꽥 소리도 내고, 날 수 있습니다.
- 붉은 머리 오리 - 꺼이꺼이 소리를 내고, 날수 있습니다.
- 고무 오리 - 삑삑 소리는 내지만, 날수는 없습니다.
- 나무 모형 오리 - 나무라 소리도 못 내고, 날지도 못합니다.
오리 원형 추상 클래스를 만들고, 상속받는 구체적인 오리들을 구현해 봅니다.
모든 오리들의 원형 클래스 Duck를 만듭니다.
public abstract class Duck {
// (1) 행동 인터페이스 형식의 레퍼런스 변수 두개를 선언합니다. 오리별로 변할 수 있는 부분입니다.
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {
}
public abstract void display();
public void performFly(){ // (2) 날기 행동클래스를 위임합니다.
flyBehavior.fly();
}
public void performQuack(){ // (3) 소리내기 행동클래스를 위임합니다.
quackBehavior.quack();
}
public void swim(){ // (4) 변하지 않는 것. 모든 오리들은 물위에 뜬다고 가정한다면... 이 기능은 영원히 변하지 않으므로 구체화합니다.
System.out.println("모든 오리는 물위에 뜹니다. 가짜 오리도 뜨죠");
}
}
Duck 설명 변화하는 것과 아닌 것을 구분.
(1) 행동 인터페이스 형식의 레퍼런스 변수 두 개를 선언합니다. 오리별로 변할 수 있는 부분입니다.
(2) 날기 행동클래스를 위임합니다. (변화 대응)
(3) 소리 내기 행동클래스를 위임합니다. (변화 대응)
(4) 변하지 않는 것. 모든 오리들은 물 위에 뜬다고 가정한다면... 이 기능은 영원히 변하지 않으므로 구체화합니다.
게임에 나타날 오리들을 구현해 봅니다.
물오리는 소리도 내고 날기도 합니다.
public class MallardDuck extends Duck{
public MallardDuck() { // (1) 물오리는 꽥꽥거리고, 날수 있습니다.
flyBehavior = new FlyWithWings();
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("저는 물오리 입니다.");
}
}
붉은 머리 오리도 날기도 하고 특이한 소리를 내죠.
public class RedHeadDuck extends Duck{
public RedHeadDuck() { // (1) 붉은 머리 오리는 날기도 하지만, 특이한 울음소리를 갖고 있습니다.
flyBehavior = new FlyWithWings();
quackBehavior = new GGuyGGuyQuack();
}
@Override
public void display() {
System.out.println("저는 붉은 머리 오리 입니다.");
}
}
고무오리는 삑삑하는 특이한 소리를 내지만, 날지는 못합니다.
public class RubberDuck extends Duck{
public RubberDuck() {
quackBehavior = new Squeak(); // (1) 고무 오리는 삑삑 소리를 내고 날지는 못합니다.
flyBehavior = new FlyNoWay();
}
@Override
public void display() {
System.out.println("저는 고무오리입니다.");
}
}
모형오리는 날지도 못하며, 소리도 못 냅니다.
public class DecoyDuck extends Duck{
public DecoyDuck() {
quackBehavior = new MuteQuack(); // (1)모형 오리는 소리도 못내고 날지도 못합니다.
flyBehavior = new FlyNoWay();
}
@Override
public void display() {
System.out.println("저는 모형 오리 입니다.");
}
}
지금까지 만든 오리와 오리의 행동 코드들을 main 합수로 실행해 봅니다.
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallard = new MallardDuck(); //물오리
mallard.display();
mallard.performQuack();
mallard.performFly();
System.out.println("------------------------------");
Duck redheadDuck = new RedHeadDuck(); //붉은 머리 오리
redheadDuck.display();
redheadDuck.performQuack();
redheadDuck.performFly();
System.out.println("------------------------------");
Duck rubber = new RubberDuck(); // 고무 오리
rubber.display();
rubber.performQuack();
rubber.performFly();
System.out.println("------------------------------");
Duck decoy = new DecoyDuck(); // 모형 오리
decoy.display();
decoy.performQuack();
decoy.performFly();
}
}
실행
각 오리는 행동을 원형오리 클래스로부터 상속받지 않으며, 오리 별로 능력(날기, 소리 내기)을 행동 객체로 구성함으로 행동을 부여받게 됩니다.
상속보다는 구성을 활용한다.
날아가는 행동과 소리 내는 행동을 객체 생성자로 받아서 실행 중에 바꿀 수도 있겠죠?
마치며
스트래티지 패턴에 대해 공부했습니다.
스트래티 패턴에서는 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.
스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 교환할 수 있다.
알고리즘이란 본 예제에서는 소리와 나는 행동입니다. 저희는 소리와 날아다니는 행동을 캡슐화했습니다.
아래 그림에서 클라이언트는 "오리" 겠네요.
저희가 구현한 오리는 롤플레잉 게임(디아블로)의 소서리스, 네크로멘서, 바바리안이 될 수 있겠네요. 소리와 날아가는 행동은 각 캐릭터의 스킬능력일 수 있고요.~
'기타 IT 경험 > 객체지향, 방법론, 디자인패턴' 카테고리의 다른 글
애자일에 대해 알아보자 Agile (2) | 2022.12.16 |
---|---|
소프트웨어 공학- 소프트웨어 프로세스 모델의 이해 (0) | 2022.11.29 |
객체지향 개발 5대 원리 솔리드 원칙 (0) | 2017.07.25 |
댓글