잘 설계된 컴포넌트는 클래스 내부 데이터와 내부 구현 정보를 외부 컴포넌트로부터 숨긴 채, 캡슐화를 통해 보호한다. 즉, 내부 데이터와 내부 구현 정보는 클래스 내부에 숨겨져 있으며, 외부에서는 클래스가 제공하는 공개된 API를 통해서만 데이터와 동작을 조작할 수 있다. 이는 코드의 유지보수성과 확장성을 향상시키는 소프트웨어 설계의 근간이 되는 원리이다.
접근 지정자
멤버에 접근하는 클래스 | 멤버의 접근 지정자 | |||
private | package-private | protected | public | |
같은 패키지의 클래스 | X | O | O | O |
다른 패키지의 클래스 | X | X | X | O |
접근 가능 영역 | 클래스 내 | 동일 패키지 내 | 동일 패기지와 자식 클래스 | 모든 클래스 |
기본 원칙
: 모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 한다.
톱레벨 클래스와 인터페이스
: package-private / public
패키지 외부에서 쓰지 않는다면 package-private로 선언하자
- public
공개 API가 되므로 하위 호환을 위해 영원히 관리해줘야 한다.
- package-private
패키지 안에서만 이용할 수 있다. 따라서 내부 구현이 되어 클라이언트에 영향을 끼치지 않고 수정, 교체, 제거할 수 있다.
한 클래스에서만 사용하는 package-private 톱레벨 클래스나 인터페이스는 이를 사용하는 클래스 안에 private static으로 중첩시키자(item 24)
톱레벨로 두면 같은 패키지의 모든 클래스가 접근할 수 있지만, private static으로 중첩시키면 바깥 클래스 하나에서만 접근할 수 있다.
멤버(필드, 메소드, 중첩 클래스, 중첩 인터페이스)
: private / package-private / protected / public
모든 멤버는 private으로 만든 후 같은 패키지의 다른 클래스가 접근해야 하는 멤버에 한해 package-private으로 풀어주자.
멤버는 클래스의 구현이므로 공개 API에 영향을 주지 않지만, Serializable을 구현한 클래스에서는 그 필드들이 의도치 않게 공개 API가 될 수도 있다.(item 86, 87)
protected 멤버의 수는 최대한 줄이자.
public 클래스에서는 멤버의 접근 수준을 package-private에서 protected로 바꾸는 순간 그 멤버에 접근할 수 있는 대상 범위가 엄청나게 넓어진다. public 클래스의 protected 멤버는 공개 API이므로 영원히 지원돼야 한다. 또한, 내부 동작방식을 API 문서에 적어 사용자에게 공개해야 할 수도 있다.(item 19)
상위 클래스의 메소드를 재정의할 때는 그 접근 수준을 상위 클래스에서보다 좁게 설정할 수 없다.
(다형성에 어긋난다.)
리스코프 치환 원칙(item 10)으로 인한 제약이다.
클래스가 인터페이스를 구현하는 경우에도 이 규칙이 적용된다. 클래스는 인터페이스가 정의한 모든 메소드를 public으로 선언해야 한다. 인터페이스를 구현하는 클래스가 인터페이스에서 정의한 메소드를 모두 구현하고, 외부에서 인터페이스를 통해 클래스를 사용할 때 메소드의 접근 수준에 제약이 없도록 하기 위함이다.
테스트 목적으로 클래스, 인터페이스, 멤버의 접근 범위를 넓혀 API로 만들지 않도록 주의하라.
필요한 경우, public 클래스의 private 멤버를 package-private만 풀어주자.
애초에 테스트 코드를 테스트 대상과 같은 패키지에 두면 package-private 요소에 접근할 수 있으므로, 그 이상 늘릴 필요는 없을 것이다.
public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다.(item 16)
필드가 가변 객체를 참조하거나, final이 아닌 인스턴스 필드를 public으로 선언하면, 그 필드와 관련된 모든 것은 불변식을 보장할 수 없게 된다.
➕ public 가변 필드를 갖는 클래스는 일반적으로 스레드 안전하지 않다. 필드가 final이면서 불변 객체를 참조하더라도 ..
여러 스레드에서 동시에 해당 필드를 변경하는 경우, 객체의 일관성이 깨질 가능성이 크다.
➕ 꼭 필요한 구성요소인 상수라면 public static final 필드로 공개해도 된다. 불변인지 확인하라.
반드시 기본 타입 값이나 불변 객체를 참조해야 한다.(item 17) 다른 객체는 참조하지 못하지만, 참조된 객체 자체가 수정될 수 있기 때문이다.
➕ 클래스에서 public static final 배열 필드를 두거나 이 필드를 반환하는 접근자 메소드를 제공해서는 안된다.
배열은 가변 객체이므로 객체의 일관성을 보장할 수 없다.
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 메소드를 추가한다.(방어적 복사)
?? 사실 이해가 안 된다. 저렇게 하면 원본과 복제본이 동일한 객체를 참조할 수도 있으므로, List로 반환하는 게 올바른 방법 아닌가?
private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
}
모듈 시스템
: public ? / protected?
자바 9부터는 모듈 시스템이 도입되면서 두 가지 암묵적 접근 수준이 생겼다.
protected/public 멤버라도 해당 패키지를 공개하지 않는다면 모듈 외부에서 접근할 수 없다. 즉, 클래스를 외부에 공개하지 않으면서 같은 모듈을 이루는 패키지 사이에서 자유롭게 공유할 수 있다.
이를 활용한 대표적인 예가 JDK이다. 자바 라이브러리에서 공개하지 않은 패키지들은 해당 모듈 밖에서 절대로 접근할 수 없다.
'Language > Java' 카테고리의 다른 글
[effective java] 아이템 17. 변경 가능성을 최소화하라. (0) | 2023.03.25 |
---|---|
[effective java] 아이템 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라. (0) | 2023.03.25 |
[effective java] 아이템 14. Comparable을 구현할지 고려하라. (0) | 2023.03.25 |
[effective java] 아이템 13. clone 재정의는 주의해서 진행하라. (0) | 2023.03.22 |
[effective java] 아이템 12. toString을 항상 재정의하라. (0) | 2023.03.09 |