6장. 스프링이 사랑한 디자인 패턴

스프링에서 지원하는 여러 특징들은 어떻게 설계 되었을까? 이 챕터는 디자인패턴으로 그 내면을 유추해볼 수 있다.

Chapter 6: 스프링이 사랑한 디자인 패턴

어댑터 패턴

어댑터 패턴이란?

지원하지 않는 기능을 중계 역할을 구현하여 호환되도록 유도하는 디자인 패턴으로, 다형성을 활용한 방식이다.

어댑터 패턴은 언제 쓰일까?

  1. 레거시 프로젝트에서 최신 버전 기능을 제공해야 하는데 타입이 지원되지 않을 때

  2. 써드 파티 라이브러리를 활용해서 구현 부분을 새롭게 정의 했을 때

다양한 상황이 더 연출되겠지만, 결국 사용하게 되는 계기 자체는 현재 시스템에서 지원하지 않는 부분을 호환 시킬 수 있도록 인터페이스를 구현하도록 하는 문제 해결 방법이다.

어떻게 적용할까? 아래 상황을 가정하고, 어댑터 패턴을 적용하여 해결하는 코드를 작성한다.

이런 상황이 주어졌을 때 생각하기 쉬운 부분은 DB에게 명령을 날리는 행위 자체를 인터페이스로 빼내고, RDBMS 구현체, NoSQL 구현체를 두는 방법이다.

public interface Database {  
    void save();  
    void delete();  
  
}

public class LegacyRdbmsRepository {  
  
    public void insert() {  
        System.out.println("insert");  
    }  
  
    public void update() {  
        System.out.println("update");  
    }  
  
    public void select() {  
        System.out.println("select");  
    }  
  
    public void delete() {  
        System.out.println("remove");  
    }  
  
    public void create() {  
        System.out.println("create");  
    }  
  

public class MongoRepository {  
  
    public void createDocument() {  
        System.out.println("Creating document in MongoDB");  
    }  
  
    public void readDocument() {  
        System.out.println("Reading document from MongoDB");  
    }  
  
    public void updateDocument() {  
        System.out.println("Updating document in MongoDB");  
    }  
  
