Developer Log
  • Wiki
  • Books
    • 리눅스 시스템 프로그래밍
      • 핵심 개념 소개
        • 시스템 프로그래밍
        • API와 ABI
        • 리눅스 프로그래밍의 개념
          • 파일과 파일시스템
          • 프로세스
          • 사용자와 그룹
          • 권한
          • 시그널
          • 프로세스간 통신
          • 에러 처리
    • 데이터 베이스 첫걸음
      • 데이터베이스란
        • 데이터베이스의 역할을 생각해 보자
          • 우리와 데이터베이스의 관계
          • 데이터베이스의 기본 기능
          • 데이터베이스 종류
      • 관계형 데이터베이스란
        • 대표적인 DBMS를 알아보자
          • 관계형 데이터베이스란
          • SQL 기초 지식
          • 관계형 데이터베이스를 다루기 위한 사전 지식
      • 데이터베이스에 얽힌 돈 이야기
        • 초기비용과 운영비용을 생각하자
      • 데이터베이스와 아키텍처 구성
        • 다중화에 대해 생각해보자
          • 아키텍처란
          • 데이터베이스의 아키텍처
            • 역사와 개요
              • Stand-alone
              • 클라이언트/서버
              • Web 3계층
            • 가용성과 확장성의 확보
          • DB 서버의 다중화
            • 클러스터링
            • 리플리케이션
          • 성능을 추구하기 위한 다중화 - Shared Nothing
          • 적합한 아키텍처를 설계하기 위해
      • DBMS를 조작할 때 필요한 기본 지식
        • MySQL 설치해보자
        • MySQL과 커넥션 만들기, 데이터베이스에 전화걸기
        • SQL과 관리 명령의 차이
        • 관계형 데이터베이스의 계층
      • SQL 문의 기본
        • SELECT 문으로 테이블 내용을 살펴보자
        • SELECT 문을 응용해보자
        • 데이터를 갱신, 삽입, 제거해보자
        • 뷰를 작성하고 복수 테이블에서 선택해보자
      • 트랜잭션과 동시성 제어
      • 테이블 설계의 기초
      • 백업과 복구
      • 성능을 생각하자
    • 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴
      • 객체 지향
        • 들어가기
        • 객체 지향
        • 다형성과 추상 타입
        • 재사용: 상속보단 조립
      • 설계 원칙 / DI와 서비스 로케이터
        • 설계 원칙: SOLID
          • 단일 책임 원칙(Single Responsibility Principle)
          • 개방 폐쇄 원칙(Open - Closed Principle)
          • 리스코프 치환 원칙(Liskov Substitution Principle)
          • 인터페이스 분리 원칙(Interface Segregation Principle)
          • 의존 역전 원칙(Dependency Inversion Principle)
          • SOLID 정리
        • DI(Dependency Injection)와 서비스 로케이터
      • 주요 디자인 패턴
        • 디지인 패턴이란?
        • 전략(Strategy) 패턴
        • 템플릿 메서드(Template Method) 패턴
        • 상태(State) 패턴
        • 데코레이터(Decorator) 패턴
        • 프록시(Proxy) 패턴
        • 어댑터(Adapter) 패턴
        • 옵저버(Observer) 패턴
        • 미디에이터(Mediator) 패턴
        • 파사드(Facade) 패턴
        • 컴포지트(Composite) 패턴
        • 널(Null) 객체 패턴
        • 팩토리 메서드 패턴
        • 커맨드 패턴
        • 추상 팩토리 패턴
    • 테스트 주도 개발
      • 1부
        • 다중 통화를 지원하는 Money 객체
        • 타락한 객체
        • 모두를 위한 평등
        • 프라이버시
        • 솔직히 말하자면
        • 돌아온 '모두를 위한 평등'
        • 사과와 오렌지
Powered by GitBook
On this page

Was this helpful?

  1. Books
  2. 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴
  3. 주요 디자인 패턴

전략(Strategy) 패턴

한 과일 매장은 상황에 따라 다른 가격 할인 정책을 적용하고 있다. 매장은 첫 번째 손님을 위한 할인과 신선도가 떨어진 상품에 대한 할인 정책을 제공한다고 가정하면, Calculator 객체는 아래와 같이 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 += (item.getPrice() * 0.9);     // 첫 손님 10% 할인

            } else if(item.isFresh()) {
                sum += (item.getPrice() * 0.8);     // 덜 신선한 상품은 20% 할인

            } else {
                sum += item.getPrice();

            }

        }

        return sum;

    }

}

