개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

공부 › Java › 클래스와 객체

전략 패턴

결제 수단이 하나 늘 때 고쳐야 하는 곳

결제 수단(카드, 계좌이체, 카카오페이)마다 처리 방식이 다릅니다. if-else로 나누는 코드와, 수단별 구현을 따로 두고 갈아 끼우는 코드(전략 패턴)가 있습니다.

실행으로 대조 · JDK 21에서 같은 구조의 코드로 확인

이 실습에 나오는 개념

전략 패턴
바뀔 수 있는 알고리즘이나 동작을 인터페이스로 정의하고 각각을 별도의 클래스로 구현해, 실행 중에 교체할 수 있게 하는 패턴이다. 사용하는 쪽은 인터페이스에만 의존하므로 새로운 방식을 추가해도 기존 코드를 수정하지 않는다.
디자인 패턴
소프트웨어 설계에서 반복적으로 나타나는 문제에 대한 검증된 해결 방법에 이름을 붙여 정리한 것이다. 개발자들 사이의 공통 용어 역할을 해 설계 의도를 짧게 전달할 수 있게 한다. 특정 문제를 해결하기 위한 수단이므로 필요 없는 곳에 적용하면 복잡도만 높아진다.
다형성
같은 타입의 참조로 같은 메서드를 호출해도 실제 객체의 종류에 따라 다른 동작이 실행되는 성질이다. 상위 타입(인터페이스나 상위 클래스) 변수에 여러 하위 타입 객체를 담을 수 있고, 어떤 구현이 실행될지는 실행 시점에 결정된다(동적 바인딩). 호출하는 코드를 바꾸지 않고 새로운 구현을 추가할 수 있게 해 준다.
SOLID
객체지향 설계의 다섯 가지 원칙이다. 단일 책임(SRP: 클래스의 변경 이유는 하나), 개방-폐쇄(OCP: 기존 코드 수정 없이 확장), 리스코프 치환(LSP: 하위 타입은 상위 타입을 대체할 수 있어야 함), 인터페이스 분리(ISP: 쓰지 않는 메서드에 의존하지 않게 인터페이스를 작게), 의존 역전(DIP: 구체 클래스가 아니라 추상화에 의존)을 말한다. 변경에 강한 구조를 만드는 것이 목적이다.
인터페이스
클래스가 구현해야 할 메서드의 목록(계약)을 정의한 타입이다. 인스턴스 필드를 가질 수 없고, 한 클래스가 여러 인터페이스를 구현할 수 있다. 사용하는 코드가 인터페이스에만 의존하면 구현 클래스를 바꾸거나 테스트용 가짜 구현으로 교체하기 쉽다.
DI(의존성 주입)
객체가 사용하는 다른 객체(의존성)를 스스로 만들지 않고 외부에서 전달받는 방식이다. 스프링에서는 컨테이너가 빈을 생성하면서 생성자, 세터, 필드를 통해 의존성을 넣어 준다. 구현 클래스를 바꾸거나 테스트에서 가짜 객체를 넣기 쉬워지고 클래스 사이의 결합도가 낮아진다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. 결제 수단별 처리가 PaymentService의 메서드 안에 if-else로 들어 있습니다. 환불과 취소 메서드에도 같은 모양의 분기가 있습니다.
  2. 네이버페이 추가: pay(), refund(), cancel() 세 메서드에 각각 else if를 하나씩 끼워 넣어야 합니다. 이미 잘 돌아가던 메서드를 고치는 것이므로 카드와 카카오페이까지 다시 테스트해야 합니다. 한 곳이라도 빠뜨리면 그 기능만 실행 중에 예외가 납니다.
  3. 카카오페이로 결제를 요청합니다: pay("KAKAO", order).
  4. type이 "CARD"인지 비교합니다. 아닙니다. 다음 조건으로 넘어갑니다.
  5. type이 "BANK"인지 비교합니다. 아닙니다. 다음 조건으로 넘어갑니다.
  6. type이 "KAKAO"인지 비교합니다. 맞습니다. 이 블록 안의 카카오페이 처리 코드를 실행합니다.

실행 결과

코드

@Service
class PaymentService {
    void pay(String type, Order order) {
        if (type.equals("CARD")) {
            // 카드사 승인 요청 ... (30줄)
        } else if (type.equals("BANK")) {
            // 계좌이체 ... (25줄)
        } else if (type.equals("KAKAO")) {
            // 카카오페이 ... (40줄)
        } else if (type.equals("NAVER")) {      // 추가: 기존 메서드를 수정
            // 네이버페이 ... (35줄)
        } else {
            throw new IllegalArgumentException(type);
        }
    }
    // refund(), cancel()에도 같은 분기가 반복된다
}

이 모형의 전제

결제 처리의 실제 내용은 생략하고 구조만 비교했습니다. if-else 방식에서 환불과 취소에도 같은 분기가 있다는 것은 흔한 상황을 가정한 것입니다. 전략 패턴의 정의는 GoF 디자인 패턴의 설명을 따랐고, 스프링이 같은 인터페이스의 구현체를 List로 한꺼번에 주입해 주는 것은 스프링의 표준 기능입니다. 맵에서 꺼낸 구현 객체의 메서드가 실제로 실행되는 것과 if-else의 비교 횟수는 이 PC의 JDK 21에서 같은 구조의 코드로 확인했습니다. 스프링이 구현체를 주입하는 부분은 실행하지 않았습니다.

앞 실습: 불변과 방어적 복사 · 다음 실습: ==와 equals

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요