본문 바로가기
Language/JAVA

[Effective java] 필요한 자원, 의존 객체 주입 사용하기

by jepa 2023. 8. 21.
728x90
SMALL

목표

A를 사용하기 위해서는 B가 필요할 때, A는 자원 B를 의존한다고 볼 수 있습니다.


이렇게 많은 클래스는 하나 이상의 자원에 의존합니다.

의존 자원을 어떻게 사용하는 것이 좋을지 알아봅시다.

  • 책을 읽고 정리하자!
  • 의존 객체 주입법에 대해 알아보자.

 

의존하는 자원을 직접 명시한다면?

맞춤법 검사기(spellChecker)는 맞춤법을 검사하기 위해 사전(dictionary)이 필요할 겁니다.

먼저 검사기에서 직접 자원을 명시하여 사용하는 법을 살펴봅시다!

 

1. 정적 유틸리티 클래스로 구현했을 때

// 객체 생성을 방지한 정적메서드와 필드만을 담은 유틸리티 클래스(SpellChecker)로 구현한 예
public class SpellChecker{
    private static final Lexicon dictionary = ...;

    private SpellChecker(){} // 객체 생성을 방지
    
    //정적 메서드들
}

2. 싱글턴으로 구현했을 때

// 싱글턴으로 클래스(SpellChecker)를 구현한 예
public class SpellChecker{
    private final Lexicon dictionary = ...;

    private SpellChecker(){} // 객체 생성을 방지
    public static SpellChecker INSTANCE = new SpellChecker(...); // 싱글턴
    
    //메서드들
}

두 방식에는 문제점이 있습니다.

  • 유연성
    • 모두 사전(dictionary)을 하나만 사용한다는 점에서 유연성이 좋지 않다. 
      • 언어별 사전, 특수 어휘용 사전, 테스트용 사전 등 여러 용도의 사전이 필요할 수 있는데 
        이런 여러 용도를 사전 하나로 처리하기는 어렵다.
  • 테스트 용이성: 따로 객체 생성이 안된다는 점에서 테스트도 어려운 듯하다.
🙋🏻‍♀️ final를 제거하고 사전을 교체하는 메서드를 추가한다면?
 - 방식 자체가 어색하고 수동으로 처리해줘야 해서 오류를 내기 쉽다.
 - 멀티스레드 환경에서는 쓸 수 없다.
 - 사용하는 자원에 따라 동작이 달라지는 방식에 두 방식은 적합하지 않다.

유연하게 만들어보자!

SpellChecker이 여러 사전을 사용할 수 있도록 하려면 어떻게 할 수 있을까요?

 

인스턴스를 생성할 때(=검사기를 생성할 때)

생성자에 필요한 자원을 넘겨주는(=의존 객체인 사전을 주입) 방식으로 바꾸어 봅시다!

의존 객체 주입 방식

public class SpellChecker{
    private final Lexicon dictionary;

    private SpellChecker(Lexicon dictionary){ // 의존 객체를 주입받는다
        this.dictionary = Objects.requireNonNull(dictionary);
    } 

    //메서드들
}

 

의존 객체를 주입받음으로써 얻을 수 있는 장점은 아래와 같다.

  • 유연성: 인스턴스 생성할 때 자원을 넘겨줌으로써 환경에따라 필요한 자원을 넘겨줄 수 있다.
    • 예시는 자원을 하나 사용했지만 여러개여도 상관 없이 잘 작동한다.
  • 테스트 용이성: 객체 생성을 할 수 있어 테스트가 용이해진다.
  • 여러 클라이언트가 의존 객체들을 안심하고 공유할 수 있다.

해당 의존 객체 주입법은 생성자, 정적 팩터리, 빌더 모두에 똑같이 응용이 가능하다.

 

변형, 생성자에 자원 팩터리를 넘겨줄 수 있다.

팩터리란?
호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체
Java8에서 나온 팩터리의 완벽한 예
Supplier<T> 인터페이스

Supplier<T>를 입력 받는 메서드는 팩터리의 타입 매개변수를 제한해야 합니다.

명시한 타입(자원)의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있다.

클래스 create(Supplier<? extends 자원> 자원Factory) {...}

 

ex) Mosaic create(Supplier<? extends Tile> tileFactory){...}

