# 스프링 프레임워크에서 사용되는 디자인 패턴

\


## 디자인 패턴(Design Pattern)

* 소프트웨어 디자인 과정에서 자주 발생하는 문제들에 대한 일반적인 해결책
* [객체 지향 4대 원칙(캡슐화, 상속, 추상화, 다형성)](oop)과 [설계 원칙(SOLID)](../etc/solid)를 기반으로 구현되어 있음
  * 4대 원칙 ➜ 요리 도구
  * 설계 원칙 ➜ 도구의 사용법
  * 디자인 패턴 ➜ 레시피
* 디자인 패턴 분류
  * GoF(Gang of Four)의 디자인 패턴은 크게 `생성 패턴`, `구조 패턴`, `행위 패턴`으로 분류됨

\


### 생성 패턴 (Creational Pattern)

* **생성 패턴의 목적**
  * 객체 인스턴스를 생성할 때와 관련된 문제에 대한 해결책을 다룸
    * 객체 생성 과정을 보다 유연하게 만들어주고, 코드의 타이트한 결합을 방지함
  * 클래스의 캡슐화를 통해 코드의 유연성과 재사용 가능성을 향상시키는 패턴
* **생성 패턴의 예**
  * `싱글턴 (Singleton)`: 하나의 클래스 인스턴스를 전역에서 접근 가능하게 하면서 해당 인스턴스가 한 번만 생성도록 보장하는 패턴
  * `팩토리 메서드 (Factory Method)`: 객체를 생성하기 위한 인터페이스를 정의하고, 서브 클래스에서 어떤 클래스의 인스턴스를 생성할지 결정(서브 클래스에게 객체 생성을 위임)하는 패턴
  * `추상 팩토리 (Abstract Factory)`: 관련된 객체의 패밀리를 생성하기 위한 인터페이스 제공하며, 구체적인 팩토리 클래스를 통해 객체 생성을 추상화하는 패턴
  * `빌더 (Builder)`: 복잡한 객체의 생성 과정을 단순화하고, 객체를 단계적으로 생성하여 구성하는 패턴
  * `프로토타입 (Prototype)`: 객체를 복제하여 새로운 객체를 생성하는 패턴으로, 기존 객체를 템플릿으로 사용하는 패턴

\


### 구조 패턴 (Structural Pattern)

* **구조 패턴의 목적**
  * 클래스와 객체를 조합하여 더 큰 구조를 만드는 패턴
  * 서로 다른 인터페이스를 가진 객체들을 조합하여 단일 인터페이스르 제공하거나, 서로 다른 객체들을 묶어 새로운 기능을 제공하는 패턴
* **구조 패턴의 예**
  * `어댑터 (Adapter)`: 인터페이스 호환성을 제공하지 않는 클래스를 사용하기 위해 래퍼(Wrapper)를 제공하는 패턴
  * `브리지 (Bridge)`: 추상화와 구현을 분리하여 두 가지를 독립적으로 확장할 수 있는 패턴
  * `컴포지트 (Composite)`: 개별 객체와 복합 객체를 동일하게 다루어 트리 구조의 객체를 구성하는 패턴
  * `데코레이터 (Decorator)`: 객체에 동적으로 새로운 기능을 추가하여 객체를 확장할 수 있는 패턴
  * `퍼사드 (Facade)`: 서브 시스템을 더 쉽게 사용할 수 있도록 단순한 인터페이스를 제공하는 패턴
  * `플라이웨이트 (Flyweight)`: 공유 가능한 객체를 통해 메모리 사용을 최적화하는 패턴
  * `프록시 (Proxy)`: 다른 객체에 대한 대리자(Proxy)를 제공하여 접근 제어, 지연 로딩 등을 구현하는 패턴

\


### 행위 패턴 (Behavioral Pattern)

* **행위 패턴의 목적**
  * 클래스나 객체 사이의 알고리즘이나 책임 분배에 관련된 패턴
    * 객체들의 알고리즘을 캡슐화하여 객체의 행위를 변경하고 싶은 경우
    * 객체들의 행위를 객체 안으로 캡슐화하여 재사용성을 높이고 싶은 경우
