Item20. 추상 클래스보다는 인터페이스를 우선하라

추상 class

추상 class와 인터페이스의 가장 큰 차이점은 추상 class가 정의한 type을 구현하는 class는 반드시 추상 class의 하위 타입이 되야한다는 점이다.

java는 단일 상속만 지원함으로, 추상 class 방식은 새로운 타입을 정의하는데 제약을 갖게 된다. 반면 인터페이스의 경우는 어떤 class를 상속했더라도 같은 type으로 취급된다

기존 class에 새로운 인터페이스를 구현해넣는것은 쉬우나, 새로운 추상 class를 넣기는 힘들다.
두 class가 동일한 추상 class 를 확장하려면 그 추상 class는 계층구조상 두 class의 공통조상이여야 한다.

Read more

Item19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라

상속용 클래스의 문서화

  • 상속용 클래스는 overriding할 수 있는 method들을 내부적으로 어떻게 이용하는 지 문서로 남겨야 한다.
  • 클래스의 API로 공개된 method에서 클래스 자신의 또 다른 method를 호출할 수도 있는데, 이 method가 overriding이 가능하다면 method의 API 설명에 적어두어야 한다.
  • 어떤순서로 호출되는지, 호출 결과가 이어지는 처리에 어떤 영향을 줄 수 있는지도 문서화해야한다.

문서화 예시 : https://docs.oracle.com/javase/7/docs/api/java/util/AbstractCollection.html

API 문서의 method설명 끝에서 종종 ‘This Implementation’ ~ 로 시작하는 절을 볼 수 있는데 이 부분이 method의 내부 동작 방식을 설명하는 곳이다.

클래스를 안전하게 상속할 수 있도록 하려면, 내부 구현 방식에 대해 설명해주어야 한다.

( Java method 주석에 @implSpec tag를 붙여주면 자바독 도구가 생성해준다. )

Read more

Item18. 상속보다는 Composition을 사용해라

상속의 단점

  • 상속은 캡슐화를 꺠트린다.

상위 class 내부 구현 변경에 따라 하위 class가 영향을 받을 수도 있다. 상위 class가 확장을 충분히 고려해두고 설계되지 않으면 하위 class는 상위 class의 변화맞추어 수정되야 한다.

Read more

Item17. 변경 가능성을 최소화하라

불변 클래스란 객체 생성 후에 객체의 내부 값을 수정할 수 없는 클래스이다. 불변 객체에 간직된 정보는 고정되어 객체 소멸 시점까지 변경되면 안된다.

자바 String,primitive type boxing 클래스, BigInteger,BigDecimal 이 여기에 속한다.

불변 객체를 사용하는 이유는 다음과 같다.

  • 가변 클래스보다 사용하기 쉬우며 , 안전하다.

    ex) 멀티쓰레드상황에서 여러 client가 동시에 객체를 사용하는 경우 (Thread-Safe)

불변 객체 만드는 규칙

  1. 객체의 상태를 변경하는 메소드 (ex) setter) 를 제공하지 않는다

  2. class를 확장할 수 없도록 한다. 대표적으로 class를 final로 선언하면 된다.

  3. 모든 필드를 final로 선언한다.
    ( 어떤 method도 객체의 상태 중 외부에 비치는 값을 변경할 수 없다. )

  4. 모든 필드를 private로 선언한다. 불변 필드인 경우에는 public final로 선언하여도 불변 객체는 되지만, API를 수정하기 힘들어진다.

  5. 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다. 클래스 내부에 가변 필드가 있다면, client에서 그 가변 필드의 참조값을 가지고 수정할수 있으므로 생성자/접근자/readObject method에서 모두 방어적 복사를 수행해야 한다.

Read more

Item15. 클래스와 멤버의 접근 권한을 최소화하라

잘 설계된 컴포넌트는 모든 내부 구현을 완벽히 숨겨, 구현과 API를 깔끔하게 분리한다.

오직 API를 통해서만 다른 컴포넌트와 소통하며, 서로 구체적인 동작 방식은 개의치 않는다.

