본문 바로가기
Java/Elegant Objects

[엘레강트 오브젝트] 1. 출생 - (3) 생성자에 코드를 넣지 마세요

by 잭피 2021. 9. 2.
인자에 손대지 말자
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 인스턴스를 캡슐화합니다

진정한 객체지향에서 인스턴스화란 더 작은 객체들을 조합해서 더 큰 객체를 만드는 것을 의미합니다

객체를 조합해야 하는 단 하나의 이유는 새로운 계약을 준수하는 새로운 엔티티가 필요하기 때문입니다

  1. 객체를 인스턴스화하기
  2. 객체가 우리를 위해 작업을 하게 하기

이 두 단계는 겹쳐서는 안됩니다

생성자는 코드가 없어야하고, 오직 할당문만 포함해야 합니다

이 조언을 지지하는 이유는 생성자에 코드가 없을 경우 성능 최적화가 더 쉽기 때문에 코드 실행 속도가 더 빨라지기 때문입니다
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(); // 여기서부터 캐싱 데이터 사용

이제 제어하기 쉽고 투명합니다

객체를 인스턴스화하는 동안에는 객체를 만드는 일 이외에는 어떤 일도 수행하지 않습니다

실제 작업은 객체의 메서드가 수행합니다

더불어 직접 이 과정을 제어할 수 있고 객체가 동작하는 동안 최적화할 수 있습니다

 

분명 파싱이 단 한번만 수행되는 경우도 있습니다

하지만 일관성이라는 측면에서 위 처럼 구현하는게 좋습니다

이 클래스의 미래는 보장할 수 없습니다

 

가벼운 생성자는 설정하기 쉽고 투명하게 사용할 수 있기 때문에 객체를 더 빠르게 만들 수 있습니다

 

 

이 글은 학습을 위해 “엘레강트 오브젝트" 책 내용을 정리한 글입니다.

저작권 관련 문제가 있다면 바로 삭제하도록 하겠습니다

댓글