* **행위 패턴의 예**
  * `옵저버 (Observer)`: 객체 간의 일대다 종속 관계를 정의하여 한 객체의 상태 변경이 일어나면 다른 객체들에게 알리는 패턴
  * `전략 (Strategy)`: 알고리즘을 객체의 행동으로 캡슐화하여, 동일한 행동을 다른 알고리즘으로 교체할 수 있게하는 패턴
  * `커맨드 (Command)`: 요청을 객체로 캡슐화하여 요청을 매개변수화 하고, 요청을 큐에 저장하거나 로깅하고 실행을 지연시키는 패턴
  * `상태 (State)`: 객체의 상태를 캡슐화하고, 상태 전환을 관리하는 패턴
  * `책임 연쇄 (Chain of Responsibility)`: 요청을 보내는 객체와 이를 처리하는 객체를 분리하여, 다양한 처리자 중 하나가 요청을 처리하는 패턴
  * `방문자 (Visitor)`: 객체 구조의 요소에 대한 연산을 정의하여, 구조의 요소가 연산을 수행하도록 하는 패턴
  * `인터프리터 (Interpreter)`: 언어나 문법에 대한 해석기를 제공하여, 주어진 언어로 표현된 문제를 해결하는 패천
  * `메멘토 (Memento)`: 객체의 내부 상태를 저장하고 복원할 수 있는 기능을 제공하는 패턴
  * `중재자 (Mediator)`: 객체 간의 상호 작용을 캡슐화하여, 객체 간의 직접적인 통신을 방지하는 패턴
  * `템플릿 메서드 (Template Method)`: 알고리즘 구조를 정의하면서 하위 클래스(서브 클래스)에서 각 단계의 구현을 제공하는 패턴
  * `반복자 (Iterator)`: 컬렉션 내의 요소들에 접근하는 방법을 표준화하여 컬렉션 내부 구조에 독립적으로 접근할 수 있는 패턴

\
\


> **스프링 프레임워크에서 사용되는 디자인 패턴**
>
> 싱글톤 패턴 ➜ 스프링 컨테이너(스프링 빈)\
> 팩토리 메서드 패턴 ➜ 스프링의 DI(BeanFactory, Application Context)\
> 프록시 패턴 ➜ 스프링의 AOP(@Transactional)\
> 템플릿 메서드 패턴 ➜ JDBCTemplate, DispatcherServlet\
> 어댑터 패턴 ➜ HandlerAdapter 등등

\


## ◽ 싱글턴 패턴 (Singleton Pattern)

* 어플리케이션에서 객체를 딱 하나만 만들어 사용하기 위한 패턴
  * 인스턴스에서 static을 통해 최초 1번만 메모리를 할당시켜서 클래스의 인스턴스를 딱 1개만 생성하고, 어디서든지 그 인스턴스에 접근할 수 있도록 함
*   싱글턴 패턴의 구현 방법

    ```java
    public class Singleton {
        private static Singleton instance;
        
        private Singleton() {}
        
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    ```

    * 생성자를 `private`으로 선언하여 외부에서 인스턴스를 생성하지 못하도록 하고,`getInstance()` 메서드를 통해 싱글턴 인스턴스를 얻을 수 있도록 함
* 순수 Java로 구현한 싱글톤 패턴의 문제점
  * 싱글톤 패턴을 구현하기 위한 코드가 더 늘어남
  * 인스턴스를 반환해주는 구현 클래스를 직접 참조해야하기 때문에 DIP를 위반함
  * 내부 속성을 변경 및 초기화하기 어렵고, private 생성자로 자식 클래스를 생성하기 어려움

\


### 싱글톤 컨테이너(스프링 컨테이너)

![](https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2\&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZzBq1%2FbtrXoDm8BrT%2Ft8DxbZwlW79VxL2kxU1h9K%2Fimg.png)

