"잘" 설계된 Component VS "어설프게" 설계된 컴포넌트
이 둘의 결정적 차이는
"(클래스 내부 테이터) && (내부 구현 정보)를 외부 컴포넌트로 부터 얼마나 잘 숨겼느냐" ,즉, 정보 은닉(캡슐화)를 얼마나 잘 했느냐이다.
정보 은닉(캡슐화)는 오직 API를 통해서만 다른 컴포넌트들과 소통하며 서로의 내부 동작에 전혀 관여하지 않는 것이 핵심이다.
Spring에서는 대표적으로 IoC 컨테이너
(Inversion of Control)와 DI (Dependency Injection)
를 사용하여 컴포넌트들을 관리한다.
장점
정보 은닉의 장점은,
시스템을 구성하는 컴포넌트들을 독립시켜서 개발, 테스트, 최적화, 분석, 수정 등을 개별적으로 할 수 있게하는 것과 연관이 깊다.
다음은 정보은닉의 장점이다.
시스템 개발 속도를 높인다.
시스템 관리 비용을 낮춘다.
성능 최적화에 도움이 된다. (성능을 높여주지는 않지만)
소프트웨어 재사용성을 높인다.
큰 시스템을 제작하는 난이도를 낮춘다.
정보은닉
을 위한 장치)그렇다면 어떻게 정보은닉을 할까.
바로 자바의 접근 제어 메커니즘이 그 예시이다.
각 요소의 접근성은
private
, protected
, public
)으로 정해진다.
그중에서도 2. 접근제한자를 제대로 활용하는 것이 정보 은닉의 핵심이라고도 할수 있다!
기본원칙: "모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 한다."
소프트웨어가 올바로 동작하는 한 항상 낮은 접근 수준을 부여해야 한다는 뜻이다.
톱레벨(가장 바깥) 클래스와 인터페이스에 부여할 수 있는 접근 수준은 package-private
과 public
두가지이다.
<<톱레벨의 의미>>
// MyClass.java (파일명과 클래스명이 동일해야 함)
public class MyClass {
// 클래스 내용...
}
톱레벨 클래스와 인터페이스를
public
으로 선언하면 공개 APIpackage-private
으로 선언하면 패키지 내부에서만 사용할 수 있다. 패키지 외부에 사용할 일이 없다면 가급적 package-private
을 권장한다. 이들은 내부 구현이 되어 언제든 수정이 가능하다. 즉, 클라이언트(코드)에 피해 없이 다음 릴리즈에서 수정, 교체, 제거를 할 수 있다.
반면 public
으로 선언하면 API가 되므로 하위호환을 위해 영원히 관리해야만 한다.
또다른 중요한 부분은 public
일 필요가 없는 클래스의 접근 수준을 package-private
톱레벨 클래스로 좁히는 일이다.
멤버 접근 지정자
다음은 멤버, 즉, ((필드, 메서드, 중첩클래스, 중첩 인터페이스))에 접근할 수 있는 수준 4가지이다.
private
: 멤버를 선언한 톱 클래스에서만 사용 가능package-private
: 패키지 내부에서만 접근 가능, 접근 지정자를 명시하지 않을 때 적용됨protected
: package-private
+ 이 멤버를 선언한 클래스의 하위 클래스에서도 접근 가능 public
: 모든 곳에서 접근 가능접근 지정자 설계 순서
private
로 만든다. package-private(default)
으로 풀어준다. 단, 권한을 풀어주는 일을 자주하게 된다면 컴포넌트 분해를 고려한다.
(예외) Serializable을 구현한 클래스에서는 필드들이 의도치 않게 공개 API가 될 수도 있다. [아이템 86,87]
package-private
에서 protected
로 바꾸는 순간 공개 API가 되므로 protected
멤버는 영원히 지원되어야 한다. 따라 내부 동작 방식을 API문서에 적어 사용자에게 공개할 수 있어야 한다. [item 19]
따라서 protected
는 멤버의 수는 적을수록 좋다.
멤버 접근성을 방해하는 제약
한편, 몇가지 제약사항이 있다.
바로, "상위 클래스의 메서드를 재정의 할 때는 그 접근 수준을 상위클래스에서보다 좁게 할 수 없다는 것"이다.
이는 리스코프 치환 원칙(상위클래스의 인스턴스가 하위 클래스의 인스턴스를 대체해 사용) [item 10]을 지키기 위해서 필요하다.
어기면 컴파일 오류가 발생한다.
[item 16] public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다.
이 두 경우는 그 필드에 제한 할 수 있는 능력이 사라진다. 즉, 불변성 보장이 안 된다.
또한 public 가변 필드를 갖는 클래스는 일반적으로 Thread-safe
하지 않다....
필드가 수정 될 때 다른 작업을 수행하지 못하기 때문이다.
예외는, 해당 클래스가 표현하는 "추상 개념"을 완성하는데 꼭 필요한 구성요소로써의 상수 (보통 단어 사이에 _(밑줄)과 함께 쓰임) 라면 public static final
필드로 공개 가능하다.
말이 어렵지만, 풀어 말하면 하위 클래스에서도 공통적으로 사용하는 상수라는 의미이다..
단, 반드시 기본 타입 값이나 불변 객체를 참조해야 한다. [item 17]
(예: Math 클래스의 수학적인 연산 PI, E 메서드)
class Thing {
int id;
public Thing(int id) {
this.id = id;
}
}
class NotSafeClass {
// 클라이언트에서 해당 필드에 참조하여 원본 배열을 변경가능
public static final Thing[] PRIVATE_VALUES = { new Thing(1), new Thing(2), new Thing(3)};
}
class SafeClass1 {
private static final Thing[] PRIVATE_VALUES = { new Thing(1), new Thing(2), new Thing(3)};
// public 불변 리스트 반환, 원본 배열에 접근 불가능
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
}
class SafeClass2 {
private static final Thing[] PRIVATE_VALUES = { new Thing(1), new Thing(2), new Thing(3)};
// public 복사본 배열 반환(방어적 복사), 원본 배열에 접근 불가능
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
}
}
자바 9에서 추가된 모듈 시스템을 통해 두 가지 암묵적 접근 수준(?)이 추가되었다.
즉, module의 공개(export)여부에 따라 접근이 되냐/안되냐 여부가 갈린다
앞서 언급했던 접근지정자는 (private/package-private/protected/public ) 네 가지 수준이라면 모듈접근은 export
/non-export
두 가지 수준이다.
++ 모듈은 패키지들의 묶음