Dev_Henry

[Java] 싱글톤으로 만들고 의존성 주입받기(스프링 흉내내기) - 우테코 프리코스 본문

CS/Java

[Java] 싱글톤으로 만들고 의존성 주입받기(스프링 흉내내기) - 우테코 프리코스

데브헨리 2023. 11. 7. 15:59
728x90

우테코 프리코스를 진행하다보면 배워갈게 많다고 해서 프리코스를 진행중이다.

과제의 요구사항이 적다보니 1주차는 별 생각없이 구현을 했었는데 사람들을 보니 전부 MVC패턴으로 구현하고 있더라..

그래서 2주차는 나도 좀 분리를 해서 작성했는데, 음 여전히 요구사항 자체가 별로 없다보니 굳이 잘게잘게 분리하는게 비효율적으로 느껴져서 적당한 크기로만 분리했었다.

그리고 이번 3주차 과제는 좀 더 본격적으로 분리를 하려고 했다.

분리를 하는김에 spring을 흉내내서 각각 객체들을 싱글톤으로 관리하고, 처음 프로젝트를 돌릴때 의존성 주입방식으로 생성해주면 더욱 효율성 좋고 유지보수가 편리하도록 만들 수 있을것 같아서, 이렇게 진행하려한다.

 

보통 싱글톤을 간단하게 구현하는 방식으로는 lazy initialization 방식을 많이 사용한다.

public class InputView {
    private final InputView inputView;
    private InputView(){}
    public static InputView getInstance(){
    	if(inputView == null) inputView = new InputView();
        return inputView;
    }
}

 

하지만 최근 싱글톤에 대해 공부를 했는데 해당 방식은 멀티스레드 환경에서 문제가 생길 수 있어 권장하지 않는 방법이고, sync 키워드를 사용하기에는 성능 하락 이슈가 있어 가장 권장하는 방법은 내부클래스로 holder를 두고, 클래스 로딩시점을 이용한 초기화 방법이라고 한다.

public class InputView {
    private InputView(){}
    private static class InputViewHolder{
        private static final InputView inputView = new InputView();
    }
    public static InputView getInstance(){
        return InputViewHolder.inputView;
    }
}

 

 


 

이렇게 싱글톤으로 구현하던중 막히는 상황이 생겼다.

위의 view처럼 생성에 필요한 파라미터가 없는 객체들은 위의 방식으로 싱글톤 인스턴스를 생성하는데 무리가 없었지만,

Controller의 경우 생성단계에서 view와 service가 필요하다.

그리고 이것들을 내부 생성자에서 가져오는것이 아니라, 외부에서 주입받는 방식으로 하고싶다.

싱글톤을 구현할때 holder방식이 아닌 lazy초기화 방식을 사용한다면 아래와 같은 방식으로 주입받을 수 있었다.

public class LottoController {
    private final InputView inputView;
    private final OutputView outputView;
    private static LottoController instance;

    private LottoController(InputView inputView, OutputView outputView){
        this.inputView = inputView;
        this.outputView = outputView;
    }

    public static LottoController getInstance(InputView inputView, OutputView outputView){
        if(instance==null){
            instance = new LottoController(inputView,outputView);
        }
        return instance;
    }
}


----

public class Application {
    public static void main(String[] args) {
        // TODO: 프로그램 구현
        InputView inputView = InputView.getInstance();
        OutputView outputView = OutputView.getInstance();
        LottoController lottoController = LottoController.getInstance(inputView,outputView);
    }
}

하지만 위에서 말했던 것처럼 해당 방식은 멀티스레드에서 안전하지 않은 방식이다.

 

그래서 아래처럼 holder 방식으로 작성을 하려고 하니 주입받을 수가 없었다.

public class LottoController {
    private final InputView inputView;
    private final OutputView outputView;

    private LottoController(){
        this.inputView = InputView.getInstance();
        this.outputView = OutputView.getInstance();
    }

    private static class LottoControllerHolder{
        private static final LottoController lottoController = new LottoController();
    }

    public static LottoController getInstance(){
        return LottoControllerHolder.lottoController;
    }
}

 


 

그럼 의존성 주입을 받으면서 싱글톤 객체로 만들려면 어떻게 해야할까?

이 목표를 가장 잘 구현한 것이 스프링이다. 즉 스프링에서는 어떤방식으로 구현해주고 있을까?

정답은 스프링을 공부한다면 계속해서 듣게될 '컨테이너'이다.

스프링은 IoC컨테이너 (DI컨테이너, 싱글톤 컨테이너.. 등 다양하게 불린다)를 이용해 Bean객체를 관리하고 의존성주입을 해준다.

실제 스프링은 엄청 복잡하게 구현되어있겠지만, 간단하게 흉내내보기로 한다.

public class SingletonRegistry {
    private static Map<String, Object> registry = new ConcurrentHashMap<>();

    private SingletonRegistry() {}

    public static void register(String key, Object instance) {
        if (!registry.containsKey(key)) {
            registry.put(key, instance);
        }
    }

    public static Object getInstance(String key) {
        return registry.get(key);
    }
}

우선 싱글톤 컨테이너를 저장하고 관리해줄 컨테이너를 만들어야한다.

위 객체는 ConcurrentHashMap으로 멀티스레드에 안전하게 Object들을 관리한다.

이제 앞으로 싱글톤 객체를 생성할때는 해당 컨테이너에 register하고, 사용할때는 컨테이너에서 get해와 사용하도록 하면된다.

public class LottoController {
    private final InputView inputView;
    private final OutputView outputView;
    private LottoController(InputView inputView, OutputView outputView){
        this.inputView = inputView;
        this.outputView = outputView;
    }

    public static LottoController getInstance(InputView inputView, OutputView outputView){
        String key = "LottoController";
        if (SingletonRegistry.getInstance(key) == null) {
            SingletonRegistry.register(key, new LottoController(inputView, outputView));
        }
        return (LottoController) SingletonRegistry.getInstance(key);
    }
}

 

 

이렇게 스프링 컨테이너를 흉내내어 싱글톤으로 객체를 관리하며 의존성주입까지 해주었다.

 

우테코 프리코스를 진행하면서 우테코 커뮤니티에서 다양한 사람들의 의견도 들어보고 다른사람들이 작성한 과제 코드를 보다보니, 뭔가 과제를 무조건 MVC패턴으로 작성하고, 클래스와 메서드를 분리할수 있는 최대한 분리해야만 한다고 생각하는 사람들이 좀 보였다. 어디까지나 책임을 분리하고 패턴을 적용시키는 것은 추후 프로그램이 확장되거나 수정될때 유지보수를 편리하게 하기 위함임을 생각하고, 규모가 작고 변경될 일이 없는 프로젝트에서는 오히려 배보다 배꼽이 커지는 상황이 생길 수 있다는걸 생각해야 한다.

 

물론 프리코스를 진행하는 목적인 객체지향에 대해 생각해보고 학습하는 용도로는 이렇게 책임을 분리하고 패턴을 적용시키는 연습을 하는게 효과가 좋은 것 같다.

728x90
반응형