EJ2E Item 20. 태그가 있는 클래스 대신 클래스 계층구조를 선호하라.

참조: Effective Java 2nd Edition. Prefer class hierarchies to tageed classes

사용자 삽입 이미지
위와 같은 클래스의 단점:
– enum, switch 문, 태그 필드로 인해 지져분하다.
– 여러 구현체를 하나의 클래스로 합쳐놓았기 때문에 가독성이 떨어진다.
– 불필요한 필드까지 가지고 인스턴스를 만들어야 하기 떄문에 메모리 풋프린트가 증가한다.
– 생성자에서 불필요한 필드까지 초기화하지 않는 이상 필드를 final로 선언할 수 없다.
– 생성자에서 초기화를 잘못했을 때 컴파일 시점에 이것을 알 수 없다.
– 새로운 종류를 추가했을 때 switch문에 case를 추가해야 한다는 것을 기억해야 한다. 안그러면 런타임 에러가 발생한다.
– 인스턴스 데이터 타입이 실제 타입을 알려주지 못한다.
=> 즉 장황하고, 에러가 발생할 여지가 많고, 비효율적이다.

먼저 추상 클래스를 만들어서 일반적인 것들을 이 클래스로 이동시키고,
구체적인 하위 클래스를 정의한다.
해당 하위 클래스에서 추상 매서드를 구현한다.

사용자 삽입 이미지
이렇게 구성하면
– 깨끗하고 간단하다.
– 특정 타입에 관련된 속성은 해다 클래스가 가지게 된다. 따라서 불필요한 필드가 없다.
– 모든 필드를 final로 선언할 수 있다.
– 컴파일 시점에 생성자에서 데이터 필드를 초기화 하는지, 추상 매서드를 구현했는지 확인할 수 있다.

EJ2E Item 17. 상속에 대한 설계와 문서화를 제대로 하지 않을 거면 아예 상속을 허용하지 말라.

참조: Effective Java 2nd Edition Item 17: Design and document for inheritance or else prohibit it.

오버라이딩이 가능한 메소드에는 반드시 문서화를 해야 한다.
 – “This implementation ~~” 즉 “이 구현체에서는..” 이라는 식으로 다음 배포 때는 다르게 구현할 수도 있다는 의미를 내포하는 문서화와 그 내용을 자세히 알려줘야 한다.

상속을 고려한 클래스를 테스트하는 유일한 방법은 하위클래스를 작성하는 것이다.
– 배포하기 전에 반드시 하위클래스를 작성해서 테스트 해봐야 한다.

생성자에서 오버라이딩이 가능한 메소드를 직접적으로든 간접적으로든 호출하면 안 된다.
– 상위 클래스 생성자에서 하위 클래스가 오버라이딩한 메소드를 호출하다가 아직 필드 초기화도 안 된 하위 클래스의 필드를 사용해서 원치 않던 결과가 발생할 수 있다.

Cloneable과 Serializable 인터페이스를 구현한 클래스가 상위클래스가 될 여지가 있다면
– 생성자와 마찬가지로 clone과 readObject에서 오버라이딩이 가능한 메소드를 호출하지 않도록 한다.

안전하게 상속하도록 문서화 하거나 설계하지 않은 클래스를 상속할 수 없도록 하는 것이 최선책이다.
– 두 가지 방법 중에서 쉬운 방법은 클래스를 final로 만드는 것이다.
– 두 번째 방법은 모든 생성자를 private 또는 package-private으로 만들고 public static 팩터리를 추가한다. 이 방법은 내부적으로 상속이 가능해서 약간 더 유연하다. Item 15에서 다뤘다.

EJ2E Item 16. 상속보다 컴포지션을 선호하라

참조: Effective Java 2nd Edition Item 16: Favor composition over inheritence

상속
– 상속은 코드 재사용을 하는 강력한 방법이지만 항상 최선의 방법은 아니다. 무분별하게 사용했다가는 연약한 소프트웨어가 된다.

– 상속은 동일한 패키지 내에서 하위 클래스와 상위 클래스 구현을 같은 프로그래머가 관리할 때 안전하다. 또한 상속은 상속하려는 클래스가 확장을 고려해서 설계되었고 문서화 되어 있을 때 안전하다.

– 이 책에서 말하는 상속은 구현체 간의 상속이지 인터페이스 상속을 말하는 건 아니다.

– 메소드 호출과 달리 상속은 캡슐활르 위반한다. 즉 하위 클래스가 상위 클래스의 구현에 의존한다.  상위 클래스의 구현 내용이 배포를 거듭하면서 바뀔 수 있는데.. 이로 인해 하위 클래스 코드는 건드리지도 않았는데 깨질 수가 있다.