> 스프링에서는 사용자가 싱글톤 패턴을 구현하지 않고, 싱글톤 패턴의 문제점을 해결하면서 객체의 인스턴스를 싱글톤으로 관리함

* 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리함
  * 스프링 빈(Bean)은 싱글톤 패턴으로 관리되는 빈으로 하나의 빈 인스턴스가 컨테이너 내에서 공유되고, 여러 요청에서 동일한 빈 인스턴스를 사용함
  * 컨테이너 당 하나의 싱글톤 인스턴스를 가지는 구조로, 한 애플리케이션 내부에 여러 개의 컨테이너를 가지는 구조라면 전역적 싱글톤이 아닌 컨테이너에서의 싱글톤 인스턴스 개념을 가짐
* `@Autowired` 싱글톤
  * 스프링에서 `@Autowired` 어노테이션을 통해 컨테이너 내 Bean을 주입받아 사용할 수 있음
    * 최초 애플리케이션 구동 시, Bean을 컨테이너에 등록하여 `@Autowired` 구문을 찾아 해당 변수에 등록한 Bean을 주입하여 사용함
* 빈 스코프를 프로토타입으로 설정하면, 싱글톤이 아닌 매번 새로운 인스턴스를 생성할 수 있음 (디폴트는 싱글톤)

\


#### 안티패턴

* **전역 상태 공유**
  * 싱글턴은 전역 상태를 공유하므로, 다른 객체들 사이에 강한 결합을 만들고, 테스트하기 어렵게 만듦
* **개발 확장 어려움**
  * 만약 다수의 싱글톤이 서로 강하게 결합되어 있다면, 코드의 확장이 어려워질 수 있음
  * OCP(Open-Closed Principle)를 위반하게 됨
* **스레드 세이프 문제**
  * 멀티스레드 환경에서 동시에 `getInstance()` 메서드를 호출하면, 여러 인스턴스가 생성될 수 있음
  * 이를 해결하기 위해 `getInstance()` 메서드에 `synchronized` 키워드를 사용할 수 있지만, 성능이 저하됨

> 싱글턴 패턴을 안티패턴으로 간주하여 자바 코드에서 사용이 감소하고 있음

\
\


## ◽ 팩토리 메서드 패턴 (Factory Method Pattern)

![](https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2\&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGSapI%2FbtrXrUuRTbI%2FGE3mdXKb38rJFkJMqHIKm0%2Fimg.png)

* 객체를 생성하는 인터페이스는 미리 정의하되, 객체 생성은 서브 클래스(팩토리)로 위임하는 패턴
  * 객체를 만들어주는 클래스를 별도로 만들어서 객체를 생성하는 방식
* 생성된 객체를 반환하는 메서드를 추상화하여 인터페이스를 통한 다형성을 제공함
*   팩토리 메서드 패턴 구현 방법

    ```java
    public interface Vehicle {
        void drive();
    }

    public class Airplane implements Vehicle {
        @Override
        public void drive() {
            System.out.println("drive Airplane");
        }
    }

    public class Boat implements Vehicle {
        @Override
        public void drive() {
            System.out.println("drive Boat");
        }
    }

    public interface VehicleFactory {
        Vehicle createVehicle();
    }

    public class AirplaneVehicleFactory implements VehicleFactory {
        @Override
        public Vehicle createVehicle() {
            return new Airplane();
        }
    }

    public class BoatVehicleFactory implements VehicleFactory {
        @Override
        public Vehicle createVehicle() {
            return new Boat();
        }
    }
    ```

    * 공통 인터페이스를 정의하고, 인터페이스를 상속한 클래스를 구현함
    * 구현한 클래스의 객체 반환용 팩토리 클래스를 정의
    * 팩토리 클래스를 통해 객체를 받아서 사용함

\


### BeanFactory, Application Context

> Spring의 DI(Dependency Injection)는 팩토리 메서드 패턴을 따름\
> 스프링에서는 빈을 생성하는 빈 컨테이너가 팩토리 패턴을 사용하여 빈을 관리함

