공부방

스프링과 디자인 패턴

devchive 2023. 8. 27. 20:26

디자인 패턴

소프트웨어 디자인 패턴(software design pattern)은 소프트웨어 공학의 소프트웨어 디자인에서 특정 문맥에서 공통적으로 발생하는 문제에 대해 재사용 가능한 해결책이다.

 

디자인 패턴은 시스템을 디자인 할 때 공통된 문제들을 해결하는데에 쓰이는 형식화 된 관행이라고 볼 수 있다. 

'스프링 입문을 위한 자바 객체 지향의 원리와 이해' 라는 책에서 소개된 디자인 패턴중 일부를 살펴 보고 이 패턴들이 스프링에서 어떤식으로 사용 되는지 살펴보자

 

어댑터 패턴

먼저 어댑터 패턴이 적용되지 않은 두 클래스를 보자

public class ServiceA{
	void runServiceA(){
    	System.out.println("ServiceA");
    }
}

public class ServiceB{
	void runServiceB(){
    	System.out.println("ServiceB");
    }
}

이 두 클래스를 사용하는 곳에서는 runServiceA 와 runServiceB라는 서로 다른 함수를 호출 해야 한다. 위 코드를 어댑터 패턴을 적용해 개선을 해보면 

public class AdapterServiceA{
	ServiceA sa = new ServiceA();
    
    void runService(){
    	sa.runServiceA();
    }
}

public class AdapterServiceB{
	ServiceB sb = new ServiceB();
    
    void runService(){
    	sb.runServiceB();
    }
}

이런식으로 어댑터 클래스를 새로 두어 실제 사용할때는 같은 이름의 함수를 호출하면 된다. 이처럼, 어댑터 패턴은

"호출당하는 쪽의 메서드를 호출하는 쪽의 코드에 대응하도록 중간에 변환기를 통해 호출하는 패턴" 이라 할 수 있다. 

 

스프링에서 어댑터 패턴이 사용되는곳은 대표적으로 HandlerAdapter 가 있다. 

HanderAdapter 중 하나인 SimpleControllerHandlerAdapter를 예시로 보자. handle 이라는 method가 있는데 DispatcerServelet에서는 이 method를 호출하여 handler를 작동시킨다. 이 HandlerAdapter를 구현한 다른 클래스들도 각자에 맞게 handle 함수를 구현하고 호출하는곳에서는 동일하게 handle 을 호출하는 형식이다. 

 

프록시 패턴

먼저 프록시 패턴을 적용하지 않은 코드를 보자

public class Service {
	public String runSomething(){
   		return "Hello";
    }
}

위 클래스를 사용하는곳에서는 Service의 인스턴스를 만들고 runSomething 메소드를 호출할 것이다. 이를 프록시 패턴을 적용하여 수정해 보자.

public interface IService{
	String runSomething();
}

public class Service implements IService{
	public STring runSomething(){
    	return "Hello";
    }
}

public class Proxy implements IService{
	IService service;
    
    public String runSomething() {
    	serive = new Service();
        return service.runSomething();
    }
}

위처럼 수정된 코드에서는 Service 의 인스턴스를 생성하는것이 아닌 Proxy의 인스턴스를 생성하고 runSomething 메소드를 호출하면 Service의 runSomething 결과를 마찬가지로 받을 수 있다.

이게 무슨 차이가 있나 싶겠지만, 이런식으로 Proxy를 통해 대신 호출하게 되면 Proxy의 runSomething에 별도의 제어 흐름을 추가할 수 있다. Service 의 runSomething 전후에 자신이 원하는 로직을 넣을 수 있게되었다. 

프록시 패턴을 정리하면 "제어 흐름을 조정하기 위한 목적으로 중간에 대리자를 두는 패턴" 이라 할 수 있다. 

 

스프링에서 프록시 패턴이 적용된 대표적인 부분은 스프링의 AOP 기능이다. 

스프링 AOP란, Aspect Oriented Programming의 약자이다. 위 그림처럼 여러 클래스에 흩어져있는 관점들을 하나로 모아 처리하는 것이다. 

대표적인 예로, 스프링에서 @Transactional 는 AOP 방식으로 처리된다. 실제 비즈니스 로직 외에 트랜잭션 시작 등의 전처리와 커밋, 롤백등의 후처리등을 프록시 패턴으로 처리한다. 

 

싱글톤 패턴

싱글톤 패턴은 클래스의 인스턴스를 하나만 만들어 사용하는 패턴이다. 

 

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

위는 싱글톤 패턴의 예시이다. 물론 아주 좋은 싱글톤 생성 방식은 아니다. 자세한 싱글톤에 대한 설명은 다음을 참조한다.

2023.08.27 - [JAVA] - Singleton 구현 기법

 

Singleton 구현 기법

 

devchive.tistory.com

이처럼, 싱글톤 패턴을 사용하면 항상 하나의 인스턴스만 생성하여 사용된다. 

 

스프링에서의 싱글톤 패턴으로는 다음과 같은 부분을 볼 수 있다. 

DefaultBootstrapContext의 getInstance 함수이다. 현재 Scope 이 싱글톤이면 this.instances에 인스턴스를 하나 할당하고 계속 같은 인스턴스를 반환하는것을 볼 수 있다.

 

코드로 찾아본 예시는 위와 같지만, 스프링에서 제일 유명한 싱글톤 패턴은 싱글톤 컨테이너이다. 스프링에서는 생성된 Bean을 싱글톤 패턴으로 관리하여 단 하나의 인스턴스만 갖고있는다. 이를 컨테이너가 필요한 곳에 주입하는 방식으로 사용된다.