디자인 패턴 중 전략(Strategy) 패턴 쉬운 예제

Choizz·2023년 1월 19일
0

디자인 패턴

목록 보기
2/8

이번 포스트팅은 디자인 패턴중 전략 패턴을 적용해보고 실습해 보자.

전략 패턴

비슷한 기능(알고리즘)을 수행하는 경우, 캡슐화하여 클라이언트로부터 분리를 통해 코드를 변경을 하거나 기능을 확장을 용이하게 해준다.

중요한 것은 변하는 기능을 독립적으로 캡슐화하는 것이다. 상속보단 구현(인터페이스)을 사용한다.


오리 특징 출력 프로그램

  • 전략 패턴을 사용해서 오리의 특징을 출력하는 간단한 프로그램을 작성해 보았다.
  • 오리는 종류마다 나는 것(이하 fly)과 꽥꽥 소리를 내는(이하 quack) 특징을 다르게 갖고, 추후에 추가되거나 변경될 수 있다.

1) 먼저 변경이 될 가능성이 있는 부분인 fly 특성을 캡슐화 하자.

  • 일단 FlyBehavior 인터페이스를 만들고 클래스들이 이것을 구현하게 한다.
public interface FlyBehavior {
    public void fly();
}
public class HighFly implements FlyBehavior {

    @Override
    public void fly() {
        System.out.println("높게 날아요");
    }
}
public class LowFly implements FlyBehavior {

    @Override
    public void fly() {
        System.out.println("낮게 날아요");
    }
}
public class NoFly implements FlyBehavior {

    @Override
    public void fly() {
        System.out.println("못 날아요");
    }
}


  • QuackBehavior 인터페이스를 만들고 이것을 구현하는 클래스들을 만든다.
public interface QuackBehavior {

    public void quack();
}
public class NoQuack implements QuackBehavior {

    @Override
    public void quack() {
        System.out.println("조용");
    }
}
public class BigQuack implements QuackBehavior {

    @Override
    public void quack() {
        System.out.println("크게 꽥");
    }
}
public  class SmallQuack implements QuackBehavior {

    @Override
    public void quack() {
        System.out.println("작게 꽥");
    }
}


2) 오리가 공통적으로 갖는 특성을 추상화하여 추상 클래스로 만든다.

  • 여기서는 생성자로 FlyBehavior와 QuackBehavior 타입을 받고 필드로 만든다(DI).
  • 이렇게 구체적인 클래스에 의존하는 것 보다 추상화된 인터페이스에 의존하는 것이 나중에 코드 수정을 최소화할 수 있다.
public abstract class Duck {

    protected FlyBehavior flyBehavior;
    protected QuackBehavior quackBehavior;

	// 인터페이스 타입을 DI한다.
    public Duck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {
        this.flyBehavior = flyBehavior;
        this.quackBehavior = quackBehavior;
    }
	
    //오리가 소리를 내는 것을 quackBehavior에 위임한다.
    public void doQuack() {
        quackBehavior.quack();
    }
	
    //오리가 나는 것을 flyBehavior에 위임한다.
    public void doFly() {
        flyBehavior.fly();
    }
    
    //오리가 다친다.
    public boolean injured() {
        System.out.println("날개 부상");
        return true;
    }
    
    //오리가 다칠 경우 나는 행동을 바꾼다.
    public void setFly(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }
    
    public void setQuack(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }
	
    
    public abstract void display();
}
  • Duck 클래스를 상속하는 클래스들을 만든다.
public class Duck1 extends Duck {

    public Duck1(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {
        super(flyBehavior, quackBehavior);
    }

    @Override
    public void display() {
        System.out.println("오리 1입니다.");
    }
}
public class Duck2 extends Duck{

    public Duck2(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {
        super(flyBehavior, quackBehavior);
    }

    @Override
    public void display() {
        System.out.println("오리 2");
    }
}
public class Duck3 extends Duck {

    public Duck3(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {
        super(flyBehavior, quackBehavior);
    }

    @Override
    public void display() {
        System.out.println("오리 3입니다.");
    }
}


3) 출력

  • Duck들을 생성하면서 구체적인 클래스(fly, quack 인터페이스를 구현)들을 주입한다.
  • 이 구체적인 클래스들은 다형성 덕분에 Duck 클래스에서는 추상화 타입(FlyBehavior, QuackBehavior)으로 들어가게 된다.
	    // Duck을 생성하면서 구체적인 클래스들을 주입한다.
        Duck duck1 = new Duck1(new HighFly(), new BigQuack());
        Duck duck2 = new Duck2(new LowFly(), new SmallQuack());
        Duck duck3 = new Duck3(new NoFly(), new NoQuack());

        duck1.display();
        duck1.doQuack();
        duck1.doFly();

        System.out.println("================");
        duck2.display();
        duck2.doQuack();
        duck2.doFly();

        System.out.println("================");
        duck3.display();
        duck3.doQuack();
        duck3.doFly();

fly 특성을 하나 더 추가하게 됐다고 가정해보자.

  • MiddleFly라는 클래스를 추가한다.
public class MiddleFly implements FlyBehavior{

    @Override
    public void fly() {
        System.out.println("중간 높이로 난다.");
    }
}

전략 패턴을 사용하면

  • Duck 클래스나 다른 클래스의 코드 변경 없이 기능만 추가할 수 있다.
  • 런타임 중에 코드를 바꾸고 다른 객체를 주입하는 것이 가능해 진다(set 메서드).
	    // Duck을 생성하면서 구체적인 클래스들을 주입한다.
        Duck duck1 = new Duck1(new HighFly(), new BigQuack());
        Duck duck2 = new Duck2(new LowFly(), new SmallQuack());
        Duck duck3 = new Duck3(new NoFly(), new NoQuack());

        duck1.display();
        duck1.doQuack();
        duck1.doFly();

        System.out.println("================");
        duck2.display();
        duck2.doQuack();
        duck2.doFly();

        System.out.println("================");
        duck3.display();
        duck3.doQuack();
        duck3.doFly();
        
         System.out.println("=====런 타임 변경======");
        //만약 오리가 다치면 fly 특성을 바꿔 줄 수 있다.
        if (duck1.injured()) {
            duck1.setFly(new MiddleFly());
            duck1.display();
            duck1.doFly();
        }
//결과

오리 1입니다.
크게 꽥
높게 날아요
================
오리 2
작게 꽥
낮게 날아요
================
오리 3입니다.
조용
못 날아요
=====런 타임 변경======
날개 부상
오리 1입니다.
중간 높이로 날아요.

정리

  • Duck이 FlyBehavior와 QuackBehavior에 의존하지만 인터페이스에 의존함으로써 변화에 더 유연하게 대처할 수 있게 된다.
  • 변화하는 부분을 캡슐화하여 구현함으로서 기능이 추가가 되거나 변경이 되어도 클라이언트 코드에는 영향이 없다.
  • OCP 원칙과 DIP 원칙을 지킬 수 있다.
  • 전략 패턴은 실행 전에 먼저 조립을 해두고 실행을 한다. 그리고 중간에 필요한 특성을 바꿀 때는 setter를 사용해서 중간에 전략을 바꿀 수 있다. 하지만 싱글톤 방식에서 setter 메서드를 사용할 때는 동시성 문제가 발생할 가능성이 있다는 점을 알아두어야 한다.

Reference

profile
집중

0개의 댓글