[EFFECTIVE JAVA] ITEM 15, 클래스와 멤버의 접근 권한을 최소화하라

Posted by iheese on June 21, 2023 · 6 mins read

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


정보 은닉, 캡슐화

  • 어설프게 설계된 컴포넌트와 잘 설계된 컴포넌트의 큰 차이 : 클래스 내부 데이터와 내 구현 정보를 외부 컴포넌트로부터 얼마나 잘 숨겼는지
  • 내부 구현을 완벽히 숨겨, 구현과 API를 깔끔하게 분리한다.


정보 은닉의 장점

  • 시스템 개발 속도를 높인다.
    • 여러 컴포넌트를 병렬로 개발할 수 있기 때문이다. (연관되지 않고 개별적으로 개발이 가능하다?)
  • 시스템 관리 비용을 낮춘다.
    • 각 컴포넌트의 파악이 빨라져 디버깅 용이, 다른 컴포넌트로 교체 용이
  • 성능 최적화에 도움이 된다.
    • 서로 의존성이 낮기 때문에 해당 컴포넌트 내에서만 최적화를 진행하면 되기 때문이다.
  • 소프트웨어 재사용성이 높아진다.
    • 독자적으로 동작할 수 있는 컴포넌트라면 그 컴포넌트와 함께 개발되지 않은 낯선 환경에서도 유용하게 쓰일 수 있다.
  • 큰 시스템을 제작하는 난이도를 낮춰준다.
    • 시스템 전체를 완성하지 않아도 개별 컴포넌트의 동작을 확인할 수 있기 때문이다.


정보 은닉의 기본 원칙

  • 모든 클래스와 멤버의 접근성을 가능한 좁혀야 한다.
  • 접근성 : 그 요소가 선언된 위치 + 접근 제한자
    • 소프트웨어가 동작하는 한 항상 가장 낮은 접근 수준을 부여해야 한다.


접근 수준

  • 가장 바깥을 의미하는 톱레벨 클래스와 인터페이스에 부여할 수 있는 접근 수준은 package-private(default), public이다.
  • 톱레벨 클래스, 인터페이스를 public 으로 부여하면 공개 API가 된다.
  • 톱레벨 클래스, 인터페이스를 package-private 으로 부여하면 패키지 내부에서만 사용이 가능하다.
    • private으로 구현이 되었다면 클라이언트에 피해없이 내부 구현을 수정할 수 있고 public으로 구현하게 되면 하위 호환을 위해 영원히 관리해주어야 한다.


private static 중첩 클래스
  • 한 클래스에서만 사용하는 package-private 톱레벨 클래스나 인터페이스는 이를 사용하는 클래스 안에 private static으로 중첩시키자
    • ITEM24: 멤버 클래스는 되도록 static으로 만들어라


자바에서 사용되는 멤버 필드의 접근 제어자

  • private : 멤버를 선언한 해당 클래스 내에서만 접근 가능
  • package-private(default) : 멤버가 소속된 같은 패키지 내에서만 접근 가능
    • 인터페이스의 멤버는 기본적으로 public 이 적용된다.
  • protected : 상속한 클래스까지 접근 가능, 다른 패키지여도 자식 클래스면 접근 가능하다.
  • public : 전체 영역에서 접근 가능


1 . 클래스의 공개 API 설계 후, 그 외 모든 멤버는 private으로 만들자 2 . 같은 패키지의 다른 클래스가 접근이 필요한 멤버에 한해서 package-private으로 접근 수준 풀기 3 . 권한 풀기가 잦아지면 컴포넌트 분해 고려해보기

  • Serializable을 구현한 클래스에서 그 필드들도 의도치 않게 공개 API 가 될 수 있음(ITEM 86,87) 4 . protected로 멤버 접근 수준 풀기
  • public 클래스의 protected 멤버는 공개 API이다.


멤버 접근성 제약

  • 상위 클래스의 메서드를 재정의할 때 그 접근 수준을 상위 클래스보다 좁게 설정할 수 없다.
    • 리스코프 치환 원칙 : 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체해 사용할 수 있어야 한다.


주의점

  • 코드 테스트를 목적으로 클래스, 인터페이스, 멤버의 접근 점위를 공개 API로 만들어선 안된다.
    • pulblic 클래스의 private 멤버를 package-private까지 풀어주는 것까지는 허용


public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다.

  • 필드가 가변 객체를 참조하거나, final이 아닌 인스턴스 필드를 public으로 선언하면 그 필드에 담을 수 있는 값을 제한할 힘을 잃는다.
  • public 가변 필드를 갖는 킄래스는 일반적으로 스레드 안전하지 않다.
    • 또한 필드가 final, 불변 객체를 참조하더라도 public 필드를 없애는 리팩터링은 불가하게 된다.


정적 필드에서 public 필드 예외

  • 추상 개념을 완성하는 데 꼭 필요한 구성요소의 상수라면 가능
  • 관례상 이런 상수 이름은 대문자 알파벳과 단어 사이에 _을 넣는다.
  • 기본 타입 값이나 불변 객체를 참조해야 한다.


클래스에서 public static final 배열 필드를 두거나 이 필드를 반환하는 접근자 메소드를 제공해서는 안된다.

  • 클라이언트에서 배열의 내용을 수정할 수 있게 된다.

  • 불변 리스트

private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
  • 방어적 복사
private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values(){
  return PRIVATE_VALUES.clone();
}


JAVA9 의 모듈 시스템

  • 패키지 : 클래스의 묶음 / 모듈 : 패키지의 묶음
  • 모듈 중 공개할(export) 패키지를 선언한다.
    • 관례상 module-info.java 파일에
  • protected, public 멤버라도 해당 패키지에서 공개하지 않았따면 모듈 외부에서 접근할 수 없다.
  • 같은 모듈을 이루는 패키지끼리 자유롭게 공유할 수 있다.
  • 암묵적 접근 수준 : public, protected 접근 수준 효과가 그 묘듈 내로 한정되는 변종이다.


모듈에 적용되는 새로운 두 접근 수준은 상당히 주희해서 사용해야 한다.
  • 모듈의 jar 파일을 자신의 경로가 아니라 어플리케이션 클래스패스에 두면 그 모듈 안의 패키지는 마치 모듈이 없는 것처럼 행동한다.
    • 암묵적 접근 수준이 해제된다.
    • JDK가 이 접근 수준을 활용한 예, 자바 라이브러리에서 공개하지 않은 패키지들은 해당 모듈 밖에서는 절대로 접근할 수 없다.


모듈의 장점을 누리기 위한 조치
  • 패키지를 모듈 단위로 묶는다.
  • 모듈 선언에 패키지들의 의존성을 명시한다.
  • 소스 트리를 재배치하고 모듈 내부의 모듈 시스템을 적용하지 않은 패키지로의 모든 접근에 특별한 조치를 취해야 한다.

모듈의 개념은 당분간 사용하지 않는 것이 좋을 것 같다.


결론

  • 프로그램 요소의 접근성은 가능한 한 최소한으로
  • 꼭 필요한 것만 최소한의 public API 설계하기
  • public 클래스는 상수용 public static final 필드 외에는 어떠한 public 필드도 가져서는 안된다.
  • public static final 필드가 참조하는 객체가 불변인지 확인하자


Reference: