잘 설계된 컴포넌트는 모든 내부 구현을 완벽히 숨겨 구현과 API를 깔끔히 분리함
오직 API를 통해서만 다른 컴포넌트와 소통하며 서로의 내부 동작 방식에는 전혀 개의치 않음, 정보 은닉 혹은 캡슐화라고 하는 이 개념은 소프트웨어 설계의 근간이 되는 원리임
정보은닉은 시스템을 구성하는 컴포넌트들을 서로 독립시켜서 개발, 테스트, 최적화, 적용, 분석, 수정을 개별적으로 할 수 있게 해주는 것과 연관이 있음
장점은 여러가지 존재함
자바는 정보 은닉을 위한 다양한 장치를 제공함, 그 중 접근 제어 메커니즘은 클래스, 인터페이스, 멤버의 접근성을 명시함
각 요소의 접근성은 그 요소가 선언된 위치와 접근 제한자로 정해짐, 이것을 제대로 활용하는 것이 핵심임
모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 함, 즉 소프트웨어가 올바로 동작하는 한 항상 가장 낮은 접근 수준을 부여해야 함
톱레벨 클래스와 인터페이스에 부여할 수 있는 접근 수준은 package-private
과 public
두 가지임
public
으로 선언하면 공개 API가 됨package-private
로 선언하면 해당 패키지 안에서만 이용할 수 있음패키지 외부에서 쓸 이유가 없다면 package-private
선언이 나음
public
으로 선언시, API가 되므로 하위 호환을 위해 영원히 관리해줘야함
한 클래스에서만 사용하는 package-private
톱레벨 클래스나 인터페이스는 이를 사용하는 클래스 안에 private static
으로 중첩시켜보자
private static
으로 중첩시키면 바깥 클래스 하나에서만 접근할 수 있음public
일 필요가 없는 클래스의 접근 수준을 packaga-private
톱레벨 클래스로 좁히는 일
public
클래스는 그 패키지의 API인 반면, package-private
톱레벨 클래스는 내부 구현에 속함필드, 메서드, 중첩 클래스, 중첩 인터페이스 등에 부여할 수 있는 접근 수준
private
: 멤버를 선언한 톱레벨 클래스에서만 접근할 수 있음package-private
: 멤버가 소속된 패키지 안의 모든 클래스에서 접근할 수 있음, 접근 제한자를 명시하지 않았을 때 적용되는 패키지 수준임(단, 인터페이스의 멤버는 기본적으로 public
이 적용됨)protected
: package-private
의 접근 범위를 포함하며, 이 멤버를 선언한 클래스의 하위 클래스에서도 접근할 수 있음public
: 모든 곳에서 접근할 수 있음클래스의 공개 API를 세심히 설계한 후, 그 외의 모든 멤버는 private
으로 만들자, 그런 다음 오직 같은 패키지의 다른 클래스가 접근해야 하는 멤버에 한하여(private
제한자를 제거해) package-private
으로 풀어주자
private
과 package-private
멤버는 모두 해당 클래스의 구현에 해당하므로 보통은 공개 API에 영향을 주지 않음(단, Serializable을 구현한 클래스에서는 그 필드들도 의도치 않게 공개 API가 될 수도 있음)
public
클래스에서는 멤버의 접근 수준을 package-private
이나 protected
로 바꾸는 순간 그 멤버에 접근할 수 있는 대상 범위가 엄청나게 넓어짐
public
클래스의 protected
멤버는 공개 API이므로 영원히 지원돼야 함, 또한 내부 동작 방식을 API 문서에 적어 사용자에게 공개해야 할 수도 있음, 따라서 protected
멤버의 수는 적을수록 좋음
상위 클래스의 메서드를 재정의할 때는 그 접근 수준을 상위 클래스보다 좁게 설정할 수 없음, 이 규칙은 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체해 사용할 수 있는 규칙(리스코프 치환 원칙)을 지키기 위해서 필요함, 이 규칙을 어기면 컴파일 오류가 남
클래스가 인퍼테이스를 구현하는 건 이 규칙의 특별한 예이고 이때 클래스는 인터페이스가 정의한 모든 메서드를 public
으로 선언해야함
public
클래스의 인스턴스 필드는 되도록 public
이 아니어야 함
final
이 아닌 인스턴스 필드를 public
으로 선언하면 그 필드에 담을 수 있는 값을 제한할 힘을 잃게됨(그 필드와 관련된 모든 것은 불변식을 보장할 수 없게 된다는 뜻)필드가 수정될 때 다른 작업을 할 수 없게 되므로 public
가변 필드를 갖는 클래스는 일반적으로 스레드 안전하지 않음
정적 필드에서도 해당 클래스가 표현하는 추상 개념을 완성하는 데 꼭 필요한 구성요소로써의 상수라면 public static final
필드로 공개해도 좋음
_
)을 넣음길이가 0이 아닌 배열은 모두 변경 가능하니 주의하자
클래스에서 public static final
배열 필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공해서는 안됨
이러면 클라이언트에서 내용 수정이 가능하고 보안 허점이 생김
public static final Thing[] VALUES = { ... };
이를 해결하기 위한 방법은 2가지가 존재함
앞 코드의 public
배열을 private
으로 만들고 public
불변 리스트를 추가하는 것
private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
배열을 private
으로 만들고 그 복사본을 반환하는 public
메서드를 추가함(방어적 복사)
private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
}
둘 중 나은 방식에 대해서 나은 쪽을 생각하면 됨
자바 9에서는 모듈 시스템이라는 개념이 도입되어서 암묵적 접근 수준이 추가됨
모듈은 패키지들의 묶음임, 모듈은 자신에 속하는 패키지 중 공개(export)할 것들을 선언함
protected
혹은 public
멤버라도 해당 패키지를 공개하지 않았다면 모듈 외부에서는 접근할 수 없음
모듈 시스템을 활용하면 클래스를 외부에 공개하지 않으면서도 같은 모듈을 이루는 패키지 사이에서는 자유롭게 공유할 수 있음
모듈 시스템?
일반적으로 프로젝트를 만들면 아래와 같은 구조로 만들어짐
프로젝트명대로 만들어지고 그 하위에 패키지를 만들고 클래스를 만들고 작업 처리를 함
이 모듈 시스템을 통해서 아이템 15에서 말한대로 접근 권한을 최소화하고 분리를 시켜서 나눈다는 것이 시스템적으로 모듈로 묶어서 처리하는 것임
즉 위의 사진으로 따지면 src
하위의 다양한 패키지를 바탕으로 클래스를 나누지만 여기서 모듈을 활용한다면 Algorithm
외에 또 하나의 위와 같은 패키지 구조를 가질 수 있는 모듈을 더 생성해서 이를 통해서 분리를 하는 것임
이는 그러면 아이템 15에서 계속 말한대로 정보은닉, 캡슐화 하는 서로 독립시켜 개발하는 이 독립성을 더더욱 보장 받을 수 있음
일례로 안드로이드 역시 이와 같은 프로젝트 구조를 사용해서 예를 든다면 app
모듈 안에서 여러 컴포넌트 요소들을 만들고 패키지구조를 분리해서 관리하지만 이를 더 확실하게 독립시키고 처리하기 위해서 레이어 단위로 모듈을 분리해서 처리할 수 있음
즉, 이는 View만을 처리하는 레이어, Data만을 관리해서 처리하는 레이어로 각각 모듈로 관리함으로써 해당 모듈에는 딱 해당 관심사에 대해서만 관리하고 코딩을 하는 것인데 이는 어찌보면 아이템 15에서 말한 주장에서의 더 효율적인 관리가 될 수 있음
그리고 이 모듈들의 경우 완벽히 독립되어 있음과 동시에 이를 사용하는데 있어서 의존성 추가를 통해서 서로 참조를 할 수 있고 이는 아이템 15 마지막에서 말하는 모듈을 활용해 더더욱 아이템 15에서 주장하는 바를 더더욱 효과적으로 사용할 수 있음(물론 규모가 그 정도로 크지 않다면 오히려 비효율적일 수도 있음)