item 15. 클래스와 멤버의 접근 권한을 최소화하라
잘 설계된 컴포넌트란?
클래스 내부 데이터와 구현 정보를 외부 컴포넌트로부터 얼마나 잘 숨겼느냐에 달린다.
오직 API를 통해서 외부 컴포넌트와 소통한다.
"캡슐화"
[ 캡슐화 / 정보 은닉의 장점 ]
- 시스템 개발 속도를 높인다.
- 여러 컴포넌트를 병렬로 개발 가능
- 시스템 관리 비용을 낮춘다.
- 각 컴포넌트별 디버깅 속도가 빠르다.
- 컴포넌트 교체 부담이 적다.
- 성능 최적화에 도움을 준다.
- 다른 컴포넌트에 영향을 주지 않고 해당 컴포넌트만 최적화가 가능하다.
- 소프트웨어 재사용성을 높인다.
- 외부에 거의 의존하지 않고 독자적으로 동작할 수 있는 컴포넌트일 경우.
- 큰 시스템 제작 난이도를 낮춰준다.
- 시스템이 완성되지 않은 상태라도 개별 컴포넌트 동작 검증이 가능하기 때문이다.
[ 자바에서 제공하는 정보 은닉 장치 ]
- 접근 제어 메커니즘
: 클래스, 인터페이스, 멤버의 접근성 명시.
-> 여기서 "접근성", 즉 요소가 선언된 위치와 접근 제한자 중, 접근 제한자를 활용하는 것이 정보 은닉의 핵심이다.
모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 한다.
멤버에 부여할 수 있는 접근 수준 4가지
(멤버 : 필드, 메서드, 중첩 클래스, 중첩 인터페이스)
1. private: 멤버를 선언한 톱레벨 클래스에만 접근 가능
2. package-private(default): 패키지 안의 모든 클래스에서 접근 가능
3. protected: 동일 패키지 및 상속받은 하위 클래스에서 접근 가능
4. public: 모든 곳에서 접근 가능
접근 제한자가 접근할 수 있는 인스턴스 필드
구분 public protected default private 동일 클래스 O O O O 동일 패키지
상속O O O X 동일 패키지 O O O X 다른 패키지
상속O O X X 다른 패키지 O X X X
- 클래스 공개 API를 설계했다면, 그 외는 모두 private로 선언해라.
- 그 후 같은 패키지의 다른 클래스가 접해야하는 멤버에 한해 default로 풀어주자.
- (주의) Serializable을 구현한 클래스에서는 그 필드들도 의도치 않게 공개 API가 될 수도 있다. 주의 !!
- public class에서는 접근 수준을 defalut에서 protected로 바꾸는 순간 대상 범위가 엄청나게 넓어지니 주의하자.
--> protected는 적을 수록 좋다.
[ 멤버 접근성을 좁히지 못하게 방해하는 제약 ]
- 상위 클래스의 메서드를 재정의하는 경우.
- 위와 같은 경우는 리스코프 치환 원칙(상위 클래스의 인스턴스는 하위 클래스 인스턴스로 대체해 사용할 수 있어야 한다)을 지키기 위해 필요하다.
- 해당 경우는 인터페이스가 정의한 모든 메서드를 public 으로 선언한다.
[ 접근 제한자 주의사항 ]
1. Test의 경우 public class의 멤버를 private에서까지 default까지 풀어주는 것까지만 허용된다.
- 그 이상으로 테스트만을 위해 공개 API로 만들어서는 안된다.
2. public class의 인스턴스 필드는 되도록 public이 아니어야 한다.
- public 가변 필드를 갖는 클래스인 경우에, 불변식을 보장할 수 없다.
- 필드가 수정될 때 다른 작업을 할 수 없고, 스레드 안전하지 않기 때문이다.
- 내부 구현을 바꾸고 싶어도 public 필드를 없애는 방식으로는 리팩터링할 수 없다.
- 정적 필드에서도 동일하다.
- 예외 : 해당 클래스가 표현하는 추상 개념을 완성하기 위해 꼭 필요한 구성 요소로써의 상수 인 경우.
- public static final로 공개 가능하다.
- 위의 예외 경우가 아닌 상태에서 클래스에서 public static final 필드를 두거나 이를 반환하는 접근자 메서드를 사용할 경우, 길이가 0이 아닌 배열이라면 클라이언트 배열 내용이 수정될 수도 있는 보안 허점이 존재한다.
3. 어떤 IDE가 생성하는 접근자는 private 배열 필드의 참조를 반환하여 위의 문제를 동일히 일으킬 수 있다.
public static final Thing[] values {...};
- 해결책 1
- 앞 코드의 public 배열을 private로 만들고 public 불변 리스트 추가하기
private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
- 해결책 2
- 배열을 private로 만들고 복사본을 반환하는 public method를 추가하기.
private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values(){
return PRIVATE_VALUES.clone();
}
[ Java 9의 모듈 시스템 ]
- 두가지 암묵적 접근 수준이 추가되었다.
- public / protected 수준과 같으나, 그 효과가 모듈 내부로 한정된다. (모듈 : 패키지의 묶음)
- 모듈은 자신이 속하는 패키지 중 공개할 것들을 module_info.java에 추가한다.
- public, protected라도 해당 패키지를 공개하지 않았다면 모듈 외부에서 접근할 수 없다.
(모듈 안에서는 exports로 선언했는지 여부에 영향을 받지 않는다.)
- 모듈을 이용하면 클래스를 외부에 공개하지 않으며 같은 모듈을 이루는 패키지 사이에서는 자유롭게 공유할 수 있다.
[주의할 점]
- 모듈의 JAR 파일을 모듈 경로가 아닌 classpath에 두면 모듈이 없는 것과 같은 효과를 내기 때문에 주의해야 한다.
- 대표적인 예 : JDK