엄범

 

아이템 15. 클래스와 멤버의 접근 권한을 최소화하라
  • 잘 설계된 컴포넌트는 클래스 내부 데이터와 내부 구현 정보를 외부 컴포넌트로부터 완벽히 숨긴다. 필요한 것만 public으로 공개한다.
    • 즉, 구현과 API를 깔끔히 분리하고 오직 API를 통해서만 다른 컴포넌트와 소통하며 서로의 내부 동작 방식에는 개의치 않는다.
    • 정보 은닉(=캡슐화) 개념.
  • 메서드를 public으로 한다는건, 다른 컴포넌트가 이 메서드를 사용할 여지가 있다는 것이다.
    • 이는 즉, 바꾸고 싶을 때 의존성 때문에 바꾸지 못하는 경우가 생기고, 지속적으로 관리해줘야 된다는 의미가 된다.
    • 그래서 접근성은 가능한 한 낮게 할당하는 것이 좋다. private이라면 내부에서 언제든 변경 가능하기 때문에.

 

아이템 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
  • package-private 클래스 혹은 private 중첩 클래스라면 필드가 private이 아니어도 괜찮다.

 

아이템 17. 변경 가능성을 최소화하라
  • 상속을 금지(아이템19)하고 내부 상태를 항상 고정으로 만들어 불변 클래스로 쓸 수 있다.
  • 불변 클래스는 상태 변화가 없어 여러모로 안전하고 장점이 많으니 써야하는 상황이 오면 주저말고 쓰면 된다.
  • 불변 클래스의 단점은 상태를 바꿀 수 없으니 원하는 객체를 완성할 때 까지 계속 객체를 만들고 버리고 해야 된다는 점이다.
    • 대처 방법은 쓰일 것 같은 연산(새로운 객체를 반환받는, 예를 들면 숫자 몇 개를 변경한다던가)을 메서드로 제공하는 것이다.
    • 그리고 이런 연산을 제공하기 위한 내부적인 처리는 내부에 가변 동반 클래스로 두는게 일반적이다.
  • 상태가 불변이기 때문에 내부적으로 데이터를 캐시해서 제공해도 좋다.
  • 객체를 재활용할 목적으로 상태를 다시 초기화 하는 경우가 있는데, 복잡성만 커지고 성능 이점은 거의 없으므로 하지 않는다.

 

아이템 18. 상속보다는 컴포지션을 사용하라
  • 여기서 얘기하는 상속이란 implements가 아니라 extends를 말한다.
  • 상속의 단점?
  • 컴포지션이란? [Coding/CodingNote] - [코딩 노트] 객체 지향 패러다임  
    • 컴포지션은 보통 내부 private 변수로 두고 이를 Wrapper클래스로 감싸게 되는데, 이런 패턴을 Decorator pattern이라고 한다.
    • 컴포지션과 전달의 조합은 넓은 의미로 delegation이라고 부른다.
    • 단, 엄밀히 따지면 래퍼 객체가 내부 객체에 자기 자신의 참조를 넘기는 경우만 위임에 해당한다.
    • 예시) 래퍼 클래스(상속 대신 컴포지션) / 재사용할 수 있는 전달 클래스
      • InstrumentedSet 안에 private final로 Set을 가져도 되는데 중간에 ForwardingSet을 한 번 거치는 이유는 재활용성 때문 인 듯.
      • 만약 ForwardingSet이 없었다면, InstrumentedSet을 만들 때 clear, isEmpty 같은걸 각각 다 만들어줘야 했을 것임. ForwardingSet이 중간에서 기본적으로 메서드들을 제공해주기 때문에 InstrumentedSet이 add, addAll만 딱 재정의해도 되도록 했음.
      • 사용은 아래와 같이 한다.

```java

Set<Instant> times = new InstrumentedSet<>(new TreeSet<>(cmp));

Set<E> s = new InstrumentedSet<>(new HashSet<>(INIT_CAPACITY));

```

 

아이템 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라
  • 상속용 클래스를 만든다면 당연히 문서화 해야 하는건데... 중요한건 Override 가능한 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 한다.
    • 상속 받아서 Override하면 동작이 달라질 수 있기 때문.
  • 상속용 클래스라면, 내부에서 Override 가능 메서드를 호출하지 않는 것이 좋다. 직접적으로든, 간접적으로든.
    • 상속해서 하위 클래스가 그 메서드를 Override 하면, 그를 사용하는 다른 메서드들의 동작까지 달라지기 때문이다!
      • 그래서 내부에서 Override 가능 메서드를 호출하려면, 그 메서드가 하위 메서드에서 구현되어도 동작이 변하지 않게 문서화와 스펙을 docstring에 잘 명시해주어야 한다.
      • interface의 경우, default 메서드에서 추상 메서드를 사용하는 경우를 종종 볼 수 있다.(대표적으로 java.util.Map) 이는 인터페이스 자체가 클래스와는 다른 목적, 즉 어떠한 스펙과 규약에 맞춰서 이 메서드를 작성해라라는 의미이기 때문이다.
    • Override 가능하다는건 public이나 protected 메서드를 의미한다.
    • 예시 ) class Super / class Sub extends Super  
      • 보면, Sub.생성자()가 Super.생성자()를 호출하고 여기서 overrideMe()가 호출되는데 바로 재정의된 Sub.overrideMe()가 호출된다!
      • 이런 맥락에서 생성자, clone(), readObject() 는 Override 가능 메서드를 절대로 호출하지 말아야 한다.
    • Override 가능 메서드를 호출하지 않으려면?  (인턴할 때 썼던 방법)
      • 메서드 body를 모두 private _helperMethod()로 옮긴 다음
      • Override 가능 메서드에서는 단순히 _helperMethod()를 반환해주고,
      • 내부적으로도 private _helperMethod()를 사용하도록 한다.
  • 상속을 금지하는 방법으로는 두 가지가 있다.
    • final을 붙이는 방법
    • 생성자를 private으로 만들고 public static 팩토리 메서드를 제공하는 방법
      • 이 방법은 특히 내부 패키지에서는 상속 가능, 외부 패키지에서는 상속 불가능으로 만들고 싶을 때 유용한데, 생성자를 package-private으로 만들면 외부 패키지에서는 상속이 불가능하다.

 

