Study/이펙티브 자바 / / 2023. 8. 19. 18:04

[Effective Java 3E] 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

💥 개요

많은 클래스가 하나 이상의 자원에 의존합니다. 가령 맞춤법 검사기는 사전(dic-tionary)에 의존하는데, 이런 클래스를 정적 유틸리티 클래스로 구현한 모습을 드물지 않게 볼 수 있습니다.

1. 정적 유틸리티 클래스

public class SpellChecker {
	private static final Lexicon dictionary = ...;
    
    private SpellChecker() {}
    
    public static boolean isVaild(String word) {...}
    public static List<String> suggestions(String typo) {...}
}

2. 싱글턴

public class SpellChecker {
	private final Lexicon dictionary = ...;
    
    private SpellChecker() {}
    public static SpellChecker INSTANCE = new SpellChecker(...);
    
    public static boolean isVaild(String word) {...}
    public static List<String> suggestions(String typo) {...}
}

두 방법 다 확장에 유연하지 않고 테스트가 어렵습니다.

사전은 굉장히 여러 종류가 있는데 dictionary 하나로 이 역할을 모두 수행하기는 어렵습니다.

사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않습니다.

필드에서 final을 제거하고 다른 사전하는 메서드를 추가할 수 있겠지만, 어색하고 오류를 내기 쉬우며 멀티쓰레드 환경에서 사용할 수 없다는 단점이 있습니다.

 

💡 해결방법

public class SpellChecker {
    private final Lexicon dictionary;
    
    public SpellChecker(Lexicon dictionary){
    	this.dictionary = Objects.requireNotNull(dictionary);
    }
    
    public static boolean isVaild(String word) {...}
    public static List<String> suggestions(String typo) {...}
}

interface Lexicon {}

public class customDictionary implements Lexicon {
}

이와 같이 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식(객체 주입)을 통해 해결이 가능합니다.

이 패턴은 아주 단순하고 자원이 몇 개든 의존 관계가 어떻든 상관없이 잘 동작합니다. 또 불변을 보장하여 멀티쓰레드 환경에서도, 여러 클라이언트가 의존 객체들을 안심하고 사용할 수 있습니다.

하지만 의존 객체 주입이 유연성과 테스트 용이성을 개선해주지만, 의존성이 수백, 수천개가 되는 큰 프로젝트에서는 코드를 어지럽게 만들기도 합니다. 그렇기 때문에 Spring과 같은 프레임워크를 사용하면 이런 어질러짐을 해결 할 수 있습니다.

 

클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면
싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다.
이 자원들을 클래스가 직접 만들게 해서도 안 된다.
대신 필요한 자원을 의존 객체 주입을 통해 유연성, 재사용성, 테스트 용이성을 개선하자!

 

 

❤️ 스터디 질답 정리

Q.왜 DI를 하지 않으면 유연성과 재사용성이 떨어지는건지?

A.isValid()와 같은 코드에서 English SpellChecker와 Korean SpellChecker를 사용하려면 매번 다른 클래스를 만들어야 합니다. 하지만 DI를 사용하면 하나의 클래스에서 인터페이스를 통해 유연하게, 재사용이 가능합니다. 

 

Q. 그러면 이렇게 매번 객체를 변경하는게 좋은 일인지?

A. 동시성 이슈등이 발생하는 경우라면 좋지 않습니다. 그래서 final을 명시하여 바꾸지 못하도록 하거나, 동시성에 대비해야 합니다.

 

Q. 그럼 동시성 이슈가 없는 thread safe한 객체를 만들기 위해서는 뭘 해야하는지?

A. 불변(immutable) 객체로 만들어야 합니다. 불변 객체로 만들게 되면 객체 내부의 상태값이 변경되지 않아 동시성 이슈가 없을 수 있습니다.

 

Q. 그럼 불변 객체를 만드는 방법은 어떻게 해야하는지?

A. 모든 필드를 final로 변경하고 필드 중 클래스가 있다면 해당 클래스도 immutable해야합니다. 그리고 컬렉션이 필드에 있다면, 생성자와 getter 모두 방어적 복사를 통해 참조를 끊어야 합니다.

 

Q. 가변 객체는 thread safe할 수 없는건지?

A. 가변 객체의 경우 synchronized를 선언하여 락을 거는 방법이 있습니다. 해당 방법을 통해서 간단하게 thread간 동기화를 시켜 thread safe하게 객체를 변경할 수 있습니다. 그리고 명시적으로 Lock을 얻어 thread safe하게 할 수 있습니다. 하지만 이 보다 좋은 방법은 concurrenthashmap을 사용하는 방법이 있습니다. 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유