Item 15. 클래스와 멤버의 접근 권한을 최소화하라
•
컴포넌트의 잘 설계 되었는가는 내부 구현 정보를 외부 컴포넌트로부터 얼마나 완벽히 숨겼는가로 차이가 난다.
•
내부 구현을 완벽히 숨겨 구현과 API를 깔끔하게 분리한다면, 내부 동작 방식에는 개의치않고 오직 API를 통해서만 다른 컴포넌트와 소통한다.
•
캡슐화 혹은 정보 은닉이라 하는 이 개념은 시스템을 구성하는 컴포넌트들을 서로 독립시켜서 개발, 테스트, 최적화, 적용, 분석, 수정을 개별적으로 할 수 있게 해주는 것과 연관이 있다.
◦
여러 컴포넌트를 병렬로 개발할 수 있기 때문에 시스템 개발 속도를 높인다.
◦
각 컴포넌트를 더 빨리 파악하고 컴포넌트를 교체하는데 부담이 적기 때문에 시스템 관리 비용을 낮춘다.
◦
다른 컴포넌트에 영향을 주지 않고 특정 컴포넌트만 최적화 할 수 있기 때문에 성능 최적화에 도움을 준다.
◦
외부에 거의 의존하지 않고 독자적으로 동작하는 컴포넌트는 개발되지 않은 낯선 환경에서 유용하게 쓰일 가능성이 크기 때문에 소프트웨어 재사용성을 높인다.
•
Java에서 제공하는 정보 은닉 중 하나는 클래스, 인터페이스, 멤버의 접근성(접근허용 범위)을 명시하는 접근 제어 메커니즘이다.
•
각 요소에 대한 접근 허용 범위는 그 요소가 선언된 위치와 private, protected, public과 같은 접근 제한자로 결정된다.
•
정보 은닉의 기본 원칙은 모든 클래스와 멤버의 접근 가능성을 가능한 좁혀야 한다는 것이다. 다시말해 올바른 소프트웨어 동작을 하면서 가장 낮은 접근 수준을 부여해야 한다는 뜻이다.
•
가장 외부에 드러나있는 톱레벨 클래스와 인터페이스에는 package-private와 public을 부여할 수 있다.
◦
톱레벨 클래스와 인터페이스를 public으로 선안하면 공개 API가 되고, package-private로 선언하면 해당 패키지 안에서만 접근 가능하다.
◦
그러한 이유로 패키지 외부에서 사용할 일이 없다면 package-private로 선안하는 것이 권장된다. public으로 선언하게 되면 하위 호환을 위해 지속적으로 관리해줄 필요가 발생할 수 있다.
•
하나의 클래스에서만 사용되는 package-private 톱레벨 클래스나 인터페이스가 있다면, 해당 클래스를 사용하는 클래스 안에 private static으로 넣어서 사용하는 것을 권장한다.
package-private class A {
...
}
package-private class B {
A getAFromB();
}
Java
복사
package-private class B {
private static class A {
...
}
A getAFromB();
}
Java
복사
◦
첫 번째 코드처럼 톱레벨로 두면 같은 패키지의 모든 클래스가 접근 가능하지만, private static으로 중첩시키면 바깥 클래스 하나에서만 접근할 수 있다.
•
public 클래스는 그 패키지의 API이지만 package-private 톱레벨 클래스는 내부 구현에 속하기 때문에, public일 필요가 없는 클래스의 접근 수준을 package-private 톱레벨 클래스로 좁히는 것이 권장된다.
•
필드, 메서드, 중첩 클래스, 중첩 인터페이스와 같은 멤버에 부여할 수 있는 접근 수준은 4가지가 있다.
◦
private : 멤버를 선언한 톱레벨 클래스에서만 접근 가능
◦
package-private : 멤버가 소속된 패키지 안의 모든 클래서에서 접근 가능
접근 제한자를 명시하지 않으면 인터페이스는 public, 인터페이스를 제외하면 기본적으로 package-private가 적용된다.
◦
protected : package-private의 범위를 포함하여 이 멤버를 선언한 클래스의 하위 클래스에서도 접근 가능
◦
public : 모든 곳에서 접근 가능
•
클래스에서 같은 패키지의 다른 클래스가 접근해야 하는 멤버에 한해 package-private로 풀어주고 그 외의 모든 멤버를 private로 만들자
◦
권한을 풀어주는 일이 너무 잦다면 시스템에서 컴포넌트를 더 분해해야 하는게 아닌지 생각해보자
◦
private와 package-private 멤버는 모두 클래스의 내부 구현에 해당하므로, 일반적으로 공개 API에 영향을 주지 않는다.
◦
다만 Serializable을 구현한 클래스에서는 그 필드들도 의도치 않게 공개 API가 될 수도 있다.
Serializable
직렬화(serialize)란 자바 언어에서 사용되는 Object 또는 Data를 다른 컴퓨터의 자바 시스템에서도 사용 할수 있도록 바이트 스트림(stream of bytes) 형태로 연속전인(serial) 데이터로 변환하는 포맷 변환 기술을 일컫는다. 그 반대 개념인 역직렬화는(Deserialize)는 바이트로 변환된 데이터를 원래대로 자바 시스템의 Object 또는 Data로 변환하는 기술이다.
•
public 클래스의 멤버가 package-private에서 protected로 바뀌면, protected는 공개 API이므로 해당 멤버에 접근할 수 있는 범위가 매우 넓어지기 때문에 protected 멤버의 수는 적을수록 좋다.
•
상위 클래스의 메서드를 재정의할 때는 리스코프 치환 원칙을 지키기 위해 접근 수준을 상위 클래스에서보다 좁게 설정할 수 없다.
리스코프 치환 원칙(LSP, Liskov Substitution Principle)
서브 타입은 언제나 기반 타입으로 변경가능해야 한다는 원칙이다. 다시말해 부모 클래스는 자식 클래스 인스턴스로 대체 될 수 있어야하고, 자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행이 보장되어야 한다는 의미이다.
•
단지 코드를 테스트하려는 목적으로 인해 클래스나 인터페이스, 멤버를 공개 API로 만들어서는 안된다.
◦
public 클래스의 private 멤버를 package-private까지 풀어주는 것은 허용할 수 있지만, 그보다 더 풀어주게 되면 공개 API가 되므로 해서는 안된다.
◦
테스트 코드를 테스트 대상과 같은 패키지에 두면 package-private 요소에 접근할 수 있기 때문에 할 이유도 없다.
•
public 클래스의 인스턴스 필드는 가급적 public이 아니어야한다.
◦
필드가 가변 객체를 참조하거나 final이 아닌 인스턴스 필드를 public으로 선언하게 되면 해당 필드에 담을 수 있는 값을 제한할 수 없게 된다. 다시말해 해당 필드와 관련된 불변식을 보장할 수 없게 된다는 뜻이다.
◦
또한 필드가 수정되는 동안 다른 작업을 할 수 없게 되므로 public 가변 필드를 갖는 클래스는 일반적으로 스레드 안전하지 않다.
◦
final이면서 불변 객체를 참조하더라도 public으로 구현하게되면, 내부 구현을 때 public 필드를 없애는 방식으로는 리팩토링할 수 없게 된다.
•
정적 필드에서 해당 클래스가 표현하는 추상 개념을 완성하는데 꼭 필요한 구성요소로써의 상수라면 public static final 필드로 공개해도 된다.
◦
public static final 필드는 반드시 기본 타입 값이나 불변 객체를 참조해야 한다.
◦
public static final로 두어도 길이가 0이 아닌 배열은 변경이 가능하므로, 배열을 필드로 두거나 이 필드를 반환하는 접근자 메서드를 제공하면 안된다.
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();
}
Java
복사
◦
이와 같이 배열을 private로 만들고 public 불변 리스트를 추가하거나, private 배열의 복사본을 반환하는 public 메서드를 추가하는 방어적 복사 방법으로 해결할 수 있다.
•
자바 9에서는 모듈 시스템 개념이 도입되어 두 가지 암묵적 접근 수준이 추가되었다.
◦
패키지가 클래스들의 묶음이라면 모듈은 패키지들의 묶음으로, 모듈은 자신에 속하는 패키지 중 공개(export)할 것들을 선언할 수 있다.
◦
protected나 public 멤버라도 해당 패키지를 공개하지 않는다면 모듈 외부에서는 접근할 수 없다. 모듈 내부에서는 공개 선언 여부는 아무런 영향을 받지 않는다.
◦
모듈 시스템을 활용하면 클래스를 외부에 공개하지 않으면서도 모듈 내 패키지 사이에서 자유롭게 공유할 수 있다.
◦
생성한 모듈의 jar 파일을 자신의 모듈 경로가 아닌 어플리케이션의 클래스패스(classpath)에 두면, 모듈 안의 모든 패키지는 모듈의 공개 여부와 상관 없이 public과 protected 멤버를 모듈 밖에서도 접근할 수 있게하여 모듈이 없는 것처럼 동작하기 때문에 조심해서 사용해야 한다.
◦
이러한 암묵적 접근 수준을 적극 활용한 예시가 JDK 자체로, 자바 라이브러리에서 공개하지 않은 패키지들은 해당 모듈 밖에서 절대 접근할 수 없다.
◦
JDK 외에도 모듈 개념이 받아들여질 지를 예측하기 이른 감이 있으니 당분간은 꼭 필요한 경우가 아니라면 사용하지 않는게 좋을 것 같다.