출처 - https://github.com/jmxx219/CS-Study (opens in a new tab)
스프링 프레임워크에서 사용되는 디자인 패턴
디자인 패턴(Design Pattern)
- 소프트웨어 디자인 과정에서 자주 발생하는 문제들에 대한 일반적인 해결책
- 객체 지향 4대 원칙(캡슐화, 상속, 추상화, 다형성) (opens in a new tab)과 설계 원칙(SOLID) (opens in a new tab)를 기반으로 구현되어 있음
- 4대 원칙 ➜ 요리 도구
- 설계 원칙 ➜ 도구의 사용법
- 디자인 패턴 ➜ 레시피
- 디자인 패턴 분류
- GoF(Gang of Four)의 디자인 패턴은 크게
생성 패턴
,구조 패턴
,행위 패턴
으로 분류됨
- 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)
: 컬렉션 내의 요소들에 접근하는 방법을 표준화하여 컬렉션 내부 구조에 독립적으로 접근할 수 있는 패턴
암기요령
- 구조패턴 : A, B, D, 2F, P
- 행위패턴 : CIMOS TV
스프링 프레임워크에서 사용되는 디자인 패턴
- 싱글톤 패턴 ➜ 스프링 컨테이너(스프링 빈)
- 팩토리 메서드 패턴 ➜ 스프링의 DI(BeanFactory, Application Context)
- 프록시 패턴 ➜ 스프링의 AOP(@Transactional)
- 템플릿 메서드 패턴 ➜ JDBCTemplate, DispatcherServlet
- 어댑터 패턴 ➜ HandlerAdapter 등등
◽ 싱글턴 패턴 (Singleton Pattern)
- 어플리케이션에서 객체를 딱 하나만 만들어 사용하기 위한 패턴
- 인스턴스에서 static을 통해 최초 1번만 메모리를 할당시켜서 클래스의 인스턴스를 딱 1개만 생성하고, 어디서든지 그 인스턴스에 접근할 수 있도록 함
- 싱글턴 패턴의 구현 방법
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 생성자로 자식 클래스를 생성하기 어려움
싱글톤 컨테이너(스프링 컨테이너)
스프링에서는 사용자가 싱글톤 패턴을 구현하지 않고, 싱글톤 패턴의 문제점을 해결하면서 객체의 인스턴스를 싱글톤으로 관리함
- 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리함
- 스프링 빈(Bean)은 싱글톤 패턴으로 관리되는 빈으로 하나의 빈 인스턴스가 컨테이너 내에서 공유되고, 여러 요청에서 동일한 빈 인스턴스를 사용함
- 컨테이너 당 하나의 싱글톤 인스턴스를 가지는 구조로, 한 애플리케이션 내부에 여러 개의 컨테이너를 가지는 구조라면 전역적 싱글톤이 아닌 컨테이너에서의 싱글톤 인스턴스 개념을 가짐
@Autowired
싱글톤- 스프링에서
@Autowired
어노테이션을 통해 컨테이너 내 Bean을 주입받아 사용할 수 있음- 최초 애플리케이션 구동 시, Bean을 컨테이너에 등록하여
@Autowired
구문을 찾아 해당 변수에 등록한 Bean을 주입하여 사용함
- 최초 애플리케이션 구동 시, Bean을 컨테이너에 등록하여
- 스프링에서
- 빈 스코프를 프로토타입으로 설정하면, 싱글톤이 아닌 매번 새로운 인스턴스를 생성할 수 있음 (디폴트는 싱글톤)
안티패턴
- 전역 상태 공유
- 싱글턴은 전역 상태를 공유하므로, 다른 객체들 사이에 강한 결합을 만들고, 테스트하기 어렵게 만듦
- 개발 확장 어려움
- 만약 다수의 싱글톤이 서로 강하게 결합되어 있다면, 코드의 확장이 어려워질 수 있음
- OCP(Open-Closed Principle)를 위반하게 됨
- 스레드 세이프 문제
- 멀티스레드 환경에서 동시에
getInstance()
메서드를 호출하면, 여러 인스턴스가 생성될 수 있음 - 이를 해결하기 위해
getInstance()
메서드에synchronized
키워드를 사용할 수 있지만, 성능이 저하됨
- 멀티스레드 환경에서 동시에
싱글턴 패턴을 안티패턴으로 간주하여 자바 코드에서 사용이 감소하고 있음
◽ 팩토리 메서드 패턴 (Factory Method Pattern)
-
객체의 인터페이스는 미리 정의하되, 객체 생성은 서브 클래스(팩토리)로 위임하는 패턴
- 객체를 만들어주는 클래스를 별도로 만들어서 객체를 생성하는 방식
-
생성된 객체를 반환하는 메서드를 추상화하여 인터페이스를 통한 다형성을 제공함
-
팩토리 메서드 패턴 구현 방법
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)는 팩토리 메서드 패턴을 따름
스프링에서는 빈을 생성하는 빈 컨테이너가 팩토리 패턴을 사용하여 빈을 관리함
- Spring의
BeanFactory
- 스프링의
BeanFactory
인터페이스는 객체(빈) 생성과 관리의 복잡성을 추상화하는 팩토리 메서드 패턴의 일종으로, 이를 통해 스프링 컨테이너는 빈의 생명주기를 관리함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를 사용함
- 스프링은 BeanFactory를 상속하여 추가적인 Application을 다루는
◽ 프록시 패턴 (Proxy Pattern)
-
개념
- 한 객체(proxy)가 다른 객체(subject or service)에 대한 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴
- 프록시 객체는 실제 객체(대상 원본 객체)에 대한 참조를 가지고 있어서 클라이언트의 요청을 처리하고, 실제 객체로 작업을 위임함
- 대상 객체(Subject)의 메소드를 직접 실행하는 것이 아닌, 대상 객체에 접근하기 전에 프록시(Proxy) 객체의 메서드를 접근한 후 추가적인 로직을 처리한 뒤에 대상 객체에 접근함
- 프록시와 대상은 동일한 인터페이를 가지고 있으며 이를 통해 다른 인터페이스와 완전히 호환되도록 바꿀 수 있음
- 한 객체(proxy)가 다른 객체(subject or service)에 대한 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴
-
사용 시기
- 접근을 제어하거나 기능을 추가하고 싶은데 기존 객체를 수정할 수 없는 상황인 경우
- 초기화 지연, 접근 제어, 로깅, 캐싱 등 기존 객체 동작에 수정 없이 가미하고 싶은 경우
-
Spring에서는 두 가지 타입의 Proxy를 사용함
- 빈으로 등록하려느 객체가 인터페이스를 하나라도 구현하고 있으면 JDK를 이용하고, 인터페이스를 구현하고 있지 않으면 내장된 CGLib 라이브러리를 사용함
CGLib Proxy
: 클래스의 바이트 코드를 조작하여 프록시 객체를 생성해주는 라이브러리JDK Dynamic Proxy
: Interface들을 프록시할 때 사용
Spring의 AOP
스프링에서는 프록시 패턴을 사용하여 AOP (opens in a new tab)를 구현함
-
스프링에서는 AOP에 기반해 수 많은 로킹 처리, 트랜잭션 처리를 공통 모듈화시켜서 따로 작성함
- Spring의 AOP는 CGLib라는 런타임 프록시 생성 라이브러리를 사용해서 AOP를 위한 프록시 객체를 생성하여 사용함
-
스프링에서는 어노테이션으로 서비스가 추상회되어 많이 사용하는데, 그 중에서
@Transactional
은 DB 트랜잭션 처리를 간단하게 만들어주고 비즈니스에 집중하게 만들어줌@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 객체에 대한 접근을 제어할 수 있게 함
- 스프링은 BookRepository 빈 객체를
◽ 템플릿 메서드 패턴 (Template Method Pattern)
- 어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화하여 전체 일을 수행하는 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내용을 바꾸는 패턴
- 변하지 않는 기능(템플릿)은 상위 클래스에 만들어두고 자주 변경되며 확장할 기능은 하위 클래스에서 만들도록 함
- 상위의 메서드 실행 동작 순서는 고정하면서 세부 실행 내용은 다양화될 수 있는 경우에 사용
JdbcTemplate
- JdbcTemplate은 커넥션 정리와 결과 반복, 예외 처리, 트랜잭션 처리 등과 같은 복잡하고 세부적인 사항들을 스프링 내부에서 알아서 처리하게 해주는 코드가 들어가 있음
- 따라서 반복되는 DB 연동 로직은 JdbcTemplate 클래스의 템플릿 메서드가 제공하고, 개발자는 달라지는 SQL 구문과 설정값만 수정하면 됨
- DB에 질의할 때 4가지 단계를 거치게 되는데 JdbcTemplate의
query()
가 템플릿 메서드 패턴으로 동작함public class JdbcTemplate { public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException { // Execute query... } }
DispatcherServlet (opens in a new tab)
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)
- 서로 일치하지 않는 인터페이스를 갖는 클래스들을 함께 동작시키기 위해 사용하는 패턴
- 스프링에서는 어댑터 패턴 (opens in a new tab)을 사용하여 다양한 유형의 컨트롤러 및 메시지 변환 작업을 수행함
Ref
- guru (opens in a new tab)
- Spring Interview Questions and Answers (opens in a new tab)
- [Design Pattern] 디자인 패턴 종류 (opens in a new tab)
- Design Patterns in the Spring Framework (opens in a new tab)
- 디자인 패턴 (opens in a new tab)
- factory method pattern vs abstract factory pattern 차이 알아보기 (opens in a new tab)
- [Spring & Design Pattern] Spring에서 발견한 디자인 패턴_template method pattern (opens in a new tab)