[Java] Item 15 : 클래스와 멤버의 접근 권한을 최소화해라
Effective Java를 읽고 정리한 정리본입니다.
📌 Item 15 : 클래스와 멤버의 접근 권한을 최소화해라
🫧 정보 은닉 (캡슐화)
잘 설계된 컴포넌트는 클래스 내부 데이터와 내부 구현 정보를 외부 컴포넌트로부터 얼마나 잘 숨겼느냐이다. 이것은 정보 은닉, 혹은 캡슐화라고 부르며, 자바에서는 그 요소가 선언된 위치와 접근 제한자로 구현이 가능하다.
🫧 목적에 따른 접근 제한자
가장 바깥 레벨이라는 의미의 톱레벨 클래스와 인터페이스에 부여할 수 있는 접근 수준은 package-private과 public이 있다.
-
package-private : 해당 패키지 내부에서만 이용이 가능하다. 패키지 외부에서 쓸 일이 없으며, API가 아닌 내부 구현이 목적인 경우 사용한다.
-
public : API의 목적을 가진 클래스의 접근 제어자. 하위 호환을 위해 관리가 필요하다.
여기서 우리는 주제에 대해 다시 논의하려 한다. public으로 선언한 경우 API가 되며, 평생 하위 호환을 위해 관리를 해 줘야 한다.
처음부터 API로 만드려는 목적을 가졌다면 상관이 없겠지만, 그렇지 않은 경우 문제가 발생하곤 한다. 따라, public일 필요가 없는 클래스의 접근 수준을 package-private 톱레벨 클래스로 줄여야 한다.
🫧 멤버에 부여할 수 있는 접근 수준
여기서 멤버는 필드, 메서드, 중첩 클래스, 중첩 인터페이스를 말한다.
- private : 멤버를 선언한 톱레벨 클래스 안에서만 접근이 가능하다.
- package-private : 멤버가 소속된 패키지 안의 모든 클래스에서 접근할 수 있다. 접근 제한자를 명시하지 않았을 때 적용되는 패키지 접근 수준이다. (인터페이스의 멤버는 기본적으로 public 적용)
- protected : package-private의 접근 범위를 포함하며, 이 멤버를 선언한 클래스 하위 클래스에서도 접근할 수 있다.
- public : 모든 곳에서 접근할 수 있다.
쉽게 말해 private은 클래스 내부에서만, package-private는 같은 패키지 내에서, protected는 같은 패키지 + 자식 클래스, public은 모든 곳에서 접근이 가능하다.
공개 API를 만들더라도, 접근 범위를 최소화시켜 (privata) 문제 상황을 사전에 방지해야 한다.
🫧 공개 API에서 접근 권한 최소화 방법
- 클래스의 공개 API를 세심히 설계한 후, 그 외의 모든 멤버는 private으로 만든다.
- 같은 패키지의 다른 클래스가 접근해야 하는 멤버에 한하여 package-private으로 풀어준다.
만약 2번의 과정이 반복된다면, 컴포넌트를 더 분해해야 하는 건 아닌지 다시 한 번 고민해 보도록 하자.
이렇게 만든다면 private과 package-private 멤버는 모두 해당 클래스의 구현에 해당하므로 공개 API에 영향을 끼치지 않게 되어 좋은 설계라고 할 수 있다.
🫧 멤버의 접근성을 좁히지 못하는 까닭
상위 클래스의 메서드를 재정의할 때 그 접근 수준을 상위 클래스에서보다 좁게 설정할 수 없다.
이 문제는 리스코프 치환 원칙에 의해 발생한다.
💡 리스코프 치환 원칙: 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체해 사용할 수 있어야 한다.
따라서 클래스가 인터페이스를 구현할 때는 인터페이스가 정의한 모든 메서드를 public으로 선언해야 한다.
🫧 접근 최소화를 위한 몇 가지 주의 사항
-
public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다.
-> 필드를 제한할 수 없을 뿐더러, 필드와 관련된 모든 것은 불변식을 보장할 수 없게 된다. 뿐안 아니라, public 가변 필드를 갖는 클래스는 일반적으로 스레드 안전하지 않기 때문이다. - 해당 클래스가 표현하는 추상 개념을 완성하는 데 꼭 필요한 구성요소로써의 상수라면 public static final 필드로 공개해도 좋다.
- 길이가 0이 아닌 배열은 모두 변경 가능하므로 클래스에서 public static final 배열 필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공해서는 안 된다.
✨ 배열 관련 문제 해결 방법
길이가 0이 아닌 배열이 클라이언트에게 수정되지 않게 하려면 어떻게 해야 할까?
public static final Thing[] VALUES = {...};
이런 코드로 둔다면 접근 최소화를 위한 몇 가지 주의사항
에서의 3번에 어긋난다. 클라이언트에서 그 배열의 내용을 수정할 수 있게 되는 것이다.
방법은 크게 두 가지이다.
- 앞 코드의 public 배열을 private으로 만들고 public 불변 리스트를 추가한다.
- 배열을 private으로 만들고 그 복사본을 반환하는 public 메서드를 추가한다.
위 방법을 이용한 예시 코드를 살펴보자.
✨ 1번 방법 예시 코드
private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES =
Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
✨ 2번 방법 예시 코드
private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
}