정보 은닉 (Information Hiding) 의 장점

  1. 시스템을 구성하는 컴포넌트를 서로 독립시켜서 개발/테스트/적용/수정 분석을 개별적으로 할 수 있게 해준다

  2. 여러 컴포넌트를 병렬로 개발 가능함으로, 개발 속도를 높임

  3. 각 컴포넌트를 빨리 파악 및 디버깅이 관리하고, 다른 컴포넌트로 교체하는 작업이 손쉽기 때문에 유지보수 비용이 덜 든다

  4. 직접적으로 성능을 높여주지는 않지만, 성능최적화에 도움을 준다. 완성된 시스템을 프로파일링해 최적화할 컴포넌트를 정하고, 해당 컴포넌트만 최적할 수 있기 때문이다.

  5. SW 재사용을 높임

  6. 시스템 개발 난이도를 낮춘다. 개별 컴포넌트단위로 나누어 개발하고 테스트할 수 있기 때문이다.

Read more

Item14. Comparable을 구현할지 고려하라.

compareTo는 단순 동치성 비교에 더해 순서까지 비교할 수 있으며, generic하다.

1
2
3
public interface Comparable<T> {
public int compareTo(T o);
}

comparable을 구현했다는 것은 그 클래스의 인스턴스들에 순서가 있음을 뜻한다. 그래서 Comparable을 구현한 객체들의 배열은 다음과 같이 정렬이 가능하다.

1
2
3

Arrays.sort(arrayOfInstance);

순서가 명확한 값 class를 작성한다면 Comparable 인터페이스 구현을 고려하자.

compareTo method의 일반 규약은 다음과 같다.

 
compareTo method는 주어진 객체와 this 객체의 순서를 비교한다. 
this 객체가 주어진 객체보다 작으면 음의 정수 , 같으면 0 , 크면 양의 정수를 반환하고
이 객체와 비교할수 없는 타입의 객체가 주어지면 ClassCastException을 던진다. 
1. Comparable을 구현한 class는 모든 x,y에 대해서 sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) 
2. Comparable을 구현한 class는 추이성을 보장해야 한다. ( x.compareTo(y) > 0 && y.compareTo(z) > 0)이면 x.compareTo(z) > 0이다. 
3. Comparable을 구현한 class는 모든 z에 대해 x.compareTo(y) == 0 이면, sgn(x.compareTo(z)) == sgn(y.compraeTo(z)) 다.
4. 부가적으로 다음의 규약은 필수는 아니나 지키면 좋다.(x.compareTo(y) == 0) == x.equals(y) 여야 한다. 
   만약 위 식이 성립하지 않는다면 이 클래스의 순서는 equals method와 일관되지 않음을 명시해야 한다. 

compareTo규약을 지키지 못하면 비교를 활용하는 TreeSet, TreeMap, Collections,Array등의 클래스에서 제공해주는 API가 정상적으로 작동하지 않을 수도 있다.

Read more

Item13. clone overriding은 주의해서 진행하라

Object class에는 clone() method가 protected로 선언되어 있고, 객체를 복사하려면 java.langCloneable Interface를 implement하여 복사할 수 있다.

1
2
3
package java.lang;
public interface Cloneable {
}

(https://docs.oracle.com/javase/7/docs/api/java/lang/Cloneable.html)

Cloneable은 아무 method도 없는 marker interface지만 ,Object의 clone method의 동작 방식을 결정한다.

Cloeanble을 구현한 class에서부터 만들어진 객체에서 clone을 호출하면 객체의 필드들을 복사한 객체를 반환하도록 하고, 그렇지 않은 객체에서는 CloneNotSupportedException을 던진다.

Read more

Item12. toString은 항상 overriding하자

Object toString 은 별도로 overrriding 하지 않는 경우에는 class명@16진수_해시코드 를 반환한다.

1
getClass().getName() + '@' + Integer.toHexString(hashCode())

Object 명세에 보면 toString은 간결하며, 사람이 읽기 쉬운 형태의 정보를 반환해야 하며,

toString을 overrding한 모든 하위 클래스에서도 toString 을 재정의해야 하며, 해당 객체가 가진 주요 값 필드를 반환하는게 바람직하다.
(https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html)

  • toString을 재정의하면 다음과 같은 장점을 가지고 있다.

    1. 해당 객체의 주요필드를 로그에 남김으로 빠른 디버깅 가능
  • 선택사항 : toString 사용시 포맷을 문서화 여부

    • 값 class라면 문서화하는 것을 권장하나, 단점은 해당 포맷에 종속된다.
  • 포맷 명시 여부와 상관없이 toString 반환 값에 포함된 정보를 가져올 수 있는 API 제공해야 함