Prodo 독서 리뷰

[Effective Java] Item 15. 클래스와 멤버의 접근 권한을 최소화하라

prodo-developer 2021. 4. 11. 18:28

1. 캡슐화

컴포넌트는 모든 내부 구현을 숨김으로써 구현과 API를 분리한다. 오직 API를 통해 다른 컴포넌트와 소통할 뿐, 서로의 내부 동작 방식은 신경쓰지 않는다.

 

컴포넌트들을 독립시키는 캡슐화의 장점은 다음과 같다.

 

  • 시스템 개발 속도를 높인다.
    • 여러 컴포넌트를 병렬로 개발할 수 있다.
  • 시스템 관리 비용을 낮춘다.
    • 각 컴포넌트를 빨리 파악하여 디버깅이 쉽고, 컴포넌트 교체 부담이 적다.
  • 성능 최적화에 기여한다.
    • 다른 컴포넌트에 영향을 주지 않고 특정 컴포넌트를 최적화할 수 있다.
  • 소프트웨어 재사용성을 높인다.
    • 독자적으로 동작하는 컴포넌트는 낯선 환경에서도 유용하게 쓰일 가능성이 높다.
  • 큰 시스템을 제작하는 난이도를 낮춘다.
    • 시스템 전체가 완성되지 않아도 개별 컴포넌트의 동작을 테스트할 수 있다.

 

2. 접근 제한자

모든 클래스와 멤버의 접근성을 가능한 좁혀야 한다. 멤버 클래스가 아닌 일반 클래스는 아래의 접근 제한자들 중 public과 package-private(default)만 사용 가능하다.

 

  • private : 멤버를 선언한 톱 레벨 클래스에서만 접근할 수 있다.
  • package-private(default) : 멤버가 소속된 패키지 안의 모든 클래스에서 접근할 수 있다.
    • 접근 제한자를 명시하지 않을 때 자동 적용되지만, 인터페이스의 멤버는 기본적으로 public이다.
  • protected : package-private 접근 범위를 포함하며, 해당 멤버를 선언한 클래스를 상속한 다른 패키지의 하위 클래스에서도 접근이 가능하다.
  • public : 모든 패키지에서 접근 가능하다.

2.1. API

톱 레벨 클래스나 인터페이스를 public으로 공개하면 API가 되기 때문에 하위 호환을 위해 계속 관리해야 한다. package-private으로 선언하면 API가 아닌 내부 구현이 되어 언제든 수정할 수 있기 때문에, 외부 패키지에 공개할 필요가 없다면 package-private을 사용한다.

2.2. Inner Class

한 클래스에서만 사용하는 package-private 톱 레벨 클래스 혹은 인터페이스는 사용하는 클래스 내부에 private static Inner Class로 중첩할 수 있다. Outer Class를 제외하면 해당 클래스에 접근할 수 없게 된다.

2.3. 멤버

클래스의 공개 API를 설계하고 그 외의 모든 멤버는 private으로 선언한다. 패키지의 다른 클래스가 접근해야 하는 멤버에 한해 package-private으로 풀어준다. protected는 공개 API로서 영원히 지원되어야 하고 문서에 공개해야 할 수도 있기 때문에 적을 수록 좋다.

 

3. 제약

상위 클래스의 메서드를 오버라이드할 때 접근 수준을 상위 클래스보다 더 좁게 설정할 수 없다. 이는 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체될 수 있다는 리스코프 치환 원칙을 지키기 위함이다.

 

4. public 관련 고려사항

public 클래스의 인스턴스 필드는 가급적 public이 아니어야 한다.

  • final이 아닌 필드 및 final이더라도 가변 객체를 참조하는 필드 등이 public이면 외부에서 수정이 가능하기 때문이다.
    • 이는 필드와 관련된 불변식이 깨지고, 스레드에도 안전하지 못하다.

다만 해당 클래스가 표현하는 추상 개념을 완성하는데 꼭 필요한 구성 요소로써의 상수는 public static final로 공개해도 좋다. 이런 필드는 반드시 불변 객체를 참조해야 한다. 다른 객체를 참조하지 못하더라도, 참조하는 객체 자체가 변경될 수 있기 때문이다.

Test.java

public static final Thing[] PRIVATE_VALUES = { ... };

 

배열은 final이더라도 수정이 가능하다. 따라서 해당 필드에 직접 접근할 수 있는 API를 공개하면 안 된다.

 

Test.java

private static final Thing[] PRIVATE_VALUES = { ... };

public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

// 혹은

public static final Thing[] values() {
     return PRIVATE_VALUES.clone();
}

 

외부에서 변경을 가할 수 있는 필드는 불변 컬렉션으로 제공하거나 방어적 복사본을 제공하는 API를 사용하도록 한다.

 

5. 모듈

Java 9 부터는 모듈의 개념이 도입되었다. 모듈이란 패키지의 묶음으로서 자신에 속한 패키지 중 공개할 것을 지정한다.

 

  • protected 혹은 public 멤버라도 해당 패키지를 공개하지 않았다면 모듈 외부에서 접근할 수 없다.
    • protected와 public의 효력이 모듈 내부로 제한된다.
  • JAR 파일을 자신의 모듈 경로가 아닌 클래스패스에 위치시키면 모듈의 효과가 사라진다.
    • 패키지 공개 여부에 상관없이, 모듈 외부에서 모듈 내부 패키지의 public 및 protected 멤버에 접근 가능해진다.

요약

프로그램 요소의 접근성은 가능한 한 최소한으로 하라. 꼭 필요한 것만 골라 최소한의 public API를 설계하자.

 

그 외에는 클래스, 인터페이스, 멤버가 의도치 않게 API로 공개 되는일이 없도록 해야 한다. 

 

public static final 필드 외에는 어떠한 public 필드도 가져서는 안된다.

 

public static final 필드가 참조하는 객체가 불변인지 확인하라.