[JAVA] Effective Java. 정리 - (Item 15 ~ 25)
15. 클래스와 멤버의 접근 권한을 최소화하라
- 프로그램 요소의 접근성은 가능한 한 최소한으로 하라.
- 꼭 필요한 것만 골라 최소한의 public API를 설계하자.
- 그 외에는 클래스, 인터페이스, 멤버가 의도치 않게 API로 공개되는 일이 없도록 해야 한다.
- public 클래스는 상수용 public static final 필드 외에는 어떠한 public 필드도 가져서는 안된다.
- public static final 필드가 참조하는 객체가 불변인지 확인하라.
16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라.
- public 클래스는 절대 가변 필드를 직접 노출해서는 안된다.
- 불변 필드라면 노출해도 덜 위험하지만 완전히 안심할 수는 없다.
- package-private 클래스나 private 중첩 클래스에서는 종종 필드를 노출하는 편이 나을 수 있다.
17. 변경 가능성을 최소화하라
- getter가 있다고 해서 무조건 setter를 만들지는 말자. 클래스는 꼭 필요한 경우가 아닐라면 불변이어야 한다.
- 불변 클래스는 장점이 많으며, 단점이라고는 특정 상황에서의 잠재적 성능 차이다.
- 단순한 값 객체는 항상 불변으로 만들자.
- 성능 때문에 어쩔 수 없다면 불변 클래스와 쌍을 이루는 가변 동반 클래스를 public 클래스로 제공하도록 하자.
- 모든 클래스를 불ㄹ변으로 만들 수는 없다. 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이자.
- 객체가 가질 수 있는 상태의 수를 줄이면 그 객체를 예측하기 쉬워지고 오류가 생길 가능성이 줄어든다.
그러니 꼭 변경해야 할 필드를 뺀 나머지 모두를 final로 선언하자.
- 다른 합당한 이유가 없다면 모든 필드는 private finald 이어야 한다.
- 생성자는 불변식 설정이 모두 완료되느 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.
- 확실한 이유가 없다면 생성자와 정적 팩토리 외에는 그 어떤 초기화 메서드도 public으로 제공해서는 안됀다. 객체를 재활용할 목적으로 상태를 다시 초기화하는 메서드도 안된다. 복잡성만 커지고 성능 이점은 없다.
18. 상속보다는 컴포지션을 사용하라
- 상속은 강력하지만 캡슐화를 해친다는 문제가 있다.
- 상속은 상위 클래스와 하위 클래스가 순수한 is-a 관계일 때만 써야 한다.
- is-a 관계일 때도 안심할 수만은 없는 것은, 하위 클래스의 패키지가 상위 클래스와 다르고, 상위 클래스가 확장을 고려해 설계되지 않았다면 여전히 문제가 될 수 있다.
- 상속의 취약점을 피하려면 상속 대신 컴포지션과 전달을 사용하자.
- 특히 래퍼 클래스로 구현할 적당한 인터페이스가 있다면 더욱 그렇다.
- 래퍼 클래스는 하위 클래스보다 견고하고 강력하다.
19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라.
- 상속용 클ㄹ래스를 설계하는 것은 만만치 않다.
- 클래스 내부에서 스스로를 어떻게 사용하는지 모두 문서로 남겨야 하며, 일단 문서화한 것은 그 클래스가 쓰이는 한 반드시 지켜야 한다.
- 그렇지 않으면 그 내부 구현 방식을 믿고 활용하던 하위 클래스를 오작동하게 만들 수 있다.
- 다른 이가 효율 좋은 하위 클래스를 만들 수 있도록 일부 메서드를 protected로 제공해야 할 수 있다.
- 클래스를 확장해야 할 명확한 이유가 떠오르지 않으면 상속을 금지하는 편이 낫다.
- 상속을 금지하려면 클래스를 final로 선언하거나 생성자 모두를 외부에서 접근할 수 없도록 만들면 된다.
20. 추상 클래스보다는 인터페이스를 우선하라
- 일반적으로 다중 구현용 타입을로는 인터페이스가 가장 적합하다.
- 복잡한 인터페이스라면 구현하는 수고를 덜어주는 골격 구현을 함께 제공하는 방법을 꼭 고려해보자.
- 골격 구현은 가능한 한 인터페이스의 디폴트 메소드로 제공하여 그 인터페이스를 구현한 모든 곳에서 활용하는 것이 좋다.
- 인터페이스에 걸려있는 구현상의 제약 때문에 골격 구현을 추상 클래스로 제공하는 경우가 더 흔하다.
21. 인터페이스는 구현하는 쪽을 생각해 설계하라
- 기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요한 경우가 아니라면 피해야 한다.
- 추가하려는 디폴트 메서드가 기존 구현체들과 충돌하지는 않을지 고려해야 한다.
- 반면, 새로운 인터페이스를 만드는 경우라면 표준적인 메서드 구현을 제공하는 데 아주 유용한 수단이며, 그 인터페이스를 더 쉽게 구현해 활용할 수 있게끔 해준다.
- 디폴트 메서드는 인터페이스로부터 메서드를 제거하거나 기존 메서드의 시그니처를 수정하는 용도가 아님을 명심해야한다. 이런 형태로 인터페이스를 변경하면 반드시 기존 클라이언트를 망가뜨리게 된다.
- 결론적으로, 디폴트 메서드라는 도구가 생겼더라도 인터페이스를 설계할 때는 여전히 세심한 주의를 기울여야 한다.
- 인터페이스를 릴리즈한 후라도 결함을 수정하는게 가능한 경우도 있겠지만, 그 가능성에 기대서는 안된다.
22. 인터페이스는 타입을 정의하는 용도로만 사용하라
- 인터페이스는 타입을 정의하는 용도로만 사용해야 한다.
- 상수 공개용 수단으로 사용하지 말자. -> 클래스를 만들어 사용하는게 좋다.
23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라.
- 태그 달린 클래스르 써야 하는 상황은 거의 없다.
- 새로운 클래스를 작성하는데 태그 필드가 등장한다면 태그를 없애고 계층 구조로 대체하는 방법을 생각해보자.
- 기존 클래스가 태그 필드를 사용하고 있다면 계층구조로 리팩토링하는 것을 고민해보자.
24. 멤버 클래스는 되도록 static으로 만들라
- 중첩 클래스에는 네 가지가 있으며, 각각의 쓰임이 다르다.
- 메서드 밖에서도 사용해야 하거나 메서드 안에 정의하기엔 너무 길다면 멤버 클래스로 만든다.
- 멤버 클래스의 인스턴스 각각이 바깥 인스턴스를 참조한다면 비정적으로, 그렇지 않으면 정적으로 만들자.
- 중첩 클래스가 한 메서드 안에서만 쓰이면서 그 인스턴스를 생성하는 지점이 단 한 곳이고 해당 타입으로 쓰기에 적합한 클래스나 인터페이스가 이미 있다면 익명 클래스로 만들고, 그렇지 않으면 지역 클래스로 만들자.
25. 톱레벨 클래스는 한 파일에 하나만 담으라
- 소스 파일 하나에는 반드시 톱레벨 클래스를 하나만 담자.
- 이 규칙만 따른다면 컴파일러가 한 클래스에 대한 정의를 여러 개 만들어내는 일은 사라진다.
- 소스 파일을 어떤 순서로 컴파일하던 바이너리 파일이나 프로그램의 동작이 달라지는 일은 결코 일어나지 않을 것이다.