Effective Java - Item 15

Updated:

Effective Java

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

정보 은닉 (Encapsulation)

잘 설계된 컴포넌트의 가장 큰 특징은 클래스 내부 데이터와 내부 구현 정보를 외부 컴포넌트로부터 얼마나 잘 숨겼는가이다.
오직 API를 통해서만 다른 컴포넌트와 소통하여 서로의 내부 동작 방식에 영향을 미치지 않는다.
정보은닉, 캡슐화라고 하는 이 개념은 SW 설계의 근간이 되는 원리이다.

정보 은닉의 장점

컴포넌트들을 서로 독립시켜서 개발, 테스트, 최적화, 적용, 분석, 수정을 개별적으로 할 수 있게한다.

  1. 시스템 개발 속도를 높인다 - 병렬로 컴포넌트를 개발
  2. 시스템 관리 비용을 낮춘다 - 컴포넌트 교체 비용 하락
  3. 성능 최적화에 도움을 준다 - 최적화할 컴포넌트만 선택하여 최적화 가능
  4. SW의 재사용성을 높인다 - 의존성이 낮은 컴포넌트면 낯선 환경에서도 동작
  5. 큰 시스템을 제작하는 난이도를 줄여준다 - 시스템 완성 전, 개별 컴포넌트의 동작을 검증 할 수 있다.

정보 은닉의 원칙

기본 원칙: 모든 클래스의 멤버의 접근성을 가능한 한 좁혀야 한다.
접근성 : 접근 제한자(private, protected, public)를 이용하여 접근을 제한한다.

  • public일 필요가 없는 클래스의 접근 수준을 defualt 톱 레벨 클래스로 줄인다.
    public 클래스는 그 패키지의 API로 될 수 있기 때문.
    Member Field의 경우
    1. 최대한 private으로 만든다.
    2. 같은 패키지의 다른 클래스가 접근해야 하는 경우, default으로 풀어준다.
      단, Serializable을 구현하면 필드가 공개 API가 될 수 있음.
    3. protected 멤버의 수는 적을수록 좋다.
      protected로 바꾸는 순간, 그 멤버에 접근할 수 있는 대상 범위가 엄청나게 넓어진다.
      public 클래스의 protected 멤버는 공개 API이므로 영원히 지원되야 함.

접근 제한자 (Access Modifier)

  • private: 멤버를 선언한 톱레벨 클래스에서만 접근할 수 있다.
  • package-private(default): 멤버가 소속된 패키지 안의 모든 클래스에서 접근 할 수 있다.
  • protected: package-private의 접근 범위를 포함하며, 이 멤버를 선언한 클래스의 하위 클래스에서도 접근할 수 있다.
  • public: 모든 곳에서 접근 할 수 있다

Top Level Class

(가장 밖에 존재하는 클래스, 파일명 == 클래스명)톱 레벨 클래스와 인터페이스에는 [public과 default]만 접근 제한자를 부여할 수 있다.
public으로 선언하면 공개 API가 되어, 하위 호환을 위해 이후에도 관리해주어야 한다.
default는 내부 패키지에서만 이용하므로, 이후 릴리즈에도 내용 수정, 교체, 제거할 수 있다.

Inner Class를 사용하여 접근 제한

public class A{
  private int a;
  private List<B> b;
}
public class B{
  private int b;
}
public class A{
  private int a;
  private List<B> b;
  
  private static class B{   //private static으로 중첩
    private int b;
  }
}

톱 레벨로 두어 같은 패키지의 모든 클래스가 접근할 수 있는 것을, private static으로 중첩시켜 바깥 클래스 하나에서만 접근할 수 있게 만든다.

멤버 접근성 제약

상위 클래스의 메서드를 재정의할 때는 접근 수준을 더 좁게 설정할 수 없다.
리스코프 치환원칙(상위클래스 인스턴스는 -> 하위클래스 인스턴스로 대체 가능)을 지키기 위해.
단 인터페이스를 구현하는 경우에는 클래스의 메서드는 모두 public으로 해야 한다.