해당 코드는 클라이언트가 제공한 팩터리가 생선한 타일(Tile)로 구성된 모자이크를 만드는 메서드이다.

Supplier<T> 인터페이스에 대해서
 

[JAVA] Supplier<T> 인터페이스

Supplier 인터페이스는 Java에서 제공하는 함수형 인터페이스(Functional Interface) 중 하나입니다. Java 8부터 추가된 함수형 인터페이스로서, Supplier는 다양한 기능과 유연성을 제공하며, 람다 표현식 및

je-pa.tistory.com

의존 객체 주입(Dependency Injection, DI) 프레임워크

의존 객체 주입은 유연성과 테스트 용이성을 개선해줍니다.

다만, 의존성이 많은 대규모 프로젝트에서는 코드가 복잡해 질 수 있습니다.

 

의존 객체 주입 프레임워크를 사용하면 이런 불편함을 해소할 수 있습니다.

의존 객체 주입(Dependency Injection, DI) 프레임워크
소프트웨어 개발에서 사용되는 디자인 패턴 중 하나

클래스가 직접 의존하는 객체를 생성하지 않고 외부에서 주입받도록
(의존 객체를 직접 주입하도록 설계된) API를 알맞게 응용해 사용하는 방식

유명한 DI 프레임워크로는 Spring Framework(Java), Dagger(Android), Guice(Java), Unity(C#) 등이 있습니다.

이러한 프레임워크들은 설정 파일 또는 어노테이션 기반으로 DI를 처리하며, 개발자가 직접 객체 간의 의존관계를 설정할 필요 없이 자동으로 관리해줍니다.

 

결론

  • 클래스가 내부적으로 하나 이상의 자원에 의존하고, 해당 자원이 클래스 동작에 영향을 준다면
    싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다.
  • 클래스가 필요하는 자원을 클래스가 직접 만들게 하면 안된다.
    • 생성자 혹은 정적 팩터리, 빌더를 이용하여 넘겨받자.
  • 의존 객체 주입 방법은 유연성과 재사용성 테스트용이성에 좋다.

의문점?

클래스가 내부적으로 하나 이상의 자원에 의존하고, 해당 자원이 클래스 동작에 영향을 준다면
싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다.

고 했는데

스프링에서는 @Controller 어노테이션을 사용하여 컨트롤러를 구현하면 해당 컨트롤러는 싱글턴으로 관리된다.
보통 컨트롤러는 Service객체를 의존하는데 말이다.

이 두가지는 그럼 상반된 내용인걸까?

 

흠.. 내 생각엔 클래스 자체를 구현할 때 싱글턴 패턴으로 만들지 말라는 것으로 이해했다.

 

위의 예시에서는

public static SpellChecker INSTANCE = new SpellChecker(...); // 싱글턴

로 클래스 자체를 싱글턴 패턴으로 만들어버렸다.

 

이는 클라이언트 환경이 어떻게 되냐와는 상관없이 클래스 자체로 필요한 자원을 정해버리게 되는 것이다.

(이게 맞나..?)

 

스프링 코드로 하나 예시를 들자면

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/feed")
public class FeedController {
    private final FeedService feedService;
    
    // 이하생략

해당 FeedController는 

@RequiredArgsConstructor 애너테이션을 사용하여

final 필드를 주입받는 생성자가 있는 클래스,

즉 의존 객체 주입 방식을 사용한 클래스이다.

FeedController자체가 싱글턴 패턴이 아닌 것이다.

 

정리하자면 클래스가 내부적으로 하나 이상의 자원에 의존하고, 해당 자원이 클래스 동작에 영향을 준다면
싱글턴으로 사용하지말라는 것이 아닌, 싱글턴 패턴으로 만들지 말라는 뜻으로 이해했다..!

728x90
LIST

'Language > JAVA' 카테고리의 다른 글

[JAVA] Supplier<T> 인터페이스  (0) 2023.08.22
[JAVA] 리터럴 String vs String 객체  (0) 2023.08.22
[JAVA][자료구조] Linked List  (0) 2023.08.12
[JAVA][자료구조] HashMap  (0) 2023.08.12
[JAVA][자료구조] 배열  (0) 2023.08.10