– 오직 “is-a” 관계가 성립할 때만 적당하다. 자바 플랫폼에서 이를 위반 한 사례: Stack이 Vector를 상속받았다. Properties가 Hashtable을 상속 받았다.

컴포지션
– 컴포지션을 사용하면 앞서 말한 문제(깨지기 쉬운 코드, 상위 클래스에 새로운 메소드 추가 필요)를 해결할 수 있다.

– 포워딩(흔히 역할 위임이라고 일컫는 작업을 나타내는 듯)을 한다.

– 구현의 구체적인 내용과는 의존성이 없다.

– 래퍼(wrapper) 클래스와 데코레이터 패턴

– 래퍼 클래스 단점: 래퍼 클래스는 콜백 프레임워크에서 사용하기 적당하지 않다. 래퍼 클래스로 감싼 객체는 자기가 누구한테 감싸였는지 모르기 때문에 itself(this) 형태로 호출해도 래퍼는 무시된다. (만약 상속이었다면? 다형성 때문에 결국 상위 클래스를 상속받은 하위 클래스의 객체가 넘어갔겠지만..) => SELF problem

EJ2E Item 15. 변경을 최소화하라

참조: Effective Java 2nd Editio Item 15: Minimize mutability

자바에는 다양한 Immutable 클래스들이 있는데, 그 이유는.. 불변 클래스가 변하는(mutable) 클래스보다 설계하고 구현하고 사용하기 쉽기 때문이다.

불변 클래스를 만드는 다섯 가지 규칙
1. 객체의 상태를 바꾸는 메소드를 제공하지 말라.
2. 클래스를 확장할 수 없도록 해라.
3. 모든 필드를 final로 하라.
4. 모든 필드를 private으로 하라.
5. 변경 가능한 요소에는 상호 배타적으로 접근하도록 하라.(불변 클래스가 변하는 필드를 가지고 있을 때, 클라이언트가 해당 객체에 대한 레퍼런스를 가지지 못하게 하라. defensive copies를 생성자나 aceessor에서 만들고 readObject 메소드를 만들어라. @_@)

별로 안 간단해 보이는데;;; 간단하데요

불변 객체는 당연히 쓰레드-세이프 하다. 따라서 동기화 필요 없다. 자유롭게 공유해서 쓰면 된다.

단점은 매번 별도의 객체를 필요로 한다는 것이다. 비싼 객체는 좀.. 글켔군요.

상속을 막는 방법이 클래스를 final로 만드는 거 말고 좀 더 유연한 방법이 있다. 모든 생성자를 private 또는 package-private으로 만들고 public static 팩터리를 만드는 거다. 같은 패키지에서는 상속해서 다양한 구현체를 만들 수 있지만 해당 패키지 밖에서 참조할 땐 final 클래스와 마찬가지니깐… 오호.. 천젠데;;

어떤 클래스를 불편 불변 클래스로 만들어야 할까? 고민해본 적이 없네… 집에 가면서 생각해보자.. 굳이 바뀔 필요가 없는 클래스도 필요 없이 setter를 전부 만들진 않았을까..

EJ2E 14. public 클래스에서는 접근 메소드를 사용하지 public 필드를 사용하지 마라.

참조: Effective Java 2nd Edition. Item 14: In public classes, use accessor methods, not public fields

그런 클래스에 있는 데이터 필드에 바로 접근하면 캡슐화(Item 13) 장점을 제공하지 못하게 된다.
1. 내부를 변경하면 API까지 바뀐다. 
2. 필드를 읽을 떄 어떤 보조 행위를 추가하지 못한다.
3. 불변성을 보장할 수 없다.

따라서 private 필드를 사용하고 public 접근 메소드(getter)와 (가변 클래스인 경우) setter를 제공하라.

패키지 밖에서 해당 클래스에 접근이 필요한 경우에 접근 메소드를 제공하라. 자바 플랫폼 라이브러리중에 Dimension과 Point 클래스(java.awt 패키지)가 이를 위반하고 있다.(흠.. 뭔가 대체할 만한 클래스가 있겠죠?)

불변값에 직접 전근하게 하는건 그리 나쁘지 않다. 1과 2는 어쩔 수 없는데 불변경은 보장할 수 있다.

예제코드 hour와 minute 이라는 int 타입 final 필드를 public으로 제공하고, 해당 클래스의 생성자에서 int 값 두개를 인자로 받아서 해당 final 필드에 세팅을 해줌.