디자인 패턴 - Strategy Pattern

bp.chys·2020년 5월 17일
0

OOP & Design Pattern

목록 보기
12/17
post-custom-banner

전략 패턴

  • 전략 패턴은 정책에 따라 유연하게 동작하는 메서드(알고리즘)를 캡슐화하여 다형성을 구현하는 디자인 패턴이다.
  • 만약 패턴을 사용하지 않으면, 모든 정책에 대한 로직이 if-else 문으로 분기 되어야 하고 이는 결과적으로 코드 분석과 유지보수를 어렵게 만든다.
    • 정책을 사용하는 쪽에서 코드 수정이 일어나고, 정책이 늘어날수록 코드가 복잡해진다.
  • 기능을 사용하는 부분구현하는 부분을 명확히 분리하는 것이 중요하다.

예시

  • 한 매장에는 첫 손님을 위한 첫 손님 할인과 저녁에 신선도가 떨어진 과일에 대한 덜 신선한 과일 할인 정책이 있다.
  • 전략 패턴을 사용하지 않았을 때는 상황에 따라 if-else 문으로 분기를 해줘야 하고, 이는 서로 다른 가격 정책들이 한 곳에 밀집되어있어 가독성이 떨어지게 된다.
public class Calculator {
    public int calculate(boolean firstGuest, List<Item> items) {
        int sum = 0;
        for (Item item : items) {
            if (firstGuest) {
                sum += (int) (item.getPrice() * 0.9);
            } else if (! item.isFresh()) {
                sum += (int) (item.getPrice * 0.8);
            } else {
                sum += item.getPrice();
            }
        }
        return sum;
    }
}
  • 여러 할인 정책들을 DiscountStrategy라는 이름의 타입으로 추상화를 하고 각각의 할인된 가격을 반환하게 한다면 정책에 대한 구현과 사용에 대한 책임을 명확히 분리할 수 있다.
  • 새로운 정책이 추가되거나 기존의 정책이 사라지더라도, 사용하는 쪽에서는 코드를 수정할 필요가 없다.
  • 이때 사용하는 실제 할인 정책에 해당하는 클래스는 실행시점에 동적으로 얼마든지 변경될 수 있다.
public class Calculator {
    private DiscountStrategy discountStrategy;
   
    public Calculator(DiscountStrategy discountStrategy) {
        this.discountStrategy = discountStrategy;
    }
   
    public int calculate(List<Item> items) {
        int sum = 0;
        for (Item item : items) {
            sum += discountStrategy.getDiscountPrice(item);
        }
        return sum;
    }
}
  • 요구 사항으로 주어졌던, 두 가지 할인 정책에 대한 구현 클래스는 다음과 같이 작성할 수 있다.
  • 두 구현체 모두 DiscountStrategy라는 인터페이스를 구현한다.
// 첫 손님 할인 정책
public class FirstGuestDiscountStrategy implemensts DiscountStrategy {
    @Override
    public int getDiscount(Item item) {
        return (int) (item.getPrice() * 0.9);
    }
}
// 덜 신선한 과일 할인 정책
public class NonFreshItemDiscountStrategy implemensts DiscountStrategy {
    @Override
    public int getDiscount(Item item) {
        return (int) (item.getPrice() * 0.8);
    }
}

SOLID 관점에서 바라본 전략 패턴

  • 객체의 사용과 구현의 책임을 분리하기 때문에 단일 책임 원칙(SRP)를 따른다고 볼 수 있다.
  • 기능 확장에는 열려있고 변경에는 닫혀있는 개방 폐쇄 원칙(OCP)도 따르는 구조를 갖게 된다.
  • 상위 타입의 getDiscount() 메서드를 하위 타입에서 대체 가능한 구현(재정의)을 하고 있으므로 리스코프 치환 원칙(LSP)도 잘 지켜진다고 볼 수 있다.
  • 인터페이스를 정의할 때 사용자(Calculator) 관점에서 할인된 금액을 계산하는 메서드를 만들었고, 다른 클라이언트에서는 사용되고 있지 않으므로 인터페이스 분리 원칙(ISP)도 따르고 있다.
  • 인터페이스로 타입을 추상화하지 않았다면, 구현체를 직접 의존하게 되고 이는 두 객체간의 강한 결합으로 런타임에 메서드를 변경하지 못하게 된다. 사용객체과 구현객체 모두 추상화에 의존하게 하므로서 느슨한 결합을 가져가고 있으므로 의존성 역전 원칙(DIP)도 잘 따르고 있다고 할 수 있다.

결론

전략 패턴은 디자인 패턴 중에서도 많이 사용되는 패턴에 속한다.
전략 패턴을 사용하면 기본적으로 코드 중복 방지, 런타임 시점에 메서드 변경기능 확장이 용이 하다는 장점을 얻을 수 있다. 또한 결과적으로 위에서 언급한만큼 객체 지향 설계 원칙인 SOLID도 잘 따르는 구조를 가질 수 있기 때문에 기능이 많이 추가되고 수정되면서 많은 장점을 체감할 수 있을 것이다.

개발 도중에 많은 양의 if-else 코드를 발견했다면, 전략 패턴을 사용해서 리팩토링할 수 없는지 고민해보자. 코드가 훨씬 깔끔해지고 더욱 객체 지향적인 구조를 가져갈 수 있을 것이라 생각한다.


참고자료

  • 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴 - 최범균
profile
하루에 한걸음씩, 꾸준히
post-custom-banner

0개의 댓글