    public void deleteDocument() {  
        System.out.println("Deleting document from MongoDB");  
    }  
  
}

새롭게 NoSQL 을 사용하는 코드를 만들었다. 기존에 사용중이던 RDBMS 조작 코드는 건들 수 없기 때문에, 위에 만들어둔 Database 인터페이스를 활용해서 어댑터를 구성한다.

어댑터를 구성하게 되면 Database 의 구현체이기 때문에 비즈니스 로직을 수정하지 않고 다형성을 활용해서 요구사항을 정상적으로 반영할 수 있게된다.

이제 이렇게 구성된 데이터베이스 조작 객체를 비즈니스 로직에서 사용할 때 공개되 인터페이스 메서드를 사용하고, 의존 관계를 주입할 때 Rdbms -> NoSQL 로 변경해주기만 하면 된다.

어댑터 패턴을 활용하게 되었을 때 뚜렷한 장점은 기존 코드를 크게 수정하지 않고 새로운 요구사항을 처리할 수 있다는 것으로 유연성이 큰 장점이다.

반대로, 어댑터를 많이 사용하게 되면 코드의 복잡도가 늘어나는건 당연할 뿐더러 사소한 수정에도 과도한 수정 비용이 발생한다.

참고 자료

Adapter Design Pattern The Adapter Pattern in Java

프록시 패턴

프록시 패턴이란?

프록시 패턴은 호출하는 행위를 직접적으로 호출하지 않고, 중간에 추가적인 계층을 거쳐 원하는 행위를 호출하는 패턴이다.

직접 호출할 수도 있지만 우리는 왜 중계 객체를 거쳐 호출 객체의 행위를 호출하는걸까? 만약, 호출 객체까지 도달하기 전 유효성을 검증해야하는 코드가 있다거나 그 외에 전처리 과정이 필요한 행위가 있다고 가정하면 프록시를 두어 전처리를 수행하고 행위를 호출할 수 있다.

호출 객체가 그 모든 행위를 감당하게 되면 SRP 를 위반하여 추후에 요구사항을 반영할 때 호출 객체가 너무 많은 행위를 함으로써 변경이 까다롭게 되는 단점을 보완하기 위해서 프록시를 둔다.

예시 우린 대학생이고, 이번에 자바 과목을 수강중이라고 가정해보자. 나는 오늘 늦게 일어났고, 내 친구에게 대신 출석해달라고 부탁한다.

과목을 듣기 위해 본인이 직접 출석해야 하지만 불가피한 상황으로 인해 친구한테 부탁했다. 그리고 과목을 듣는 것 처럼, 내가 해야 할 행위에 앞서 흐름을 제어하고 싶다면 프록시 패턴을 둘 수 있다.

데코레이터 패턴

데코레이터 패턴은 프록시 패턴과 동작 방식이 거의 유사하며, 단 하나만의 차이가 존재한다.

프록시 패턴은 특정 행위를 호출하기 전 흐름을 제어 한 후 그대로 행위에 대한 결과 값을 반환 했다면, 데코레이터 패턴은 행위에 대한 반환 값에 후처리 가공 과정을 거쳐 보정된 값을 반환한다.

프록시 패턴에서 출석 후 수강에 대한 내용이었다면 데코레이터는 수강한 과목을 요약해서 정리한 결과를 반환한다.

그럼 추후에 자바라는 과목을 공부할 때 수강했을 때 기록해둔 모든 내용을 읽지 않고, 요약된 내용만 읽어도 되기 때문이다.

싱글턴 패턴

싱글턴 패턴은 굉장히 익숙하게 많이 듣는 디자인 패턴 중 하나이고, 스프링이 빈 컨테이너를 관리할 때 @Configuration 어노테이션을 사용한 빈은 싱글턴으로 구성한다.

만약, 사용자가 자신의 프로필 정보를 불러오기 위해 요청을 보냈다면 우리는 한 개의 스레드에서 객체를 생성하고, 메서드를 실행 시킨다.

만약 여러 사용자가 동시에 프로필 정보를 불러오는 요청을 보낸다면 객체를 무수히 많이 생성하면 온전하게 힙 공간을 관리할 수 없게 된다.

이런 것을 방지하기 위해 우리는 싱글턴 패턴을 적용하여, 객체를 초기에 1번만 생성하고 그 이후엔 생성된 객체를 그대로 사용하는 방식을 활용하여 보다 여유로운 힙 영역을 관리할 수 있다.

템플릿 매서드 패턴

메서드의 흐름에서 특정 구간만 변하고, 그 외는 동일한 행동을 할 때 사용할 수 있는 디자인 패턴이다.

만약, 엑셀을 가공해서 업로드하는데 개발자 개인 PC 에서는 로컬 디스크에 저장하고 스테이징 환경에서는 S3 를 쓴다고 가정해보자.

이런식으로 정해둔 템플릿에 추상메서드를 추가하여, 특정 행위는 요구사항대로 구현하고 그 외는 동일한 행위를 제공하는 디자인 패턴이다.

특정 부분만 변경되고 나머지 행위는 계속 반복된다면 이런 디자인 패턴을 활용해서 중복 코드를 최소화 하고 확장 가능하도록 구성할 수 있다.

팩터리 메서드 패턴

팩터리 메서드 패턴은 인터페이스로 제공하는 공통 메서드를 통해 다른 객체를 반환 하도록 유도하는 디자인 패턴이다.

위 엑셀 업로드 예시를 다시 팩토리 메서드 패턴으로 살펴보자.

위 코드 처럼 오버라이드된 메서드를 통해 다른 객체를 반환하는 것을 팩터리 메서드라고 하며, DIP 원칙을 활용한 디자인 패턴이다.

전략 패턴

전략 패턴은 인터페이스로 제공하는 공통된 메서드 하나를 객체의 역할 마다 구현하여 다형성을 이용해 메서드를 호출하는 방식이다.

예를 들어, 신용 카드 결제, 현금 결제, 포인트 결제 마다 각 정책이 있다고 가정해보자.

  • 신용 카드 결제 시 수수료를 계산 해야한다.

  • 현금 결제 시 수수료를 받지 않기 때문에 모두 0원으로 두어야한다.

  • 포인트 결제는 사용 금액을 수수료 정보로 치환하고, 판매 금액을 0원으로 두어야한다.

위와 같은 정책이 있다고 했을 때 해결하기 쉬운 디자인 패턴으로는 전략 패턴이 존재하고, 사실 이 전략 패턴은 디자인 패턴의 꽃이라고 부를 수 있을 정도로 굉장히 자주 접할 수 있다.

위 처럼 SRP 를 지켜 코드를 구성하면, 각 결제 유형별로 요구사항에 맞게 구현 내용을 작성할 수 있다. 추가로, 인터페이스를 구현하고 있기 때문에 실제 비즈니스 로직에서 FeeStrategy 타입으로 의존 관계를 주입 받아 calculateFee 메서드를 실행할 수 있다.

  • 이는 다형성을 활용한 방식으로 OCP, DIP 원칙을 같이 사용하게된다.

템플릿 콜백 패턴

전략 패턴의 변형 버전으로, 스프링의 3대 프로그래밍 모델 중 하나인 DI 에서 사용하는 특별한 형태의 전략 패턴이다.

템플릿 콜백 패턴은 전략 패턴과 모든 것이 동일한데, 해당 "전략"을 자바 파일로 관리하지 않고 익명 클래스로 정의해서 사용하는 것이 특징이다.

위에 간편 결제 예시를 통해 다시한 번 살펴보면 아래와 같이 구성할 수 있다.

스프링은 이런 형식으로 템플릿 콜백 패턴을 DI 에 적극 활용하고 있다. "전략을 익명 내부 클래스로 구현한 전략 패턴" 이라는 것을 기억하자.

당연히 전략 패턴의 변형이기 때문에 OCP 와 DIP 가 적용된 설계 패턴이다.

Last updated