본문으로 건너뛰기

Item15

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

1 개요

  • 잘 설계된 컴포넌트는 클래스 내부 데이터와 내부 구현 정보를 외부 컴포넌트로부터 얼마나 잘 숨겼는가로 정해진다

2 캡슐화(정보 은닉) 장점

시스템 개발 속도 향상

  • 여러 컴포넌트 병렬 개발

시스템 관리 비용 절약

  • 컴포넌트 이해도가 높아져 디버깅이 쉽고 컴포넌트 교체 부담이 적다

성능 최적화 도움

  • 최적화할 컴포넌트를 정해 다른 컴포넌트 영향을 주지않고 해당 컴포넌트만 수정 가능

소프트웨어 재사용성 높임

  • 외부에 거의 의존하지 않고 독자적으로 동작해 다른 환경에서 유용하게 쓰임

큰 시스템을 제작하는 난이도 낮춤

  • 시스템 전체가 개발되지 않아도 개별 컴포넌트의 동작을 검증할 수 있음

3 접근 제한자

  • 접근 제한자는 클래스, 인터페이스, 멤버의 접근성을 명시한다.

  • 멤버에 부여할 수 있는 접근 제한자는 총 4가지 이를 제대로 활용하는 것이 정보 은닉의 핵심이다.

    • private, package-private, proteced, public
  • 모든 클래스와 멤버의 접근성을 가능한 좁혀야 한다.

private

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

package-private

  • 접근 제한자를 명시하지 않은 경우 적용 됨
    • 단 인터페이스는 기본적으로 public
  • 멤버가 소속된 패키지 안의 모든 클래스에서 접근 가능

proteced

  • package-private의 접근 범위 포함
  • 동일 패키지 및 상속받은 하위 클래스에서 접근 가능

public

  • 모든 곳에서 접근 가능

4 기본 원칙

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

4.1 톱레벨 클래스

  • 톱레벨 클래스와 인터페이스에 사용할 수 있는 접근 제한자는 public과 package-private 뿐이다
  • 톱레벨 클래스란 가장 바깥 클래스를 의미한다.

public

  • 공개 API가 된다
  • 하위 호환을 위해 영원히 관리해야 한다

package-private

  • 해당 패키지 안에서만 이용할 수 있다
  • 패키지 외부에서 쓸 이유가 없다면 package-private으로 선언하라
  • 공개 API가 아니기 때문에 언제든 수정 가능하다
  • 클라이언트에 아무런 피해 없이 다음 릴리즈에서 수정, 교체 , 제거 할 수 있다

4.2 한 클래스에서만 사용하는 private-package 클래스

  • 한 클래스에서만 사용하는 private-package 톱레벨 클래스나 인터페이스는 이를 사용하는 클래스 안에 private static으로 중첩하라
  • 이렇게 하면 같은 패키지의 모든 클래스가 접근하는 것에서 바깥 클래스 하나만 접근하는 것으로 접근 범위가 줄어든다.

4.3 public일 필요가 없는 클래스

  • public일 필요가 없는 클래스를 private-package 톱레벨 클래스로 좁히는 일이 중요하다
  • public 클래스는 공개 API private-package 톱레벨 클래스는 내부 구현이기 때문

4.4 멤버 필드

  • 공개 API를 세심히 설계하고 그 외의 모든 멤버는 private으로 만든다
  • 오직 같은 패키지의 다른 클래스가 접근해야하는 멤버에 대하여 package-private를 적용한다
  • 위와 같은 일이 자주 발생하면 시스템에서 컴포넌트를 분리해야 되는지 고민해야한다

5 주의사항

5.1 멤버 접근 수준 package-private ->protected

  • public 클래스에서 멤버의 접근 제한자를 package-private에서 protected로 변경하면 그 멤버에 접근할 수 있는 대상이 넓어진다
  • public 클래스의 protected 멤버는 공개 API라 영원히 관리되어야 하며 내부 동작 방식을 문서화해서 사용자에게 공개해야한다
  • 따라서 protected 멤버는 적을 수록 좋다

5.2 상위 클래스 메소드 재정의

  • 상위 클래스의 메서드를 재정의 할 때 접근 수준을 더 좁게 설정할 수 없다
  • 이 제약은 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체해 사용할 수 있어야한다는 규칙(리스코프 치환 원칙)을 지키기 위해 필요

5.3 테스트 목적의 접근 제한자

  • 테스트만을 위해 클래스, 인터페이스, 멤버를 공개 API로 만들면 안된다
  • 테스트 목적으로 private 멤버를 package-private까지 풀어주는 것은 허용한다.
    • package-private은 내부 구현이다.
    • 내부 구현을 테스트 하는 것이 좋을까?
    • 내부 구현을 테스트하게 되면 내부 구현을 변경할 때 테스트 또한 같이 깨진다.
    • 이를 리팩터링 내성이 부족하다고 한다.
    • 테스트를 위해 접근 범위를 늘리기보다 public api를 테스트하면서 간접적으로 private 멤버도 테스트할 수 있는 코드를 작성하는게 좋을 것

5.4 public 클래스의 인스턴스 필드

  • public클래스의 인스턴스 필드는 상수가 아니라면 되도록 public이 아니여야 한다
  • 필드가 가변 객체를 참조하거나 final이 아닌 인스턴스 필드를 public으로 선언하면 불변식을 보장할 수 없게 된다
    • 해당 필드에 담을 수 있는 값을 제한하지 못하기 때문
  • 필드가 수정될 때 다른 작업을 할 수 없으므로 public 가변 필드를 갖는 클래스는 Thread Safe하지 않다
  • 클래스에 길이가 0이 아닌 public static final배열 필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공하면 안된다

5.5 상수

  • 해당 클래스가 표현하는 추상 개념을 완성하는데 꼭 필요한 구성요소로써 상수라면 public static final 필드로 공개해도 좋다
  • 관례상 이런 상수의 이름은 대문자 알파벳으로 쓰며 단어 사이 _를 넣는다
  • 이런 필드는 반드시 기본 타입이나 불변객체를 참조해야한다

상수로 public static final 배열을 사용하면 안된다

  • 길이가 0이 아닌 배열은 모두 변경 가능하니 주의하라
  • public static final 배열을 두거나 이 필드를 반환하는 접근자 메서드를 제공하면 안된다
    • 클라이언트가 배열의 내용을 수정할 수 있게 된다

참고