주의점

  • 코드 테스트를 위해 public 클래스의 private 멤버를 default 까지만 풀어줄수 있다.
    그 이상은, 공개 범위가 너무 넓어진다.

  • public 클래스의 인스턴스 필드는 되도록 public이 아니어야한다.
    가변 객체나, final이 아닌 인스턴스 필드를 public으로 선언하면, 불변식을 보장할 수 없다.
    즉, 다른 객체에서 임의로 값을 변경할 수 있다.

public static class C {
  // 가변 인스턴스
  public int[] arr = {1,2,3,4,5};
  public char ch = 'c';
  // 가변 객체
  public List<Integer> list = new ArrayList<Integer>();
}

public static class D {
  C c = new C();
  
  public void changeC() {
    // 객체 C의 불변성 보장 실패
    c.arr[3] = 6;
    c.ch = 'd';
    c.list.add(11);
  }
}

또한, public 가변 필드를 갖는 클래스는 일반적으로 스레드 안전하지 않다.

필드가 final인 경우, public을 제거하는 방식의 리펙토링이 불가능하므로 내부 구현을 바꿀 수 없다.

  • 단, 상수인 경우 public static final 필드로 공개할 수 있다.
    다만 항상 기본 타입 값이나 불변 객체를 참조해야 한다.

  • public static final 배열 필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공해서는 안 된다.

    public static final Type[] arr = {...};   // 보안 허점이 존재
    

    클라이언트에서 위 배열의 내용을 수정할 수 있게 된다.

이를 막는 방법은 배열을 private로 만들고 그 복사본을 반환하는 메서드를 추가한다. 이를 방어적 복사 라고 한다.

private static final Type[] arr = {...};   
public static final Type[] getArr(){
  return arr.clone();   // 원본 수정 불가
}

모듈 시스템

자바9에서 등장한 암묵적 접근 제한.
모듈 : 패키지의 묶음 || 패키지 : 클래스의 묶음
기능 : 자신이 속하는 패키지 중 공개(export)할 것들만 선택하여 모듈 외부에 공개한다.

따라서 protected 혹은 public 멤버라도 해당 패키지를 공재하지 않았다면 모듈 외부에서 접근할 수 없다.
이 암묵적 접근 수준들은 public, protected 수준과 같으나, 그 효과가 모듈 내부로 한정되는 변종

주의점

모듈의 JAR 파일을 모듈 경로가 아니라, app의 클래스 path에 두면, 모듈안 패키지가 모듈이 아닌 것 처럼 행동한다.
즉, 공개 제한 된 public, protected 변수들이 외부 모듈에서 접근이 가능하게 됨

모듈을 사용하기 위한 절차

  1. 패키지들을 모듈 단위로 묶는다
  2. 모듈 선언에 패키지들의 모든 의존성을 명시
  3. 소스트리 재배치
  4. 모듈 시스템을 적용하지 않은 (외부로 공개된) 패키지들의 접근에 조치를 취한다

정리

프로그램 요소의 접근성은 가능한 한 최소한으로 하라.
꼭 필요한 것만 골라 최소한의 public API를 설계한다. 그 외에는 클래스, 인터페이스, 멤버가 의도치 않게 API로 공개되는 일이 없도록 해야 한다.
public 클래스에서는 상수용 public static final 필드 외에는 어떠한 public 필드도 가져서는 안된다.
public static final 필드가 참조하는 객체가 불변인지 확인하라.

출처

Joshua Bloch. Effective Java 3/E. n.p.: 인사이트, 2018년 11월 1일.

Reference

https://jyami.tistory.com/77
https://jaehun2841.github.io/2019/01/19/effective-java-item15/#%EB%A9%94%EC%84%9C%EB%93%9C%EB%A5%BC-%EC%9E%AC%EC%A0%95%EC%9D%98-%ED%95%A0-%EA%B2%BD%EC%9A%B0%EC%97%90%EB%8A%94-%EC%A0%91%EA%B7%BC-%EC%88%98%EC%A4%80%EC%9D%84-%EC%83%81%EC%9C%84-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%97%90%EC%84%9C%EB%B3%B4%EB%8B%A4-%EC%A2%81%EA%B2%8C-%EC%84%A4%EC%A0%95-%ED%95%A0-%EC%88%98-%EC%97%86%EB%8B%A4
https://gyrfalcon.tistory.com/entry/JAVA-%EC%A0%91%EA%B7%BC-%EC%A0%9C%ED%95%9C%EC%9E%90