1. 클래스와 멤버의 접근 권한을 최소화 하라.
- 잘 설계된 컴포넌트는 모든 내부 구현을 완벽히 숨겨, 구현과 API를 깔끔히 분리한다.
- 정보 은닉, 혹은 캡슐화라고 하는 이 개념은 소프트웨어 설계의 근간이 되는 원리이다.
- 접근 제어 메커니즘은 클래스, 인터페이스, 멤버의 접근성을 명시한다.
- private : 멤버를 선언한 톱레벨 클래스에서만 접근할 수 있다.
- package-private : 멤버가 소속된 패키지 안의 모든 클래스에서 접근할 수 있다.
(접근 제한자를 명시하지 않았을 때 적용되는 패키지 접근 수준, 단 인터페이스의 멤버는 기본적으로 public) - protected : package-private의 접근 범위를 포함하며, 이 멤버를 선언한 클래스의 하위 클래스에서도 접근 할 수 있다.
- public : 모든 곳에서 접근 할 수 있다.
- 클래스의 공개 API를 세심히 설계한 후, 그 외의 모든 멤버는 private으로 만든다. 그 후 오직 같은 패키지의 다른 클래스가 접근해야 하는 멤버에 한하여 package-private으로 풀어준다.
- 상위 클래스의 메서드를 재정의할 때는 그 접근 수준을 상위 클래스에서보다 좁게 설정 할 수 없다.
클래스에서 public static final 배열 필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공해서는 안된다.
이런 필드나 접근자를 제공한다면 클라이언트에서 그 배열의 내용을 수정할 수 있게 된다. 해결책은 두 가지이다.
// public 배열을 private으로 만들고 public 불변 리스트를 추가하는 것.
private static final Thing[] PRIVATE_VALUES = { ... };
public static fianl 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();
}
프로그램 요소의 접근성은 가능한 한 최소한으로 하라. 꼭 필요한 것만 골라 최소한의 public API를 설계하자. 그 외에는 클래스, 인터페이스, 멤버가 의도치 않게 API로 공개되는 일이 없도록 해야 한다. public 클래스는 상수용 public static final 필드 외에는 어떠한 public 필드도 가져서는 안된다. public static final 필드가 참조하는 객체가 불변인지 확인한다.
2. 변경 가능성을 최소화하라.
- 불변 클래스란 간단히 말해 그 인스턴스의 내부 값을 수정할 수 없는 클래스이다. 불면 인스턴스에 간직된 정보는 고정되어 객체가 파괴되는 순간까지 절대 달라지지 않는다. 클래스를 불변으로 만들려면 아래 다섯가지 규칙을 따르면 된다.
- 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
- 클래스를 확장할 수 없도록 한다. 상속을 막는 대표적인 방법은 클래스를 final로 선언하는 것이다.
- 모든 필드를 final로 선언한다.
- 모든 필드를 private으로 선언한다. 필드가 참조하는 가변 객체를 클라이언트에서 직접 접근해 수정하는 일을 막아준다.
- 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
- 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.
3. 상속보다는 컴포지션을 사용하라.
- 메서드 호출과 달리 상속은 캡슐화를 깨뜨린다. 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있다.
- 기존 클래스를 확장하는 대신, 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하게 한다. 기존 클래스가 새로운 클래스의 구성요소로 쓰인다는 뜻에서 이러한 설계를 컴포지션이라 한다.
4. 추상 클래스보다는 인터페이스를 우선하라.
- 자바가 제공하는 다중 구현 메커니즘은 인터페이스와 추상 클래스, 이렇게 두가지다. 자바 8부터 인터페이스도 디폴트 메서드(default method)를 제공할 수 있게 되어, 두 메커니즘 모두 인스턴스 메서드를 구현 형태로 제공 할 수 있다.
- 인터페이스로는 계층구조가 없는 타입 프레임워크를 만들 수 있다.
public interface Singer {
AudioClip sing(Song s);
}
public interface Songwriter {
Song compose(int chartPosition);
}
// Singer와 Songwriter 모두를 확장하고 새로운 메서드까지 추가한 제 3의 인터페이스 정의
public interface SingerSongwriter extends Singer, Songwriter {
AudioClip strum();
void actSensitive();
}
- 기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요한 경우가 아니면 피해야 한다.
추가하려는 디폴트 메서드가 기존 구현체들과 충돌하지는 않을지 심사숙고해야 함도 당연하다.
5. 인터페이스는 타입을 정의하는 용도로만 사용하라.
- 인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입 역할을 한다. 달리 말해, 클래스가 어떤 인터페이스를 구현한다는 것은 자신의 인스턴스로 무엇을 할 수 있는지를 클라이언트에 얘기해주는 것이다. 인터페이스는 오직 이 용도로만 사용해야 한다.
// 상수 인터페이스 안티 패턴 - 사용금지!
public interface PhysicalConstants {
// 아보가르드 수(1/몰)
static final double AVOGADROS_NUMBER = 6.022_140_857e23;
// 볼츠만 상수(J/K)
static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;
// 전자 질량(kg)
static final double ELECTRON_MASS = 9.109_383_56e-31;
}
- 상수 인터페이스 안티패턴은 인터페이스를 잘못 사용한 예다.
- 특정 클래스나 인터페이스와 강하게 연관된 상수라면 그 클래스나 인터페이스 자체에 추가해야 한다.
- 열거 타입(ENUM)으로 나타내기 적합한 상수라면 열거 타입으로 만들어 공개한다. 그것도 아니라면 인스턴스와할 수 없는 유틸리티 클래스에 담아 공개하자.
'Java > Effective Java 3' 카테고리의 다른 글
[이팩티브 자바3] 제 5장. 제네릭 (0) | 2020.11.08 |
---|---|
[이팩티브 자바3] 제 3장. 모든 객체의 공통 메서드 (0) | 2020.10.05 |
[이팩티브 자바3] 제 2장. 객체 생성과 파괴 (1) | 2020.10.01 |