Item15. 클래스와 멤버의 접근 권한을 최소화하라
잘 설계된 컴포넌트는 모든 내부 구현을 완벽히 숨겨, 구현과 API를 깔끔하게 분리한다.
오직 API를 통해서만 다른 컴포넌트와 소통하며, 서로 구체적인 동작 방식은 개의치 않는다.
정보 은닉 (Information Hiding) 의 장점
시스템을 구성하는 컴포넌트를 서로 독립시켜서 개발/테스트/적용/수정 분석을 개별적으로 할 수 있게 해준다
여러 컴포넌트를 병렬로 개발 가능함으로, 개발 속도를 높임
각 컴포넌트를 빨리 파악 및 디버깅이 관리하고, 다른 컴포넌트로 교체하는 작업이 손쉽기 때문에 유지보수 비용이 덜 든다
직접적으로 성능을 높여주지는 않지만, 성능최적화에 도움을 준다. 완성된 시스템을 프로파일링해 최적화할 컴포넌트를 정하고, 해당 컴포넌트만 최적할 수 있기 때문이다.
SW 재사용을 높임
시스템 개발 난이도를 낮춘다. 개별 컴포넌트단위로 나누어 개발하고 테스트할 수 있기 때문이다.
접근 제어 원리는 클래스,인터페이스,멤버의 접근성을 명시하고, 각 요소의 접근성은 그 요소가 선언된 위치와 접근 제한자 (private,protected,public) 으로 정해진다,
모든 클래스와 멤버의 접근성은 가능한 낮추어야 한다. ( public->private 방향)
클래스와 인터페이스에 부여할 수 있는 접근 수준은 package-private(default) , public 2 가지이다.
클래스와 인터페이스를 public으로 선언하면 공개 API가 되며, default로 선언하면 해당 패키지 안에서만 사용할 수 있다.
public으로 선언할 경우에는 외부에 노출되는 API가 되므로, 하위 호환을 위해서 계속 관리해주어야 한다.
다음과 같이 public,package-private 클래스 안에 private static 클래스로 내부 클래스를 선언하면, outer 클래스에서는 inner 클래스 접근이 가능하지만, 외부에서는 inner 클래스 접근이 불가능하다.
1 | public class Outer { |
- public 필요가 없는 클래스의 접근 수준을 package-private(default) 로 좁히자.
public 클래스는 그 패키지의 API인 반면, package-prviate는 내부구현으로 분류되기 떄문이다.
- 접근 제한자의 종류와 접근 가능 범위
접근 지시자 | 클래스 내부 | 동일 패키지 | 하위 클래스 | Global |
---|---|---|---|---|
private | v | |||
default (package-private) | v | v | ||
protected | v | v | v | |
public | v | v | v | v |
클래스의 공개 API를 만들떄는 해당 클래스의 모든 멤버는 우선 private로 선언해두고, 오직 같은 패키지의 다른 클래스가 접근해야 하는 멤버에 한 해 , default로 풀어주자
public class의 protcted 멤버는 공개 API에 속함으로, 계속 지원해주어야 하고, 내부 동작방식을 API문서에 적어 공개해야 할 수도 있다.
- 예외 상황: 부모 class의 method를 overriding하여 자식 class에서 접근 지시자를 좁힐수는 없다. (리스코프 치환 원칙을 지키기 위해 필요)
Test 상황시에는 public 클래스의 private 멤버 변수를 default까지로 변경하는 것은 괜찮으나, 테스트를 위해 public API로 만들어서는 안된다.
- public class의 필드는 되도록 public이 아니여야 한다.
(item16관련, Immutable 객체 관련 내용이다. )
immutable 객체란 객체가 한번 생성되고 나서 외부에서 상태를 변경시킬 수없는 객체를 말하는데, 필드가 public이면 당연히 값 변경이 자유롭기 떄문에, 불변객체가 아니고 또한 Thread-safe 하지 않다.
- 헤당 클래스와 관련된 상수인 경우는 public stiac final 필드로 공개하여도 괜찮다
1 | public class Round{ |
이떄 주의할 사항은 public static final 필드에 배열을 두거나, 이 필드를 반환하는 접근자 method를 제공해서는 안된다는 점이다.
1 | public static final Thing[] VALUES = {...}; |
위 코드는 client에서 배열 원소를 가져가 수정하면 원본 배열에도 변경사항이 적용된다는 보안 취약점이 있다.
해결방법은 두가지가 있는데,
- private 배열로 만들고, public 불변 리스트를 추가한다.
1 | public class Round { |
1 |
|
Collections.unmodifiableList API는 주어진 리스트에 대해서 add 혹은 remove 연산시 UnsupportedOperationException예외를 발생시킨다
- private 배열을 만들고, 그 복사본을 반환하는 public method를 추가한다 (방어적 복사 라고 부름 )
1 | public class Round { |
- java9 부터는 모듈 시스템 개념이 도입되면서 두가지 암묵적 접근 수준이 추가되었다. (모듈 : 패키지의 묶음 )
정리
program 요소의 접근성은 가능한 최소한으로 해서 필수적인 것만 public API로 공개하자.
해당 클래스의 개념과 관련된 상수들은 public static final 로 선언하고, 이떄 public static final 필드가 client에서 변경할 수 없는 불변임을 확인하라