위의 코드는 비교적 그럴듯 해보이지만, 다음과 같은 문제가 있다.

  • 서로 다른 계산 정책이 한 코드에 섞여 있어, 정책이 추가될수록 코드 분석을 어렵게 만든다.

  • 가격 정책이 추가될 때마다 calculate 메서드를 수정하는 것이 점점 어려워진다. 예를 들면, 마지막 손님 50% 할인같은 새로운 정책이 추가된다면, calculate 메서드에는 if 블록이 하나 더 추가되어야 한다.

위와 같은 문제를 해결하기 위한 방법 중 하나는 아래 그림처럼 가격 할인 정책을 별도 객체로 분리하는 것이다.

DiscountStrategy 인터페이스는 상품의 할인 정책을 추상화하였고, 각 콘크리드 객체는 상황에 맞는 할인 계산 알고리즘을 제공한다. Caculator 객체는 가격 합산 계산의 책임을 가진다. 결과적으로, 가격 할인 알고리즘을 추상화하고 있는 DiscourseStrategy를 전략(Strategy) 이라 부르고 가격 계산 기능의 책임을 갖고 있는 Calculator를 컨텍스트(Context) 라 부른다. 이는 특정 Context에서 각 알고리즘 전략(Strategy)을 별도로 분리하는 설계 방법이며, 전략 패턴 이라고 부른다.

전략 패턴에서 Context는 사용할 전략을 직접 선택하지 않고, API Client가 사용할 전략을 Context에 전달해준다. 즉, DI(의존 주입)을 이용해서 Context에 전략을 전달하는 것이다.

앞서 보여준 예제에 Strategy Pattern을 적용하면 Calculator는 아래와 같다.

Context
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.getDicountPrice(item);

        }

        return sum;

    }

}

Calculator 객체는 생성자를 통해서 전략 객체를 주입받고, calculate() 메서드에는 각 Item의 가격을 책정할 때 전략 객체로 가격 정책을 적용하고 있다.

Strategy
public interface DiscountStrategy {
    int getDicountPrice(Item item);

}

전략 객체는 Context를 사용하는 Client에서 직접 생성하며, 이는 Client가 전략의 상세 구현에 대한 의존이 발생한다는 것을 의미한다.

Client
    @Test
    void shouldApplyFirstGuestStrategy() {
        // given
        DiscountStrategy discountStrategy = new FirstGuestDiscountStrategy();
        Calculator calculator = new Calculator(discountStrategy);
        List<Item> items = Arrays.asList(new Item("사과", 1000, LocalDateTime.now()));

        // when
        int sum = calculator.calculate(items);

        // then
        assertEquals(900, sum, "should apply 10% discounts");

    }

Client가 전략의 인터페이스가 아닌 상세 구현에 의존한다는 것이 문제처럼 보일 수 있으나, 전략의 Concrete 객체와 Client가 쌍을 이루기 때문에 유지 보수 문제가 발생할 가능성이 줄어든다.

예를 들어, 새로운 가격 할인 정책을 추가해야 한다면 새로운 Concrete 객체를 추가하고 Client만 수정하면 된다. 따라서 전략 패턴은 Client-Concrete 객체가 쌍을 이루며 오히려 코드 이해와 응집도를 높여준다.

전략 패턴의 가장 큰 이점은 확장에는 열려 있고 변경에는 닫혀 있는 개방 폐쇄 원칙 따르는 구조를 갖게 된다는 것이다.

만약 마지막 손님 대폭 할인 정책을 추가한다면, Calculator Context에는 변경이 없으며, 오직 새로운 할인 정책에 대한 Concrete 객체 추가하여 Client에서 적용하기만 하면 된다. 따라서, Calculator Context는 확장에는 열려 있고 변경에는 닫혀 있게 된다.

이전의 if-else 블록과 완전히 동일한 기능을 제공하지만, 성능의 장단점에 따라 알고리즘을 선택해야 하는 경우에도 전략 패턴을 사용할 수 있다.

Previous디지인 패턴이란?Next템플릿 메서드(Template Method) 패턴

Last updated 5 years ago

Was this helpful?