💥 개요
잘 설계된 컴포넌트는 클래스 내부 데이터와 내부 구현 정보를 얼마나 잘 숨겼느냐 입니다.
잘 설계된 컴포넌트는 모든 내부 구현을 완벽히 숨겨, 구현과 API를 깔끔히 분리합니다.
오직 API만을 통해 다른 컴포넌트와 소통하며 서로의 내부 동작 방식에는 전혀 영향을 끼치지 않는데
이것을 정보 은닉, 혹은 캡슐화라고 합니다. ( 소프트웨어 설계의 근간이 되는 원리)
👍 정보 은닉의 장점
정보 은닉의 장점은 정말 많습니다. 그중 대부분은 컴포넌트를 서로 독립시켜서 개발, 테스트, 최적화, 적용, 분석, 수정을 개별적으로 할 수 있게 해주는 것과 연관이 되어있습니다.
- 시스템 개발 속도를 높인다. 각 컴포넌트를 병렬로 개발이 가능
- 시스템 관리 비용을 낮춘다. 작은 컴포넌트로 나눠 빨리 파악할 수 있고 디버깅이 쉽고 교체하는 부담도 적음
- 성능 최적화에 도움을 준다. 완성된 시스템을 프로파일링해 최적화할 컴포넌트를 다른 컴포넌트에 영향을 주지 않고 해당 컴포넌트만 최적화가 가능
- 소프트웨어 재사용성을 높인다. 외부에 의존하지 않고 독자적으로 동작하는 경우 다른 환경에서도 유용하게 쓰임
- 큰 시스템을 제작하는 난이도를 낮춰준다. 시스템 전체가 완성되지 않아도 개별로 동작의 검증이 가능
🛠️ 정보 은닉을 위한 다양한 장치
접근 제어 메커니즘
정보 은닉의 핵심은 접근 제한자의 활용입니다.
기본 원칙은 모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 합니다.
소프트웨어가 올바르게 동작하는 한 항상 가장 낮은 접근 수준을 부여해야 합니다.
톱레벨 클래스와 인터페이스에 부여할 수 있는 접근 수준은 package-private과 public 두 가지 입니다.
톱레벨 클래스와 인터페이스를 public으로 선언하면 공개 API가 되고, package-private으로 선언하면 해당 패키지 안에서만 사용이 가능해집니다. 즉 패키지 외부에서 쓸 이유가 없다면 package-private으로 선언해야 합니다. 이렇게 되면 이들은 API가 아닌 내부 구현이 되어, 언제든 수정할 수 있고 다음 릴리스에서 수정, 교체, 제거를해도 아무런 문제가 발생하지 않습니다.
즉, public일 필요가 없는 클래스의 접근 수준을 package-private 톱레벨 클래스로 좁히는게 가장 중요합니다.
멤버에 부여할 수 있는 접근 수준(접근 범위가 좁은 순)
- private: 멤버를 선언한 톱레벨 클래스에서만 접근 가능
- package-private: 멤버가 소속된 패키지 안의 모든 클래스에서 접근 가능, 접근 제한자 명시하지 않았을 때 적용(인터페이스는 public이 적용됨)
- protected: package-private의 접근 범위를 포함, 하위 클래스에서도 접근이 가능
- public: 모든곳에서 접근 가능
🔍 접근 수준을 정하는 방법
API를 설계한 후, 그 외의 모든 멤버는 private으로 만듭니다.
이후 오직 같은 패키지의 다른 클래스가 접근해야 하는 멤버에 한해 package-private으로 풀어줍니다.
권한을 풀어주는 일을 자주 하게 된다면 컴포넌트를 더 분해해야 하는 것은 아닐지 고민해야 합니다. private과 package-private 멤버는 모두 해당 클래스의 구현에 해당하여 공개 API에 영향을 주지 않습니다. 단 Serailizable을 구현한 클래스에서는 그 필드들도 의도치 않게 공개 API가 될 수도 있습니다.
그럼 위에서 말했던 protected는 왜 사용하지 않을까요? protected 접근 제한자를 가진 멤버는 공개 API가 됩니다. 왜냐하면 상속한 클래스에서 해당 멤버를 사용하게 되니까 해당 멤버를 변경하거나 수정한다면 상속한 클래스에서 해당 값을 사용하는 모든 부분이 오동작할 가능성이 높으니, 되도록 private이나 package-private을 사용해야 합니다.
테스트를 할 때 private을 풀어도 괜찮을까?
단지 코드를 테스트하려는 목적으로 클래스, 인터페이스, 멤버의 접근 범위를 넓히려 할 때가 있습니다. 적당한 수준(private -> package-private)은 괜찮지만 그 이상은 안됩니다. 즉 테스트를 위해 공개 API로 만들면 안됩니다. 이렇게 해야 할 이유도 없이 테스트 코드를 테스트 대상과 같은 패키지에 되면 문제가 해결됩니다.
사실 테스트의 경우 이런식으로 접근 제한자를 풀어가며 해야 할 일이 얼마나 있나 싶기도 하고, 그게 올바른 테스트인지 한번 더 생각해봐야 할 것 같습니다.
⚠️ public 클래스의 인스턴스 필드는 public이 아니어야 한다.
필드가 가변 객체를 참조하거나, final이 아닌 인스턴스 필드를 public으로 선언하면 그 필드에 값을 할당하는걸 제어할 수 없게 됩니다. 더해 해당 필드가 수정될 때 락 획득같은 다른 작업을 할 수 없게 되므로 public 가변 필드를 갖는 클래스는 스레드 안전하지 않게 됩니다. 심지어 필드가 final이면서 불변 객체를 참조하는 경우에도 내부 구현을 변경하지 못하는 문제점은 여전합니다.
이런 문제는 정적 필드도 마찬가지지만, 해당 클래스의 추상 개념을 완성하는데 필요한 상수같은 경우는 괜찮습니다.
관례상 네이밍은 대문자 알파벳으로 사용하며, 각 단어 사이에 밑줄(_)을 넣어야 합니다. 또한 이런 값은 항상 기본 타입 값이나 불변 객체를 참조해야 합니다. 가변 객체를 참조하면 해당 객체가 변경이 되면 일어나면서 알 수 없는 동작을 하게 됩니다.
클래스에서 public static final 배열 필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공하면 안됩니다.
public static final Thing[] VALUES = { ... };
위와 같은 필드는 배열의 내용이 수정되며 클래스가 알 수 없는 동작을 하게 됩니다.\
💡해결 방법
해결책은 총 2가지입니다.
1.public 배열을 private으로 만들고 public 불변 리스트를 추가하는 방법
private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES =
Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
2. public 배열을 private으로 만들고 방어적 복사를 통해 복사본을 반환하는 방법
private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
}
프로그램 요소의 접근 가능성은 가능한 한 최소한으로 하라. 꼭 필요한 것만 골라 최소한의 public API를 설계하자. 그 외에는 클래스, 인터페이스, 멤버가 의도치 않게 API로 공개되는 일이 없도록 해야 한다. public 클래스는 상수용 public static final 필드 외에는 어떠한 public 필드도 가져서는 안 된다. public static final 필드가 참조하는 객체가 불변인지 확인하라.
❤️ 스터디 질답 정리
Q.프로파일링을 하는 방법?
A.모니터링 툴인데 어떤 메서드에서 얼마나 걸리는지 확인이 가능
정보 은닉의 장점에서 모든것은 인터페이스라고 생각하면 이해가 쉽다.
정보 은닉을 하기 위한 장치 중 하나가 인터페이스기 때문.
Q.중첩 클래스에서 private static을 사용해야 하는 이유?
A. 그냥 private으로 선언한 내부 클래스는 외부 클래스에 대한 참조를 내부적으로 항상 포함하고 있다. 반면, private static으로 선언한 클래스는 외부 클래스에 대한 참조를 전혀 갖고 있지 않다. 즉, private 이너 클래스는 톱레벨 클래스의 멤버에 접근할 수 있지만, private static은 불가능하다. 다시 말해서, 톱레벨 클래스와 독립적으로 사용하기 위해 private static 클래스를 사용하는 것이다.
'Study > 이펙티브 자바' 카테고리의 다른 글
[Effective Java 3E] 변경 가능성을 최소화하라 (0) | 2023.09.14 |
---|---|
[Effective Java 3E] public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (0) | 2023.09.09 |
[Effective Java 3E] Comparable을 구현할지 고민하라 (2) | 2023.09.08 |
[Effective Java 3E] clone 재정의는 주의해서 진행하라 (0) | 2023.09.01 |