일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- JPA
- 장애물인식
- 자바
- Database
- 논문
- 자바ORM표준JPA프로그래밍
- 알고리즘
- DeepLearning
- 논문리뷰
- 이펙티브자바
- Hibernate
- 아두이노
- 배달로봇
- 디자인패턴
- cartograhper
- Jetson
- 파이썬
- 영속성컨텍스트
- 자료구조
- Spring Batch
- persistance context
- 프로그래머스
- 운영체제
- MySQL
- 포인트클라우드
- 자율주행
- 딥러닝
- Java
- Python
- 네트워크
- Today
- Total
제리 devlog
[이펙티브자바] 자원을 명시하지 말고 의존 객체 주입을 사용하라 본문
많은 클래스가 하나 이상의 자원에 의존한다. 의존이라는 용어는 spring을 경험해봤다면 흔히 들었을 것이다. 하지만 의존 관계에 대해서 다시한번 짚어보고 자원을 명시하지 말고 의존 객체 주입을 사용하라는 의미를 생각해보자.
의존 관계는 어떤 것인가?
하나의 예시를 보자
public class Car(){
Wheel wheel = new SmallWheel();
public Car(){};
}
public class SmallWheel extends Wheel(){
String modelName = "A";
int size = 50;
public SmallWheel(){}
}
두개의 클래스가있다. Car클래스는 SmallWheel을 입력받아 사용한다. 이때 Car객체는 SmallWheel과 의존 관계가 형성된다. 위의 코드의 문제점은 유지, 보수측면에서 발생한다.
의존성 주입이란?
만약, 작은 바퀴가 단종되서 큰바퀴로 대체해야한다면 아래와 같이 바꿔야한다.
public class Car(){
Wheel wheel = new BigWheel();
public Car(){};
}
public class BigWheel() extends Wheel{
String modelName = "A";
int size = 200;
public BigWheel(){}
}
직접 Car클래스 내부로 접근해 주입받는 객체를 변경해야한다. 지금은 하나의 클래스를 예시로 들었지만 SmallWheel이 비행기에도사용되고 오토바이에도 사용되고 수 많은 클래스에서 사용되고 있다면 클래스를 하나하나들어가 수정해야할 것이다.
그럼 이번에는 생성자를 사용해서 외부에서 객체를 주입한다고 해보자.
public class Car(){
Wheel wheel;
public Car(Wheel wheel){
this.wheel = wheel;
};
}
public class Main(){
public static void main(String[] arg){
Car car = new Car(new BigWheel());
}
}
Car객체를 생성할때 생성자에 의존 관계를 띄고있는 Wheel객체를 주입 받는다면 일일히 Car클래스 내부를 수정하지 않아도 된다. 외부에서 의존 관계를 주입한다해서 의존성 주입(DI)라고 한다.
이펙티브 자바에서 예시
아래코드는 사전과 의존관계가 있고 맞춤법 검사기를 구현하고 있다.
//정적 유틸리티를 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다.
public class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {}//객체 생성방지
public static boolean isValid(String word) { ... }
public static List<String> suggestions(String typo) { ... }
}
//싱글턴을 잘못 사용한 예 - 유연하지않고 테스트 하기 어렵다.
public class SpellChecker {
private final Lexicon dictionary = ...;
private SpellChecker() {}//객체 생성방지
public static SpellChecker INSTANCE = new SpellChecker(...);
public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
두 가지 예시의 공통점은 dictionary가 final로 선언되어있다. dictionary는 불변하는 값으로 단 하나의 사전만 올 수 있다. 하지만 실제 맞춤법 검사기에서는 다양한 언어를 지원해야할 수 도있고 그에따라 여러 사전이 활용어야한다.
첫 번째 예시가 잘못된 이유
//정적 유틸리티를 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다.
public class SpellChecker {
private static Lexicon dictionary = ...;
private SpellChecker() {}//객체 생성방지
public static boolean isValid(String word) { ... }
public static List<String> suggestions(String typo) { ... }
}
dictionary의 final을 제거해서 변경가능하게 만들었다. 이제 SpellChecker에는 다른 사전이 올 수 있지만 또다른 문제가 생긴다. 바로, 멀티 스레드 환경에서 thread safe하지 않다. 여러 스레드에서 dictionary에 접근하여 사용할때 전부 같은 값을 공유한다. 새롭게 객체를 만들면서 다른 사전으로 변경한다 하더라도 다른 스레드에의해 유지되기 힘들다.
두 번째 예시가 잘못된 이유
//싱글턴을 잘못 사용한 예 - 유연하지않고 테스트 하기 어렵다.
public class SpellChecker {
private Lexicon dictionary = ...;
private SpellChecker() {}//객체 생성방지
public static SpellChecker INSTANCE = new SpellChecker(...);
public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
final을 제거해서 사전은 다른 사전으로 대체 가능하지만 문제는 싱글턴이라는 것이다. 인스턴스를 단 하나만 사용할 수 있다. 멀티 스레드 환경에서 dictionary에 접근할때 하나의 값을 공유하게되므로 thread safe하지 않다.
SpellChecker의 목적은 클라이언트가 여러 자원 인스턴스를 지원하며, 원하는 자원(dictionary)으로 실행되어야한다. 이 조건을 만족하기위해서는 의존성 주입을 사용해서 해결할 수 있다.
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
public static void main(String[] args){
SpellChecker spellchecker = new SpellChecker(사전);
}
}
이렇게 외부에서 의존성을 주입하면 매번 원하는 사전으로 객체를 생성할 수 있다. 여기서 한가지 더 이점은 dictionary가 final로 선언할 수 있다. 클라이언트는 사전이 변할 걱정을 하지않아도된다. 이런 방식은 생성자, 정적 팩토리, 빌더 모두 활용할 수 있다.
이 패턴의 쓸만한 변형으로 생성자에 자원 팩터리를 넘겨주는 방식이 있다. 팩터리란 호출될 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체를 말한다. Supplier<T> 인터페이스는 팩터리를 표현한 완벽한 예이다.
아래방식은 클라이언트에서 제공한 팩터리가 생성한 타일들로 구성된 모자이크를 만드는 메서드이다.
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }
Supplier<T>를 입력으로 받는 메서드는 일반적으로 한정적 와일드카드 타입을 사용해 팩터리의 타입 매개변수를 제한해야한다. 위의 Supplier는 Tile의 상속을 받는 객체만 타입 변수로 받을 수 있다. 바꿔말하면 Tile의 속성을 띄는 다양한 종류의 Tile이 외부에서 의존성을 주입할 수 있다는 것이다.
한정적 와일드 카드 타입이란?
아래 메서드는 ArrayList를 매개변수로 입력받아 사용한다.
public void A(ArrayList list) { . . . }
그런데 메서드의 로직은 Integer에 한정되어있는데 String타입에 ArrayList가 들어오면 문제가 생긴다. 만약 매개 변수로 받는 타입이 한정된다면 오류가 발생하지 않을 것이다. 이때 사용되는게 한정적 와일드 카드 타입이다.
public void A(ArrayList<? extends Number> list) { . . . }
위의 메서드는 Number타입과 Number타입을 상속받는 타입변수만 허용한다.
와일드 카드의 제한 종류
<? extends T> 와일드 카드의 상한 제한(upper bound) - T와 그 자손들을 구현한 객체들만 매개변수로 가능
<? super T> 와일드 카드의 하한 제한(lower bound) -T와 그 조상들을 구현한 객체들만 매개변수로 가능
<?> 제한 없음
'Java' 카테고리의 다른 글
[이펙티브 자바] 문자열 연결은 느리니 주의하라 + 문자열 연결 방법 비교 (0) | 2020.11.22 |
---|---|
[이펙티브자바] finalizer와 cleaner 사용을 피하라 (0) | 2020.11.17 |
[이펙티브자바] 인스턴스화를 막으려거든 private 생성자를 사용해라 + java static 개념 (0) | 2020.10.29 |
[이펙티브 자바] private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2020.10.26 |
[이팩티브 자바]생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2020.09.21 |