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

# 잘 설계된 컴포넌트의 특징

정보 은닉 혹은 캡슐화

  • 클래스 내부 데이터/구현을 완벽히 숨긴다

  • 모든 클래스의 멤버의 가장 낮은 접근 수준 부
    → 구현과 API 의 분리

  • 오직 API 를 통해서만 다른컴포넌트와 소통하며 서로의 내부 동작 방식에는 신경쓰지 않음

# 정보 은닉의 장점

시스템을 구성하는 컴포넌트들을 서로 독립시켜서

  • 개발, 테스트, 최적화, 적용, 분석, 수정을 개별적으로 할 수 있게 해준다.
  1. 시스템 개발속도 향상
    • 여러 컴포넌트를 병렬개발
  2. 시스템 관리비용 절감
    • 각 컴포넌트를 더 빨리 파악하여 디버깅 할수 있음
    • 다른 컴포넌트로 교체비용이 더 적다
  3. 성능 최적화에 도움
    • 다른 컴포넌트에 영향을 주지 않고 해당 컴포넌트만 최적화 할 수 있기 때문
  4. 소프트웨어 재사용성
    • 외부에 의존하지 않고, 독자적으로 동작하는 컴포넌트는 낯선 환경에서도 유용하게 쓰임
  5. 큰 시스템을 제작하는 난이도를 낮춤
    • 시스템 전체가 완성되지 않아도 개별 컴포넌트 동작을 검증할 수 있기 때문

# 자바가 제공하는 정보 은닉의 장치

접근제어 매커니즘

  • 클래스, 인터페이스, 멤버의 접근성을 명시
  • 접근성 결정 요소
    • 요소가 선언된 위치
    • 접근 제한자 (private, protected, public)
      • 정보 은닉 핵심

# topLevel 클래스, 인터페이스

public 일 필요가 없는 클래스의 접근 수준을 package-private 톱 레벨 클래스로 좁히자

# package-private

  • 해당 패키지 안에서만 이용가능
  • API 가 아닌 내부 구현이 되어 언제든 수정 가능
  • topLevel 클래스인 이유
    • 클래스 안에 private static 으로 중첩시키면, 바깥 클래스에서 접근할 수 있게 된다.

# public

  • 공개 API
  • 하위 호환을 위해 영원히 관리 해야 함.

# 멤버 접근 수준

멤버

  • 필드, 메서드, 중첩클래스, 중첩인터페이스

# private

멤버를 선언한 톱 레벨 클래스에서만 접근 가능

# package-private

멤버가 소속된 패키지 안의 모든 클래스에서 접근 가능

  • 접근 제한자를 명시하지 않았을 때 default 접근 수준
  • 단 인터페이스의 멤버는 public 이 default 임

# protected

package-private 의 접근 범위를 포함하며, 이 멤버를 선언한 클래스의 하위클래스에서 접근 가능

# public

모든 곳에서 접근 가능

  • 클래스의 공개 API 를 세심히 설계한다
  • 그 외 모든 멤버는 private 으로 만든다
  • 같은 패키지의 다른클래스가 접근해야 하는 멤버에 한하여 package-private 을 쓴다.
  • 권한을 풀어주는 일을 자주 하게 된다면 컴포넌트를 더 분해해야 하는 것은 아닌지 다시 고민한다

# private / package-private 멤버

  • 모두 해당 클래스의 구현에 해당하므로 보통은 API 에 영향을 주지 않는다.
  • Serializable 을 구현한 클래스에서는 의도치 않게 공개 API 가 될 수 있다.

# public 클래스의 protected

  • 공개 API, 영원히 지원되야 한다.
  • 내부 동작 방식을 API 문서에 적어 사용자에게 공개해야 한다.[19]
  • 적을수록 좋다.

# 멤버 접근성을 최소화를 방해하는 제약

상위 클래스의 메서드를 재정의 할 때, 접근수준을 더 좁게 할 수 없다.

  • 리스코프치환원칙
    • 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체해 사용할 수 있어야 한다

# 테스트 목적의 접근 범위

적당한 수준까지는 넓혀도 괜찮다

  • public 클래스의 멤버 private → package-private 까지만 허용
    • 테스트 코드를 테스트 대상과 같은 패키지에 두면 package-private 요소에 접근 가능
  • 테스트만을 위해 클래스, 인터페이스, 멤버를 공개 API 로 만들어서는 안된다.

# public 클래스의 인스턴스 필드는 되도록 public 이 아니어야 한다.[16]

다음과 같은 조건에, 불변식을 보장할 수 없다. (Thread safe 하지 않다)

  • 필드가 가변 객체를 참조할 때
  • final 이 아닌 인스턴스 필드를 public 으로 선언할 때

상수

  • public static final 필드로 공개해도 좋다.
  • 대문자알파벳, _으로 단어 구분[68]
  • 기본 타입 값이나, 불변 객체를 참조해야함[17]

# 길이가 0 이 아닌 배열은 모두 변경 가능함

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

해결책 : 클라이언트가 원하는 방향으로 둘 중 하나를 사용한다.

  1. 배열을 private 으로 만든다 → public 불변 리스트를 추가 한다.
private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES = 
    Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
1
2
3
  1. 배열을 private 으로 만든다 → 복사본을 반환하는 public 메서드를 추가한다 (방어적 복사)
private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {
    return PRIVATE_VALUES().clone();
}
1
2
3
4

# 자바9 모듈 시스템 도입

  • 패키지들의 묶음
  • 패키지 중 export (공개) 할 것들을 (관례상 module-info.java 파일에) 선언함.
  • protected / public 멤버라도 export 하지 않으면 모듈 외부에서는 접근 불가
  • 클래스를 외부에 공개하지 않으면서도 같은 모듈을 이루는 패키지 사이에서는 자유롭게 공개 가능
  • 모듈안의 public 클래스의 public/protected 형태로 사용하는 것은 흔치는 않다.
    • 패키지들 사이에서 클래스의 재배치로 해결가능하다

# 모듈의 접근 수준은 주의해서 사용해야 함

모듈의 JAR 파일을 자신의 모듈경로가 아닌 애플리케이션의 classpath 에 두면 ?? 그 모듈 안의 모든 패키지는 마치 모듈이 없는 것처럼 행동함.

  • 모듈의 export 여부와 상관없이, public 클래스가 선언한 모든 public 혹은 protected 멤버를 모듈 밖에서도 접근 가능

# 모듈의 장점을 사용하기 위해 할 일

  1. 패키지를 모듈 단위로 묶는다
  2. 모듈 선언에 패키지들의 모든 의존성을 명시한다
  3. 소스 트리를 재배치 한다
  4. 모듈 안으로 부터 일반 패키지로의 모든 접근에 특별한 조치를 해야 한다.

# JDK 는 모듈의 접근 수준을 적극적으로 활용한 대표적인 예

  • 자바 라이브러리에서 공개하지 않은 패키지들은 해당 모듈 밖에서 절대로 접근 할 수 없다.

그러나 JDK 외에도 모듈 개념이 널리 받아들여질지 예측하기는 아직 이르다. 꼭 필요한 경우가 아니라면 당분간은 사용하지 않는 것이 좋다.