아이템 20. 추상 클래스보다는 인터페이스를 우선하라
  • interface가 default method를 지원하면서, abstract class와 interface 중 어떤 것을 사용해야 하는지 모호할 때가 있다.
  • 진짜 is-a 관계가 아니라면, 웬만하면 인터페이스를 써라.
  • abstract의 가장 큰 문제는 다중상속이 불가하다는 점.
  • Singer, Songwriter가 interface라면 둘 모두 받아서 SingerSongwriter를 만들 수 있음. abstract 였다면 이게 불가능하다.
    • "싱어송라이터는 가수다." "싱어송라이터는 작곡가다" is-a 관계처럼 보이기도 하기 때문에 잘 생각해야 한다.
  • interface default method는 한 가지 제약을 가지고 있는데, Object의 메서드들(equals...)은 구현할 수 없다는 것이다.
  • 이 제약 때문에 abstract class와 interface를 같이 쓰기도 한다. 먼저 interface를 만들면서 default로 만들 수 있는건 만들고, Object의 메서드들 같이 처리할 수 없는 것들은 abstract class에 구현한다.
    • 이를 골격 구현 클래스라고 하며 관례적으로 Abstract- prefix를 사용한다. AbstractMap 같은 것.
    • 실제로 interface Map이 있고, 이걸 구현한 AbstractMap이 있다. HashMap은 이를 둘 다 extends, implements한다.

 

아이템 21. 인터페이스는 구현하는 쪽을 생각해 설계하라
아이템 22. 인터페이스는 타입을 정의하는 용도로만 사용하라

 

아이템 23. 태그 달린 클래스보다는 클래스 계층구조를 사용하라
  • 태그 달린 클래스란 한 클래스가 내부 tag 변수(flag 변수 같은 것)에 따라 Square도 됐다가, Circle도 됐다가 하는 것을 말한다.
    • 내부에 tag 상태도 유지해야하고, tag에 따라 분기도 쳐줘야 하고, tag에 따라 쓰는 멤버/안쓰는 멤버가 갈리는 등 여러모로 단점 투성이다.
    • 책에 나온거 보니 이렇게 짜는 사람도 있나보다.
  • 당연히 공통 부모 Figure를 상속하는 Square, Circle로 각각 구현하는게 정상적인 방법이다.

 

아이템 24. 멤버 클래스(nested class)는 되도록 static으로 만들라
  • 자바에서는 static class는 nested class에만 존재할 수 있다(최상위 레벨 class는 이미 static이다)
  • nested class인데 static이 아니면, 외부 클래스의 인스턴스에 대한 숨은 참조를 가지게 된다. 
    • 책에서는 참조를 저장하려면 시간과 공간이 소비된다고는 하는데, 이건 크게 고려할 부분은 아니라고 본다.
    • 문제가 되는건 참조로인해 바깥 클래스 객체가 GC가 되지 않아 memory leak이 발생할 수 있다는 점.
  • 의미론 적으로도, static이 아니라면 내부 클래스에 접근하기 위해서는 일단 Outer 객체를 생성하고 나서 ``java outerObj.InnerCls`` 형태로 접근해야 한다. (static이 아니니까!)
  • 반면 static이면 ``java outerCls.InnerCls`` 형태로 접근 가능하다. 의미 상 내부 클래스가 객체마다 따로 존재할 필요는 없으므로 static을 붙여 주는 것이 좋다.
  • [Coding/Kotlin Java] - [Kotlin/Java] Inner Class / Nested Class

 

아이템 25. 톱레벨 클래스는 한 파일에 하나만 담아라
  • 이름이 같은 클래스가 다른 파일에도 정의되어 있는 경우. 컴파일 순서에 따라 어떤 클래스가 먼저 참조될지가 바뀔 수 있어서 실행 결과가 달라질 수 있다. 어떻게 하면 컴파일 에러; 어떻게 하면 정상 실행 되는 상황이 발생할 수 있음
  • 일반적으로 파일 하나에 클래스 하나로 하는게 관례처럼 되어 있으니 따르는 것이 좋다.