아이템 15. 클래스와 멤버의 접근 권한을 최소화하라

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

 

아이템 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

  • package-private 클래스 혹은 private 중첩 클래스라면 필드가 private이 아니어도 괜찮다.

 

아이템 17. 변경 가능성을 최소화하라

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

 

굳이 가변 동반 클래스를 쓰기 보다는 getter만 외부 제공하고 setter는 제공하지 않는 식으로 가변성을 클래스 내부로 제한하는 방식을 많이 사용하게 된다.

[리팩터링 2판] 3장 Bad Smells in Code

 

아이템 18. (기능 확장이 필요할 때)상속보다는 컴포지션을 사용하라

Composition VS Extends : 상속 보다는 컴포지션을 사용하라

 

아이템 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으로 만들라

```java

// InnerCls is static

OuterCls.InnerCls innerObj = new OuterCls.InnerCls("i");


// InnerCls is not static

OuterCls.InnerCls innerObj = new OuterCls("o").new InnerCls("i");

```

  • 문제점 1. static이 아니라면 내부 클래스를 생성하기 위해서는 일단 Outer 객체를 생성하고 나서 ``java outerObj.InnerCls`` 형태로 접근해야 한다. (static이 아니니까!)
    • 내부 클래스 객체를 생성하기 위해서 외부 클래스 객체를 먼저 만들어야만 한다.
    • 반면 static이면 ``java OuterCls.InnerCls`` 형태로 접근, 생성하기 때문에 외부 클래스 객체를 먼저 생성할 필요가 없다.
  • 문제점 2. nested class인데 static이 아니면, 외부 클래스의 인스턴스에 대한 숨은 참조를 가지게 된다. 
    • 책에서는 참조를 저장하려면 시간과 공간이 소비된다고는 하는데, 이건 크게 고려할 부분은 아닌 것 같다.
    • 참조로 인해 바깥 클래스 객체가 GC가 되지 않아 memory leak이 발생할 수 있다는 점은 문제가 될 수 있다.
  • [Coding/Kotlin Java] - [Kotlin/Java] Inner Class / Nested Class  
  • 참고) 자바에서는 static 키워드붙은 class는 nested class에만 존재할 수 있다(최상위 레벨 class는 이미 static이다)

 

 

 

아이템 25. 톱레벨 클래스는 한 파일에 하나만 담아라

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