Item 15 : 클래스와 멤버의 접근 권한을 최소화하라
잘 설계된 컴포넌트는 내부 구현을 완벽히 숨겨서, 구현과 API를 깔끔히 분리한다.
오직 API를 통해서만 다른 컴포넌트와 소통하며 서로의 내부 동작 방식에는 전혀 개의치 않는다.
여기서 말하는 것은 정보 은닉, 캡슐화이다.
정보 은닉의 장점은 컴포넌트들을 서로 독립시켜서 개발, 테스트, 최적화, 적용, 분석, 수정을 개별적으로 할 수 있게 해 준다.
정보 은닉, 캡슐화의 장점
- 개발 속도 향상 : 여러 컴포넌트를 병렬로 개발하는 것이 가능하다.
- 관리 비용 절감 : 각 컴포넌트를 더 빨리 파악할 수 있고, 다른 컴포넌트로 교체하는 부담도 적다.
- 성능 최적화에 기여 : 시스템 전체에서 최적화할 컴포넌트를 정해서, 특정 컴포넌트만 최적화할 수 있다.
- 재사용성 증가 : 외부에 의존하지 않고 독자적으로 동작할 수 있다면 재사용성이 증가한다.
- 모듈화 : 시스템이 아직 완성되지 않은 상태에서도 개별 컴포넌트의 동작을 검증할 수 있다.
정보 은닉의 원칙
모든 클래스와 멤버의 접근성을 가능한 좁혀야 한다.
Java에서 접근 제어 지시자는 private / package-private(default) / protected / public 4가지가 있다.
클래스의 공개 API를 세심히 설계한 뒤, 그 외의 모든 멤버는 private으로 선언한다.
여기서 공개 API는 외부에서 사용할 수 있도록 한 메서드를 뜻한다.
또한, public class의 인스턴스 필드는 되도록 public이 아니어야 한다. 필드가 가변 객체를 참조하거나, final이 아닌 인스턴스 필드를 public으로 선언하면, 그 필드에 대한 제한할 힘들 잃게 된다. 즉, 불변성을 알 수 없게 된다.
만약 외부에 공개해야 할 꼭 필요한 상수라면 public static final 필드로 공개하자.
이런 상수는 대문자, 언더바를 이용하여 변수명을 정한다.
ex) public static final String SOME_CONSTANT = "hello"
public static final 배열의 허점
public static final Thing[] VALUES = {...};
위와 같은 배열은 외부에서 수정할 수 있다고 한다.
따라서 아래와 같은 방식으로 이 배열의 보안을 보장하자.
(방법 1)
private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Integer> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
(방법 2)
private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values(){
return PRIVATE_VALUES.clone();
}
정리하자면 프로그램 요소에 대한 접근성은 가능한 한 최소한으로 하고, 꼭 필요한 것만 public으로 선언하여 외부에서 접근할 수 있도록 하자.
Item 17 : 변경 가능성을 최소화하라
불변 클래스는 그 인스턴스의 내부 값을 변경할 수 없는 클래스를 말한다.
이 불변 인스턴스의 정보는 객체가 생성될 때부터 파괴되는 순간까지 절대 값이 달라지지 않는다.
String, primivite type의 Wrapper class 들, BigInteger, BigDecimal 등이 불변 클래스에 속한다.
불변 클래스의 규칙
- 객체의 상태를 변경하는 메서드(setter)를 제공하지 않는다.
- 클래스를 확장할 수 없도록 한다.
- 모든 필드를 final로 선언한다.
- 모든 필드를 private으로 선언한다.
- 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
불변 클래스 예제
복소수(실수부와 허수부로 구성된 수)를 표현하는 불변 클래스이다.
final class Complex{
private final double re;
private final double im;
public Complex(double re, double im){
this.re = re;
this.im = im;
}
public double realPart(){ return re; }
public double imaginaryPart(){ return im; }
public Complex plus(Complex c){
return new Complex(re + c.re, im + c.im);
}
public Complex minus(Complex c){
return new Complex(re - c.re, im - c.im);
}
public Complex times(Complex c){
return new Complex(re * c.re - im * c.im, re * c.im + im * c.re);
}
public Complex divideBy(Complex c){
double tmp = c.re * c.re + c.im * c.im;
return new Complex((re * c.re + im * c.im) / tmp, (im * c.re - re * c.im) / tmp);
}
}
- 이 클래스는 필드를 private final로 설정하고 setter를 제공하지 않아서 외부에서 변경할 수 없도록 하였다.
- public 생성자를 통해 객체 생성시에만 필드 값을 설정할 수 있다.
- 복소수를 나타내기 때문에 특수하게 getter 대신 의미 있는 메서드 realPart(), imaginaryPart() 메서드를 제공한다.
- 복소수의 사칙연산 메서드에서는 인스턴스 자신을 수정하지 않고 새로운 인스턴스를 만들어서 반환한다.
메서드 명을 add와 같은 동사 대신, plus와 같은 전치사를 사용한 점도 특이하다. 이 부분은 해당 메서드가 객체의 값을 변경하지 않는다는 사실을 명시 또는 강조하고 있다.
불변 객체의 특징
단순하다.
생성된 시점부터 파괴될 때까지 값이 변경되지 않는다. 따라서 개발자가 값의 유효성을 시시때때로 판단할 필요가 없다.
반면에 가변 객체는 상태가 바뀔 가능성을 고려해야 한다.
Thread-safe 하다.
불변 객체는 따로 동기화할 필요가 없다. 여러 스레드에서 동시에 사용해도 절대 훼손되지 않는다.
어차피 내부의 값을 변경할 수 없기 때문에, 여러 스레드에서 동시에 접근해도 객체에 영향을 줄 수 없다.
안심하고 공유할 수 있다.
불변 객체는 안심하고 공유할 수 있기 때문에, 한번 만든 인스턴스를 최대한 재활용하기에도 좋다.
예를 들면 아래와 같은 기본 상수들을 제공할 수도 있다.
public static final Complex ZERO = new Complex(0, 0);
public static final Complex ONE = new Complex(1, 0);
public static final Complex I = new Complex(0, 1);
정리
꼭 불변 클래스가 아니더라도 아래의 규칙을 따르도록 노력해 보자.
- setter는 꼭 필요한 경우에만 제공하자.
- 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.
- 불변 클래스가 아니더라도 클래스 내부에 변경할 수 있는 부분을 최대한 줄이자.
- 특별한 이유가 없다면 모든 필드는 private final이어야 한다.
- 생성자는 불변식 설정이 모두 완료되고 초기화가 완벽히 끝난 객체를 생성해야 한다.
'기타 > Book Review' 카테고리의 다른 글
[Effective Java] Item1 : 생성자 vs 정적 팩터리 메서드 (0) | 2024.03.14 |
---|---|
[CleanCode] 클린코드 리뷰_7장 : 오류처리 (0) | 2023.06.24 |
[CleanCode] 클린코드 리뷰_6장 : 객체와 자료구조 (0) | 2023.06.21 |
[Clean Code] 클린코드 리뷰_ 5장 : 형식 맞추기 (1) | 2023.06.17 |
[CleanCode] 클린코드 리뷰_ 4장 : 주석 (0) | 2023.06.16 |