[Design Pattern] 싱글턴 패턴

이재훈·2023년 8월 23일
1

디자인패턴

목록 보기
4/6

자주 사용되는 디자인 패턴

싱글턴 패턴

해당 클래스의 인스턴스를 오직 하나만 만들 수 있도록 제한하는 패턴입니다. 왜 ? 인스턴스를 하나만 만들도록 제한하는 이유가 무엇일까요?

인스턴스를 새로 생성한다는 것은 자원(메모리, 시간)을 소모하는 행위입니다. 결과적으로 인스턴스를 새로 생성하지 않으면 생성에 필요한 자원을 아낄 수 있다는 장점이 생기는 것입니다.

싱글턴 패턴이 필요한 상황


컨트롤러 - 서비스 - 레포지토리 패턴입니다. 여기서 클라이언트의 http 요청을 받을 때마다 컨트롤러 - 서비스 - 레포지토리 인스턴스를 만든다면 어떨까요?

public class Controller {
	@GetMapping("/some-api")
    public void someApi() {
    	Service service = new Service();
        service.createArticle("data");
    }
}

우리는 실제로 서비스를 새로운 객체로 생성하여 호출하지 않습니다. 매번의 http 요청마다 객체를 생성하는 메모리, 시간을 소모하게 됩니다.

실제로 스프링 프레임워크는 서비스의 객체를 싱글톤으로 생성하여 의존성 주입을 통해 내부에 메서드를 호출할 수 있게 해줍니다.

public class Controller {

	private final Service service;
    
    public Controller(Service service) {
    	this.service = service;
    }

	@GetMapping("/some-api")
    public void someApi() {
        service.createArticle("data");
    }
}

위의 그림처럼 스프링 애플리케이션 컨텍스트 안에는 싱글톤 Bean 객체를 가지고 있고 Service에 주입해주게 됩니다. 그래서 우리는 코드에 서비스를 객체 생성하지 않고도 서비스, 레포지토리의 메서드를 호출할 수 있게 되는 것 입니다.

멀티 쓰레드 환경에서 싱글턴 패턴을 사용해도 충분한 이유, 무상태성 Bean

어떻게 Controller - Service - Repository는 싱글턴 패턴을 사용해도 문제되지 않을까요?
우리는 메서드만 사용하고 있는 것 뿐만 아니라 참조 변수도 사용하고 있습니다.

@RestController
Public class Controller {
	private Service service;
    // ... 생략
}    

이렇게 참조 변수를 가지고 있더라도 각각의 로직에서 if문의 분기문에 사용된다거나 하지 않습니다. 고로 동시성 문제에서 안전합니다. 이런 것을 "무상태성 Bean"이라고 표현합니다. 참조변수를 가지는 것은 상태라고 부르지 않습니다. 어떤 값을 가지고 있고 그 값이 코드에 흐름에 영향을 준다면 그것을 상태라고 부릅니다.

그렇다면 상태를 가지고 있는 코드는 어떤 코드일까요?

public class Controller {

	private final Service service;
  	private final Boolean flag;  
    public Controller(Service service) {
    	this.service = service;
        this.flag = Boolean.FALSE;
    }

	@GetMapping("/some-api")
    public void someApi() {
    	if (flag) {
        	service.createArticle("data");
        }
    }
    public void toggleFlag() {
    	flag = !flag;
    }
}

여기서 flag는 로직에 영향을 줍니다. flag의 값에 따라 createArticler 가 호출될 수도 아닐 수도 있습니다. 이것은 멀티쓰레드 환경에서 심각한 문제를 나타낼 수 있습니다. 우리는 Bean을 무상태성으로 만들어야 여러 쓰레드에서 재사용할 수 있습니다.

정리하자면 Bean이 무상태성이기 때문에 Bean을 싱글턴으로 만들어서 사용할 수 있는 것이고 문제 없는 싱글턴 Bean이기 위해서는 무상태성이어야 합니다.
이는 성능상 이득을 가져오게 됩니다.

class Singleton {
	private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {};
    
    public static Singleton getInstance() {
    	return INSTANCE;
    }

}

싱글톤 패턴을 구현하는 방법은 여러가지가 있습니다.
위의 코드는 static으로 싱글톤 인스턴스를 생성하고 생성자는 private 접근제어자를 사용하여 외부에서 호출할 수 없게 작성하였습니다. 외부에서 Singleton 클래스의 객체를 가져오려면 getInstance 메서드를 호출해야 하고 이는 이미 생성된 Singleton 인스턴스를 리턴해 줍니다.

싱글턴 패턴이 사용되는 곳

주로 Database Connection Pool 이나 Tread Pool 등은 애플리케이션 전체에서 하나의 객체를 생성하여 공유합니다. 이런 Pool은 수십, 수백개의 커넥션과 쓰레드가 들어있기 때문에 하나 더 만들기에는 비용이 너무 큽니다. 고로 싱글턴 패턴을 사용하여 애플리케이션에서 오직 하나만 존재하게 됩니다.

싱글턴은 객체지향적일까?

객체지향적인 관점에서 보면 싱글턴 패턴은 잘못된 코드입니다. 하나의 클래스가 자신의 생성에 대한 책임과 원래 그 클래스가 해야할 기능에 대한 책임을 가지고 있기 때문에 단일 책임 원칙을 위반합니다. 하지만 항상 객체지향적인게 답일까? 라는 것을 생각해 보아야 합니다. 싱글턴 패턴의 성능상 이점은 객체지향적이지는 않지만 충분히 가치가 있기 때문입니다.

결론

객체지향의 끝이라고 불리는 스프링도 단일 책임 원칙을 위반한다는 싱글턴 패턴을 사용하고 있습니다. 무조건 객체지향적으로 코드를 작성하는 것이 정답은 아닌 것 같습니다. 성능상의 이점 등 여러가지를 고려하여 코드를 유연하게 작성할 줄 알아야 한다고 생각합니다.


해당 게시글은 프로그래머스 스쿨 강의
"실무 자바 개발을 위한 OOP와 핵심 디자인 패턴(푸)"
를 정리한 내용입니다. 쉽게 잘 설명해주시니 여러분도 강의를 듣는 것을 추천드립니다.

profile
부족함을 인정하고 노력하자

0개의 댓글