클래스와 멤버의 접근 권한을 최소화하라
잘 설계된 컴포넌트는 내부 데이터와 내부 구현 정보를 외부 컴포넌트로부터 잘 숨긴 컴포넌트이다. 모든 내부 구현을 완벽히 숨기고, 구현과 API를 깔끔히 분리하는 것이 중요하다. 이를 정보 은닉 혹은 캡슐화라고 한다.
정보 은닉의 장점
- 여러 컴포넌트를 병렬로 개발할 수 있기 때문에 시스템 개발 속도를 높인다.
- 디버깅과 교체에 용이하여 시스템 관리 비용을 낮춘다.
- 다른 컴포넌트에 영향을 주지 않기 때문에 제작 난이도를 낮추고 성능 최적화와 SW 재사용에 도움을 준다.
접근 제어자
자바에서는 정보 은닉을 위해 접근을 제어하는 메커니즘을 제공한다.
public
: 모든 곳에 접근할 수 있다. (공개 API - 영원한 관리 필요)protected
: package-private의 범위를 포함하며, 이 멤버를 선언한 클래스의 하위 클래스에서도 접근이 가능하다.package-private
: default 접근 제어자며, 같은 패키지 안에서만 접근할 수 있다. 단, 인터페이스의 멤버는 기본적으로 public이 적용된다.private
: 멤버를 선언한 현재 클래스에서만 접근할 수 있다.
컴포넌트 설계
컴포넌트를 설계할 때는 public(공개 API)을 세심히 설계한 후, 그 외의 모든 멤버는 private으로 만드는 것이다. 즉, 꼭 필요한 API가 아니라면 접근성을 최대한 좁혀서 가장 낮은 접근 지정자 수준을 부여해야 한다.
public 클래스의 관점에서 멤버의 접근 수준을 package-private에서 protected로 바꾸는 순간 그 멤버에 접근할 수 있는 대상 범위가 넓어진다. 즉, protected 멤버는 공개 API(동일 패키지)가 되므로 영원히 지원돼야 한다. 따라서 protected 멤버의 수는 적을수록 좋다.
멤버의 접근성을 좁히면서 주의할 점은 상위 클래스의 메서드를 재정의할 때는 그 접근 수준을 상위 클래스에서보다 좁게 설정할 수 없다는 것이다. 해당 규칙을 어길 경우 컴파일 오류가 발생한다. SOLID 5원칙의 L(리스코프 치환 원칙 - 상위 클래스는 하위 클래스의 인스턴스로 대체할 수 있어야한다.)이 해당 의미이다.
public 클래스 주의점
public 클래스의 인스턴스 필드들은 되도록 public이 아니어야 한다. 필드가 가변 객체를 참조하거나, final이 아닌 인스턴스 필드를 public으로 선언하면 외부에서 접근이 가능하므로 그 필드에 담을 수 있는 값을 제한할 힘을 잃게 된다. 또한, 자연스럽게 스레드 세이프하지 않게 된다.
예외적으로 public static final
필드를 통해 상수를 표현하는 경우는 대문자 네이밍의 관례를 기반으로 public 메서드로 사용한다. (단, 추상 개념을 완성하는 데 꼭 필요한 구성 요소의 상수인 경우)
// 방법 1 : 문제 발생 가능성 존재
public static final Thing[] VALUES = {...};
// 방법 2 : private 배열로 생성 후 , public 불변 리스트 추가
private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
// 방법 3 : private 배열로 생성 후, 복사본을 반환
private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
}
- 클라이언트가 무엇을 원하는 지 여부에 따라 방법 2 나 3 중에 선택하여 사용하자.
정리
자바 9 버전부터는 모듈 시스템을 통해 패키지를 묶을 수 있는 기능도 있다. JDK 외에도 모듈 개념이 널리 받아들여질지 예측하기는 쉽지 않을 것이다. (자바 8 버전 쓰는 회사도 많기 때문)
프로그램을 설계할 때 최소한의 public API를 설계하고, 그 외에 의도치 않게 공개되는 일이 없도록 하자.
public 클래스는 public static final 필드를 제외한 어떠한 필드도 public 필드를 갖지 않도록 하자. 또한 public static final 필드가 참조하는 객체가 불변인지 확인하자.