-
[Effective Java] Item 15. 클래스와 멤버의 접근 권한을 최소화하라Java/Effective Java 2020. 8. 27. 22:01반응형
잘 설계된 컴포넌트의 특징은 클래스의 내부 구현 정보를 외부 컴포넌트로부터 숨기고, 오직 API를 통해서만 다른 컴포넌트와 소통하며 서로의 내부 동작 방식에는 개의치 않는다는 점입니다. 정보 은닉, 혹은 캡슐화라고 하는 이 개념은 소프트웨어 설계의 근간이 되는 원리입니다.
정보 은닉의 장점
- 여러 컴포넌트를 병렬로 개발할 수 있기 때문에 시스템 개발 속도를 높일 수 있습니다.
- 각 컴포넌트를 더 빨리 파악하여 디버깅할 수 있고, 다른 컴포넌트로 교체하는 부담이 적어지므로 시스템 관리 비용을 낮춥니다.
- 정보 은닉 자체가 성능을 높여주지는 않지만, 성능 최적화에 도움을 줍니다. 완성된 시스템을 프로파일링 해 최적화할 컴포넌트를 정한 다음 다른 컴포넌트에 영향을 주지 않고 해당 컴포넌트만 최적화할 수 있기 때문입니다.
- 소프트웨어 재사용성을 증대시킵니다. 외부에 의존하지 않고 독자적으로 동작할 수 있는 컴포넌트라면 그 컴포넌트와 함께 개발되지 않은 낯선 환경에서도 유용하게 쓰일 가능성이 크기 때문입니다.
- 큰 시스템을 제작하는 난이도를 낮춰줍니다. 시스템 전체가 아직 완성되지 않은 상태에서도 개별 컴포넌트의 동작을 검증할 수 있기 때문입니다.
접근 제한자
자바에서는 정보 은닉을 위한 장치로 접근 제한자를 제공합니다. 접근 제한자는 클래스, 인터페이스, 멤버의 접근성을 명시합니다.
접근 제한자의 기본 원칙은 모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 합니다.
- private : 멤버를 선언한 톱레벨 클래스에서만 접근할 수 있습니다.
- package-private : 멤버가 소속된 패키지 안의 모든 클래스에서 접근할 수 있습니다. 접근 제한자를 명시하지 않았을 때 적용되는 패키지 접근 수준입니다(단, 인터페이스의 멤버는 기본적으로 public이 적용됩니다).
- protected : package-private의 접근 범위를 포함하며, 이 멤버를 선언한 클래스의 하위 클래스에서도 접근할 수 있습니다.
- public : 모든 곳에서 접근할 수 있습니다.
클래스의 공개 API를 제외한 모든 멤버는 private로 만들고, 그런 다음에 오직 같은 패키지의 다른 클래스가 접근해야 하는 멤버에 한하여 package-private로 풀어주는 것이 좋습니다. 권한을 풀어주는 일을 자주 하게 된다면 시스템에서 컴포넌트를 더 분해해야 하는 것은 아닌지 다시 고민해봐야 합니다.
public 클래스에서는 멤버의 접근 수준을 package-private에서 protected로 바꾸는 순간 그 멤버에 접근할 수 있는 대상 범위가 넓어지게 됩니다. public 클래스의 protected 멤버는 공개 API가 되기 때문에 이를 사용하는 클라이언트를 위해 영원히 지원돼야 합니다. 또한 내부 동작 방식을 API 문서에 적어 사용자에게 공개해야 할 수도 있습니다. 따라서 protected 멤버의 수는 적을수록 좋습니다.
상위 클래스의 메서드를 재정의할 때는 그 접근 수준을 상위 클래스에서보다 더 좁게 설정할 수 없습니다. 이 제약은 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체해 사용할 수 있어야 한다는 리스코프 치환 원칙을 지키기 위해 필요합니다.
public 클래스의 인스턴스 필드는 되도록 public이 아니어야 합니다. 필드가 가변 객체를 참조하거나, final이 아닌 인스턴스 필드를 public으로 선언하면 그 필드와 관련된 모든 것은 불변식을 보장할 수 없게 됩니다. public 가변 필드를 갖는 클래스는 일반적으로 Thread-safe 하지 않습니다. 필드가 final이면서 불변 객체를 참조하더하더도 내부 구현을 바꿔야할 때, 그 public 필드를 없애는 방식으로는 리팩토링할 수 없게 됩니다.
해당 클래스가 표현하는 추상 개념을 완성하는 데 꼭 필요한 구성요소로써의 상수라면 public static final 필드로 공개해도 좋습니다. 관례상 이런 상수의 이름은 대문자 알파벳으로 쓰며, 각 단어 사이에 밑줄(_)을 넣습니다. 이런 필드는 반드시 기본 타입 값이나 불변 객체를 참조해야 합니다.
클래스에서 public static final 배열 필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공해서는 안됩니다. 이런 필드나 접근자를 제공한다면 클라이언트에서 그 배열의 내용을 수정할 수 있게 됩니다.
//보안 허점이 숨어 있습니다. public static final Thing[] VALUES = { ... };
위 문제의 해결책은 두 가지 입니다.
첫 번째 방법은 앞 코드의 public 배열을 private로 만들고 public 불변 리스트를 추가하는 것입니다.
private static final Thing[] PRIVATE_VALUES = { ... }; public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
두 번째 방법은 배열을 private로 만들고 그 복사본을 반환하는 public 메서드를 추가하는 방법입니다(방어적 복사).
private static final Thing[] PRIVATE_VALUES = { ... }; public static final Thing[] values() { return PRIVATE_VALUES.clone(); }
모듈 시스템
자바 9에서는 모듈 시스템이라는 개념이 도입이 도입 되었습니다. 패키지가 클래스들의 묶음이듯, 모듈은 패키지들의 묶음입니다. 모듈은 자신에 속하는 패키지 중 공개(export)할 것들을 (관례상 module-info.java 파일에) 선언합니다. protected 혹은 public 멤버라도 해당 패키지를 공개하지 않았다면 모듈 외부에서는 접근할 수 없습니다. 물론 모듈 안에서는 exports로 선언했는지 여부에 아무런 영향을 받지 않습니다.
모듈의 JAR 파일을 자신의 모듈 경로가 아닌 애플리케이션의 클래스패스(classpath)에 두면 그 모듈 안의 모든 패키지는 마치 모듈이 없는 것처럼 동작하므로 주의해야 합니다. 즉, 모듈이 공개했는지 여부와 상관없이, public 클래스가 선언한 모든 public 혹은 protected 멤버를 모듈 밖에서도 접근할 수 있게 됩니다.
참고자료
반응형'Java > Effective Java' 카테고리의 다른 글
[Effective Java] Item 18. 상속보다는 컴포지션을 사용하라 (0) 2020.08.29 [Effective Java] Item 17. 변경 가능성을 최소화하라 (0) 2020.08.28 [Effective Java] Item 14. Comparable을 구현할지 고려하라 (0) 2020.08.26 [Effective Java] Item 13. clone 재정의는 주의해서 진행하라 (0) 2020.08.26 [Effective Java] Item 12. toString을 항상 재정의하라 (0) 2020.08.24