![](https://velog.velcdn.com/images%2Fweekbelt%2Fpost%2Fc9cfe500-9285-439c-a617-dd37af10befc%2Ffactorymethod01.png)

* Spring의 `BeanFactory`
  *   스프링의 `BeanFactory` 인터페이스는 객체(빈) 생성과 관리의 복잡성을 추상화하는 팩토리 메서드 패턴의 일종으로, 이를 통해 스프링 컨테이너는 빈의 생명주기를 관리함

      ```Java
      public interface BeanFactory {
          getBean(Class<T> requiredType);
          getBean(Class<T> requiredType, Object... args);
          getBean(String name);
          // ...
      }
      ```

      * `Bean Factory`: Bean 정의를 포함하고 Bean을 생성하는 Spring 컨테이너
      * `getBean()` 메서드는 팩토리 메서드로 간주됨
  * 기본적으로 스프링은 Bean Container를 Bean을 생성하는 Factory로 취급함
* BeanFactory의 확장 인터페이스인 `Application Context`
  * 스프링은 BeanFactory를 상속하여 추가적인 Application을 다루는 `Application Context` 인터페이스를 구현함
  * XML 파일이나 자바 어노테이션과 같은 외부 설정을 기반으로 Bean Container를 시작하기 위해 Application Context를 사용함

\
\


## ◽ 프록시 패턴 (Proxy Pattern)

![](https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2\&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3pVn9%2FbtrXv4wX7hZ%2FdKNOALhGqYqpthIcdVRnF0%2Fimg.png)

* 개념
  * 한 객체(proxy)가 다른 객체(subject or service)에 대한 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴
    * 프록시 객체는 실제 객체(대상 원본 객체)에 대한 참조를 가지고 있어서 클라이언트의 요청을 처리하고, 실제 객체로 작업을 위임함
  * 대상 객체(Subject)의 메소드를 직접 실행하는 것이 아닌, 대상 객체에 접근하기 전에 프록시(Proxy) 객체의 메서드를 접근한 후 추가적인 로직을 처리한 뒤에 대상 객체에 접근함
    * 프록시와 대상은 동일한 인터페이를 가지고 있으며 이를 통해 다른 인터페이스와 완전히 호환되도록 바꿀 수 있음
* 사용 시기
  * 접근을 제어하거나 기능을 추가하고 싶은데 기존 객체를 수정할 수 없는 상황인 경우
  * 초기화 지연, 접근 제어, 로깅, 캐싱 등 기존 객체 동작에 수정 없이 가미하고 싶은 경우
* Spring에서는 두 가지 타입의 Proxy를 사용함
  * 빈으로 등록하려느 객체가 인터페이스를 하나라도 구현하고 있으면 JDK를 이용하고, 인터페이스를 구현하고 있지 않으면 내장된 CGLib 라이브러리를 사용함
  * `CGLib Proxy`: 클래스의 바이트 코드를 조작하여 프록시 객체를 생성해주는 라이브러리
  * `JDK Dynamic Proxy`: Interface들을 프록시할 때 사용

\


### Spring의 AOP

> 스프링에서는 프록시 패턴을 사용하여 [AOP](../spring/aop)를 구현함

![](https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2\&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblDjJ4%2FbtrXn81XfgP%2FqHoZiKkp0qJ4xzedlLYcA0%2Fimg.png)

* 스프링에서는 AOP에 기반해 수 많은 로킹 처리, 트랜잭션 처리를 공통 모듈화시켜서 따로 작성함
  * Spring의 AOP는 CGLib라는 런타임 프록시 생성 라이브러리를 사용해서 AOP를 위한 프록시 객체를 생성하여 사용함
*   스프링에서는 어노테이션으로 서비스가 추상회되어 많이 사용하는데, 그 중에서 `@Transactional`은 DB 트랜잭션 처리를 간단하게 만들어주고 비즈니스에 집중하게 만들어줌

    * [@Transactional 참고 - AOP를 이용한 트랜잭션 분리](../spring/transactional)

    ```Java
    @Service
    public class BookManager {
        
        @Autowired
        private BookRepository repository;

        @Transactional
        public Book create(String author) {
            System.out.println(repository.getClass().getName());
            return repository.create(author);
        }
    }
    ```

    * 스프링은 BookRepository 빈 객체를 `EnhancerBySpringCGLIB` 객체로 wrapping하고, BookRepository 객체에 대한 접근을 제어할 수 있게 함

\
\


## ◽ 템플릿 메서드 패턴 (Template Method Pattern)

* 어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화하여 전체 일을 수행하는 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내용을 바꾸는 패턴
  * 변하지 않는 기능(템플릿)은 상위 클래스에 만들어두고 자주 변경되며 확장할 기능은 하위 클래스에서 만들도록 함
  * 상위의 메서드 실행 동작 순서는 고정하면서 세부 실행 내용은 다양화될 수 있는 경우에 사용

\


### JdbcTemplate

* JdbcTemplate은 커넥션 정리와 결과 반복, 예외 처리, 트랜잭션 처리 등과 같은 복잡하고 세부적인 사항들을 스프링 내부에서 알아서 처리하게 해주는 코드가 들어가 있음
  * 따라서 반복되는 DB 연동 로직은 JdbcTemplate 클래스의 템플릿 메서드가 제공하고, 개발자는 달라지는 SQL 구문과 설정값만 수정하면 됨
*   DB에 질의할 때 4가지 단계를 거치게 되는데 JdbcTemplate의 `query()`가 템플릿 메서드 패턴으로 동작함

    ```Java
    public class JdbcTemplate {
        public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
            // Execute query...
        }
    }
    ```

\


### [DispatcherServlet](../spring/dispatcherservlet)

```Jav

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ...
        initContextHolders(request, localeContext, requestAttributes);
        ...
        try {
            doService(request, response); // template method pattern 이용
        }
        ...
    }
    protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws Exception; // subClass에게 위임
}

public class DispatcherServlet extends FrameworkServlet {
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        logRequest(request);
        ...
    }
}
```

* `DispatcherServlet ➜ FrameworkServlet ➜ HttpServletBean ➜ HttpServlet` 상속 구조를 가지고 있는데, `DispatcherServlet`의 `doService()` 메서드가 템플릿 메서드 패턴으로 구현됨
* `FrameworkServlet`에서 `doService()`에 대한 구현은 하위 클래스에게 위임하고 있고, `DispatcherServlet`은 `doService()`를 구체화하고 있음
  * `FrameworkServlet`의 `processRequest()` 로직을 수행하다가 `doService()`를 만나면 `DispatcherServlet`의 `doService()`로직을 수행함

\
\


## ◽ 어댑터 패턴 (Adapter Pattern)

* 서로 일치하지 않는 인터페이스를 갖는 클래스들을 함께 동작시키기 위해 사용하는 패턴
* 스프링에서는 [어댑터 패턴](../../spring/dispatcherservlet#어댑터-패턴--adapter-pattern)을 사용하여 다양한 유형의 컨트롤러 및 메시지 변환 작업을 수행함

\
\


***

### Ref

* [guru](https://refactoring.guru/design-patterns)
* [Spring Interview Questions and Answers](https://www.digitalocean.com/community/tutorials/spring-interview-questions-and-answers#48-name-some-of-the-design-patterns-used-in-spring-framework)
* [\[Design Pattern\] 디자인 패턴 종류](https://gmlwjd9405.github.io/2018/07/06/design-pattern.html)
* [Design Patterns in the Spring Framework](https://www.baeldung.com/spring-framework-design-patterns)
* [디자인 패턴](https://github.com/kamranahmedse/design-patterns-for-humans)
* [factory method pattern vs abstract factory pattern 차이 알아보기](https://beomseok95.tistory.com/246)
* [\[Spring & Design Pattern\] Spring에서 발견한 디자인 패턴\_template method pattern](https://sabarada.tistory.com/19)
