ITEM4 인스턴스화를 막으려거든 private 생성자를 사용하라
정적(static) 필드(변수)
class KimFamily { static String lastname = "김"; } public class Study { public static void main(String[] args) { KimFamily A = new KimFamily(); KimFamily B = new KimFamily(); } }
정적(static) 메서드
class Counter { static int count = 0; Counter() { count++; } public static int getCount() { return count; } } public class Sample { public static void main(String[] args) { Counter c1 = new Counter(); Counter c2 = new Counter(); System.out.println(Counter.getCount()); } }
- 정적 : 클래스가 메모리에 올라갈 때 자동적으로 생성되는 메소드
- 객체를 생성하지 않고도 클래스를 통해 직접 메서드를 호출할 수 있다.
- 객체변수 접근이 불가능하고, static 변수만 접근이 가능하다.
- 반복적으로 사용되고, 멤버변수를 사용하지 않는 유틸리티 함수 제작에 유용하게 사용된다.
- Math
정적 메서드와 정적 필드만으로 구성된 클래스
- Example
- java.lang.Math 또는 java.util.Arrays 처럼 기본 타입 값이나 배열관련 메서드들을 모아 놓고자 할 때
- java.util.Collections 처럼 특정 인터페이스 구현을 위한 객체의 생성을 담당하는 정적 메서드(또는 팩토리)들을 모아놓고자 할 때
- final 클래스와 관련한 메서드들을 모아놓고자 할 때
- final 클래스는 이를 상속한 하위 클래스에서 구현 불가능 : 주로 static 하게 선언
- 이런 클래스는 인스턴스 생성을 목적으로 설계된 것이 아님
- But 개발자가 생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자(public)를 만든다
- 클래스 외부에서 인스턴스를 생성할 수 있는 상황
BAD
- 추상 클래스로 만들면? : 하위 클래스에서 인스턴스화 가능
BAD
Solution
- private 생성자를 추가하여 인스턴스가 생성되는 상황을 방지하자.
public class UtilityClass { // 인스턴스화 방지용 생성자 입니다. private UtilityClass { throw new AssertionError(); } }
- 생성자가 존재하는데 호출할 수 없는 아이러니
- 직관적이지 않으니, 주석을 통해 설명을 달아두자
- 상속을 불가능하게 만드는 효과도 있다.
- 생성자가 private이면 해당 클래스는 자식을 가질 수 없다.
- 주로 정적 유틸리티 클래스 라고 불린다.
ITEM5 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
클래스와 자원
- 클래스는 보통 하나 이상의 자원에 의존한다.
- 의존한다 = 관계성이 있다 / 자원이 클래스의 동작에 영향을 준다
- 예 : 맞춤법 검사기는
사전
객체에 의존한다. (사전에 존재하는 단어가 아니면 밑줄)
하나 이상의 자원에 존재하는 클래스(1) : 맞춤법 검사기의 잘못된 구현 방식
public class SpellChecker { private static final Lexicon dictionary = ... ; private SpellChecker() {} // 객체 생성 방지용 생성자 public static boolean isValid(String word) { ... } }
public class SpellChecker { private final Lexicon dictionary = ... ; public static SpellChecker INSTANCE = new SpellChecker(); private SpellChecker() {} public static boolean isValid(String word) { ... } }
- 위의 두가지 방식은 클래스가 단 하나의 사전 객체에만 의존한다고 가정하고 있다
BAD
- 일반적으로 사전은 하나로 구성되어 있지 않고, 여러개로 나누어져 있다. (전세계 언어 사전 X)
- 제안) final 한정자를 제거하고, 다른 사전으로 교체하는 메서드를 추가?
- 어색하고 오류를 내기 쉽다 + 멀티스레드 환경에서는 사용할 수 없다.
public class SpellChecker { private static Lexicon dictionary = ...; public static SpellChecker INSTANCE = new SpellChecker(); private SpellChecker() {} public static boolean isValid(String word) { ... } public static void changeDictionary(Lexicon new) { dictionary = new; } }
의존 객체 주입 방법
- 해당 클래스의 인스턴스를 생성할 때 생성자에게 필요한 자원을 넘겨주는 방법
public class SpellChecker { private final Lexicon dictionary; public SpellChecker(Lexicon dictionary){ this.dictionary = Objects.requireNonNull(dictionary); } public boolean is Valid(String word) { ... } }
public class KoreanDict implements Lexicon { ... } public class EnglishDict implements Lexicon { ... } Lexicon kordict = new KoreanDict(); Lexicon engdict = new EnglishDict(); SpellChecker korchecker = new SpellChecker(kordict); SpellChecker engchecker = new SpellChecker(engdict); korcheker.isVaild("한국말"); engcheker.isVaild("English");
- 불변성을 보장하기 때문에 여러 클래스가 같은 의존 객체들을 공유할 수 있다
this.dictionary
로 받아서 사용했기 때문
- 정적 팩토리와 빌더에서도 이런 방식으로 의존 객체를 넘겨줄 수 있다.
응용 : 생성자에 자원 팩토리를 넘겨주는 방식 (팩토리 메서드 패턴)
팩토리
= 호출될 때 마다 특정 타입의 인스턴스를 만들어주는 객체
- Supplier<T> 인터페이스 = 팩토리의 완벽한 표현
- 함수형 인터페이스 : 매개변수는 없고, 반환값만 있다.
String supplier= new String(); supplier = "Hello World"; Supplier<String> supplier= () -> "Hello World"; String result = supplier.get(); System.out.println(result); // Hello World
- Supplier<T>를 입력으로 받는 메서드는 한정적 와일드카드 타입(bounded wildcard type)을 사용하여 팩터리의 타입 매개변수를 제한해야 한다.
와일드 카드
: 제네릭 코드에서 물음표(?) 로 표기되어 있는 것. 아직 알려지지 않은 타입 의미.한정적 와일드카드
: (?) 가 무언가를 extend 한다.- 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩토리를 넘길 수 있게 됨
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }
의존 객체 주입 방식의 장점
- 코드의 유연성과 재사용성, 테스트의 용이성을 개선시킴
- 그러나 의존성이 많은 큰 프로젝트에서는 코드를 어지럽게 만들기도 함
- Spring 등의 의존 객체 주입 프레임워크를 사용하는 것이 좋음
- 의존 객체를 직접 주입하도록 설계된 API를 사용한다.
@Controller public class MemberController { private final MemberService memberService; @Autowired public MemberController(MemberService memberService) { this.memberService = memberService; } }
스프링
이 연관된 객체를 스프링 컨테이너
에서 찾아서 넣어준다.