클래스와 멤버의 접근 권한을 최소화하라
정보 은닉의 장점
- 여러 컴포넌트를 병렬로 개발 가능 == 시스템 개발 속도 향상
- 컴포넌트가 작게 나눠져 있기 때문에 디버깅도 쉽고 교체도 쉬움 == 시스템 관리 비용 낮춤
- 컴포넌트간 결합도가 낮기 때문에 특정 컴포넌트를 최적화하기 쉬움 == 성능 최적화에 도움
- 외부 의존성이 적은 컴포넌트 == 소프트웨어 재사용성 향상
- 단위 테스트가 쉬워짐 == 큰 시스템을 제작하는 난이도 하락
정보 은닉 기본원칙
- private - 멤버를 선언한 톱레벨 클래스에서만 접근 가능.
- package-private - 해당 패키지 안의 모든 클래스에서 접근 가능.
(default 접근 수준, 인터페이스는 public이 default)
- protected - private-package의 범위를 포함하고, 해당 클래스의 하위 클래스에서도 접근 가능.
- public - 모든 곳에서 접근 가능.
모든 클래스와 멤버의 접근성을 가능한 한 좁힌다 (public 지양)
접근성 : 요소가 선언된 위치 + 접근 제한자 (private, protected, public)
그냥 모든 클래스와 멤버의 접근성을 가능한 한 최대로 좁힌다.
항상 제일 낮은 수준의 접근성을 부여해야 한다는 것이다.
소프트웨어가 동작하기만 하면 된다.
톱레벨 클래스와 인터페이스
public으로 선언할 경우
- 클래스가 공개 API가 됨.
- 클라이언트와의 호환을 위해 릴리즈마다 계속해서 관리를 해줘야 함.
package-private 으로 선언할 경우
- 클래스가 내부 구현이 됨.
- 릴리즈마다 클라이언트에 상관없이 언제든 수정, 교체, 제거가 가능.
API로 쓸거면 public, 그게 아니면 package-private로 선언하면 된다.
한 클래스에서만 사용하는 package-private 클래스나 인터페이스의 경우
패키지 내에서 클래스나 인터페이스의 사용처가 단 한곳일 때는, 그 사용처에서 private static으로 구현한다.
멤버 (필드, 메서드, 중첩 클래스, 중첩 인터페이스)
멤버에 부여할 수 있는 접근 수준은 private, package-private, protected, public로 총 네가지이다.
멤버는 접근 수준 종류가 많아 구별하기 어려워 보일수 있지만 단순하다.
- 일단 모든 멤버를 private으로 만든다.
- 오직 같은 패키지의 다른 클래스가 접근해야 하는 멤버에 한해 package-private으로 풀어준다.
만약 위와 같은 과정에서 권한을 풀어주는 일이 많다면, 컴포넌트를 더 분해해야 하는 것이 아닌가 고민해봐야 한다.
protected는 적을수록 좋다.
protected는 package-private에 비해 접근 가능 범위가 엄청나게 넓다.
public 클래스의 protected 멤버는 공개 API이기 때문에 영원히 지원되어야 한다.
그래서 protected는 적을수록 좋다.
상위 클래스를 재정의하는 경우
앞에서 멤버의 접근성을 최대한 작게 하라고 말한 바가 있다.
그런데 인터페이스를 구현하는 클래스의 경우엔 불가능하다.
상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체하여 사용할 수 있어야 한다. (리스코프 치환 규칙)
실제로 이를 어기면 하위 클래스를 컴파일 할 때 오류가 난다.
그렇기에 하위 클래스는 상위 클래스에서보다 접근 수준을 좁게 설정할 수 없다.
클래스가 인터페이스를 구현할 경우, 인터페이스가 정의한 모든 메서드들을 public으로 선언해야만 한다.
코드를 테스트 해야할 경우
코드를 테스트 하기 위해 클래스, 인터페이스, 멤버의 접근 범위를 넓히려 할 때가 있다.
하지만 애초에 테스트 코드를 대상과 같은 패키지에 두면 굳이 그럴 이유가 없어진다. (package-private에 접근이 가능하므로)
public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다.
만약 가변 객체를 참조하거나, final이 아닌 인스턴스를 public으로 선언하면, 그 필드에 담을 수 있는 값을 제한할 힘을 잃게 된다.
그 필드와 관련된 모든 것은 불변식을 보장할 수 없다는 의미이다.
또한, 필드가 수정될 경우 다른 작업을 할 수 없게 되므로 public 가변 필드를 갖는 클래스는 일반적으로 스레드가 안전하지 않다.
문제는 필드를 final로 바꾸고 불변 객체를 참조하더라도 public이면 여전히 문제가 있다는 것이다.
정적 필드의 경우
정적 필드는 앞서 인스턴스 필드에서 말했던 것과 같은 문제점을 가지게 된다.
다만 예외가 하나 있다.
해당 클래스가 표현하는 추상 개념을 완성하는 데 꼭 필요한 구성요소이고,
상수라면 public static final 정도로 공개해도 된다.
이런 필드는 반드시 기본 타입 값이나 불변 객체를 참조해야 한다.
만약 가변 객체를 참조한다면 앞서 말한 불이익이 모두 다시 적용된다.
길이가 0이 아닌 배열
길이가 0이 아닌 배열은 모두 변경 가능하니 주의해야 한다.
따라서 클래스에서 public static final 배열 필드를 두거나, 이 필드를 반환하는 접근자 메서드를 제공해서는 안 된다.
이를 어기면, 클라이언트에서 그 배열의 내용을 수정할 수 있게 된다.
안 좋은 예시
public static final Thing[] VALUES = { ... };
해결 방법 1
private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES =
Collections.unmodifiableList(Array.asList(PRIVATE_VALUES);
위처럼 public 이였던 배열을 private로 변경한다.
그리고 public 불변 리스트를 추가한다.
해결 방법 2
private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
마찬가지로 public이였던 배열을 private으로 변경한다.
그리고 그 복사본을 반환하는 public 메서드를 하나 추가해준다. (방어적 복사)
클라이언트가 무엇을 원하냐에 따라 둘 중 하나를 선택해주면 된다.
암묵적 접근 수준
자바 9에서부터는 모듈 시스템이라는 개념이 도입되었다.
이때 두 가지의 암묵적 접근 수준이 추가되었다.
패키지가 클래스들의 묶음이라면, 모듈은 패키지들의 묶음이다.
모듈은 그 안에 속하는 패키지들 중, 공개할 것들을 명시한다. (module-info.java)
이때 공개하지 않은 패키지들은 public이던 protected던 외부에서 접근할 수가 없다.
이를 이용하여 클래스를 외부에 공개하지 않으면서 같은 모듈안에 패키지들 끼리는 자유롭게 공유가 가능하다.
암묵적 접근 수준이란, public이나 protected이지만 그것이 모듈 내로 한정되는 것을 말한다.
하지만 보통 이런 형태가 필요한 경우는 흔하지 않고,
그런 경우가 있더라도 패키지들 사이에서 클래스를 재배치하면 해결된다.
새롭게 등장한 모듈의 접근 수준을 적극 활용한 예시가 바로 JDK 그 자체이다.
자바 라이브러리에서 공개하지 않은 패키지들은 해당 모듈 밖에서는 절대로 접근할 수가 없다.
접근 보호 방식이 추가된 것 외에도 모듈 자체를 제대로 사용하려면 할 일이 많다.
결론은, 아직 모듈 자체를 사용하기에는 조금 이른 감이 있으므로 당분간은 사용하지 않는게 좋다.
Item15 정리
- 프로그램 요소의 접근성은 가능한 한 최소한으로 하라.
- 꼭 필요한 것만 골라 public API로 설계하자.
- public 클래스는 상수용 public static final 필드 외에는 어떠한 public 필드도 가지면 안된다.
- public static final 필드가 참조하는 객체가 불변인지 확인하자.