싱글톤 패턴 정리

youngjoon·2021년 8월 26일
0

Java

목록 보기
1/9
post-thumbnail

싱글톤이란?

단 하나의 유일한 인스턴스를 보장하는 디자인패턴 중 하나입니다.

프로그램 전체에서 단 하나의 인스턴스가 필요한 상황에 사용됩니다. 예를 들면 체스게임의 체스판, 로그를 기록하는 Logger, 스프링의 빈객체 등이 있습니다.


Java 구현방법 - 클래스 static변수 사용

static를 사용한 방법은 대부분의 입문서적이나 인터넷에 나와있는 방법입니다.

구현방법은 1개의 인스턴스를 보장하기 위해서 클래스 내부적으로 2개 이상 생성되지 못하도록 막아아 합니다.

다음은 체스판 클래스의 일부 코드입니다. 체스판은 체스게임 상에서 단 1개만 존재해야 하며 2개이상의 객체가 생성되서는 안됩니다.

아래 체스판의 생성자는 public으로 되어있어서 어디서나 접근가능하며 개수제한없이 객체생성이 가능합니다.

  • 체스보드 클래스
public class ChessBoard {
    public ChessBoard() {}
}

이를 해결하기 위해 생성자 접근자를 클래스 내부에서만 접근가능한 private 생성자를 사용하면 됩니다. 그리고 static변수에 객체를 미리 생성하고, 이를 반환하는 static메서드를 만듭니다.

외부에서 getInstance()메서드를 호출할 때 마다 이미 생성된 유일한 인스턴스를 반환하게 됩니다.

  • 즉시 초기화(eager initialization singleton)
public class ChessBoard {
    private static final ChessBoard instance = new ChessBoard();

    private ChessBoard() {}
    
    public static ChessBoard getInstance() {
    	return instance;
    }
}

객체생성비용이 크다면 미리 생성하지 말고 요청시에 생성하는 방법도 있습니다. 바로 지연로딩(Lazy Loading) 입니다.

아래코드에서 static변수는 null로 초기화하고, 외부에서 getInstance()메서드를 호출해 객체를 요청할 경우에만 생성합니다.

  • 지연 초기화(lazy initialization singleton)
public class ChessBoard {
    private static ChessBoard instance;

    private ChessBoard() {}
    
    public static ChessBoard getInstance() {
    	if(instance == null) {
            instance = new ChessBoard();
        }
    	return instance;
    }
}

싱글톤이 잘 보장되는지 코드를 테스트해봤습니다.

  • 코드테스트
public class ChessBoardTest {
    public static void main(String[] args) {
        ChessBoard board1 = ChessBoard.getInstance();
        ChessBoard board2 = ChessBoard.getInstance();
        System.out.println(board1);
        System.out.println(board2);
        System.out.println("board1 == board2 -> " + (board1 == board2));
    }
}
  • 실행결과
com.chessgame.board.ChessBoard@251a69d7
com.chessgame.board.ChessBoard@251a69d7
board1 == board2 -> true

Spring Framework 구현방법 - 외부에서 객체생성 및 관리

위에 방법은 클래스 내부적으로 처리해야 하므로 객체지향설계에서 좋지 못한 방법이고, SOLID원칙단일 책임 원칙(Single responsibility principle) 을 위반하게 됩니다.

하나의 클래스는 하나의 책임만 가져야 하는데 위에 방식대로 하면 객체 생성과 사용을 2가지의 책임을 가지게 됩니다.

따라서 객체 생성과 사용을 분리할 필요가 있습니다.

아래 코드 중 AppConfig클래스는 체스 게임에 필요한 객체를 생성하는 역할을 담당합니다. 클래스 안에서 직접 객체를 생성하는 것이 아닌 AppConfig에 위임하고 객체는 본래의 역할만 수행합니다.

  • 체스판
public class ChessBoard {
    public ChessBoard() {}
}
  • 수동 빈 등록
@Configuration
public class AppConfig {
    @Bean
    public ChessBoard chessBoard() {
        return new ChessBoard();
    }
}

스프링 프레임워크를 사용하면 의존객체관리를 별도의 스프링 DI컨테이너에서 생성하고 관리하게 됩니다.

다음은 스프링 어노테이션을 활용해서 객체를 관리하는 코드입니다. 앞에 작성한 코드를 스프링코드로 바꿧습니다. 여기서 어노테이션을 보시면 스프링에서 설정을 구성한다는 뜻에서 @Configuration을 붙이고, 각 메서드에 @Bean을 붙여주면 스프링컨테이너에 스프링빈으로 등록됩니다.

디폴트 설정은 싱글톤이며 변경가능합니다.

앞에 방법은 수동으로 빈객체를 등록하는 방법입니다. 또 다른 방법으로 컴포넌트 스캔으로 대상 패키지를 지정하고 빈객체를 만들 클래스에 @Component을 붙여주면 빈 객체로 등록됩니다.

  • 자동 빈 등록
@Configuration
@ComponentScan(basePackages = "com.chessgame.board")
public class AppConfig {
}
package com.chessgame.board;

@Component
public class ChessBoard {
    public ChessBoard() {
    }
}

자바에서 구현한 싱글톤처럼 잘되는지 코드를 테스트해보겠습니다.

  • 코드테스트
@SpringBootApplication
public class SpringSingletonApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringSingletonApplication.class, args);
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        ChessBoard board1 = ac.getBean(ChessBoard.class);
        ChessBoard board2 = ac.getBean(ChessBoard.class);
        System.out.println(board1);
        System.out.println(board2);
        System.out.println("board1 == board2 -> " + (board1 == board2));
    }
}
  • 실행결과
com.chessgame.board.ChessBoard@10643593
com.chessgame.board.ChessBoard@10643593
board1 == board2 -> true
profile
Java언어와 객체지향에 관심이 많은 개발자

0개의 댓글