인자에 손대지 말자
class Cash {
private int dollars;
Cash(String dlr) {
this.dollars = Integer.parsInt(dlr);
}
}
인자를 정수로 표현할 필요는 있지만, 생성자 내 객체 초기화에는 코드가 없어야 하고 인자를 건드려서는 안됩니다
대신, 필요하다면 인자들을 다른 타입의 객체로 감싸거나 가공하지 않은 형식으로 캡슐화해야 합니다
class Cash {
private Number dollars;
Cash(String dlr) {
this.dollars = new StringAsInteger(dlr);
}
}
class StringAsInteger implements Number {
private String source;
StringAsInteger(String src) {
this.source = src;
}
int intValue() {
return Integer.parseInt(this.source);
}
}
차이점은 아래와 같습니다
첫 번째 예제는 객체를 초기화하는 시점에서 곧장 텍스트를 숫자로 변환합니다
두 번째 예제는 실제로 사용하는 시점까지 객체의 변환 작업을 연기합니다
앞에서 배웠던 내용에 따라 Cash 클래스는 주 생성자 1개와 부 생성자 1개를 포함하도록 합니다
class Cash {
private Number dollars;
Cash(String dlr) {
this(new StringAsInteger(dlr));
}
Cash(Number dlr) {
this.dollars = dlr;
}
}
표면적으로 첫 번째 예제 생성자는 숫자 5를 캡슐화 합니다
Cash five = new Cash("5");
두 번째 예제는 StringAsInteger 인스턴스를 캡슐화합니다
진정한 객체지향에서 인스턴스화란 더 작은 객체들을 조합해서 더 큰 객체를 만드는 것을 의미합니다
객체를 조합해야 하는 단 하나의 이유는 새로운 계약을 준수하는 새로운 엔티티가 필요하기 때문입니다
- 객체를 인스턴스화하기
- 객체가 우리를 위해 작업을 하게 하기
이 두 단계는 겹쳐서는 안됩니다
생성자는 코드가 없어야하고, 오직 할당문만 포함해야 합니다
이 조언을 지지하는 이유는 생성자에 코드가 없을 경우 성능 최적화가 더 쉽기 때문에 코드 실행 속도가 더 빨라지기 때문입니다
class StringAsInteger implements Number {
private String text;
public StringAsInteger(String txt) {
this.text = txt;
}
public int intValue() {
return Integer.parseInt(this.text);
}
}
intValue()를 호출할 때마다 매번 텍스트를 정수로 파싱하는 것처럼 보이고 실제로 그렇게 동작합니다
Number num = new StringAsInteger("123");
num.intValue();
num.intValue();
오히려 생성자에서 한번 텍스트 파싱을 넣는게 더 효율적입니다
그렇다면 왜 이렇게 캡슐화로 감싸줘야할까요?
우선 객체를 생성할 때마다 매번 파싱을 수행하여 CPU 시간을 소모합니다
파싱이 필요없는 경우 수행하지 않도록 막을 수 없습니다
요청이 있을 때, 파싱하도록 하면 클래스의 사용자들이 파싱 시점을 자유롭게 결정할 수 있습니다
또한, 파싱이 여러번 수행되지 않고 싶다면 데코레이터를 추가해서 최초의 파싱 결과를 캐싱할 수 있습니다
class CachedNumber implements Nubmer {
private Number origin;
private Collection<Integer> cached = new ArrayList<>(1);
public CachedNumber(Number num) {
this.origin = num;
}
public int intValue() {
if (this.cached.isEmpty()) {
this.cached.add(this.origin.intValue());
}
return this.cached.get(0);
}
Number num = new CachedNumber(new StringAsInteger("123"));
num.intValue(); // 첫 번째 파싱
num.intValue(); // 여기서부터 캐싱 데이터 사용
이제 제어하기 쉽고 투명합니다
객체를 인스턴스화하는 동안에는 객체를 만드는 일 이외에는 어떤 일도 수행하지 않습니다
실제 작업은 객체의 메서드가 수행합니다
더불어 직접 이 과정을 제어할 수 있고 객체가 동작하는 동안 최적화할 수 있습니다
분명 파싱이 단 한번만 수행되는 경우도 있습니다
하지만 일관성이라는 측면에서 위 처럼 구현하는게 좋습니다
이 클래스의 미래는 보장할 수 없습니다
가벼운 생성자는 설정하기 쉽고 투명하게 사용할 수 있기 때문에 객체를 더 빠르게 만들 수 있습니다
이 글은 학습을 위해 “엘레강트 오브젝트" 책 내용을 정리한 글입니다.
저작권 관련 문제가 있다면 바로 삭제하도록 하겠습니다
'Java > Elegant Objects' 카테고리의 다른 글
[엘레강트 오브젝트] 2. 학습 - (3) 항상 인터페이스를 사용하세요 (0) | 2021.09.05 |
---|---|
[엘레강트 오브젝트] 2. 학습 - (2) 최소한 뭔가는 캡슐화하세요 (0) | 2021.09.05 |
[엘레강트 오브젝트] 2. 학습 - (1) 가능하면 적게 캡슐화하세요 (0) | 2021.09.04 |
[엘레강트 오브젝트] 1. 출생 - (2) 생성자 하나를 주 생성자로 만드세요 (0) | 2021.09.02 |
[엘레강트 오브젝트] 1. 출생 - (1) '-er'로 끝나는 이름을 사용하지 마세요 (0) | 2021.09.01 |
댓글