[테스트 주도 개발] 30장_디자인 패턴

TDD 에서 쓰일 수 있는 디자인 패턴들을 정리한다.

커맨트 패턴

간단한 메서드 호출보다 복잡한 형태의 계산 작업에 대한 호출이 필요하다면 어떻게 할까?
계산 작업에 대한 객체를 생성해서 이를 호출하면 된다. Runnable 인터페이스가 대표적인 예이다.

값 객체

공유되어야 하지만, identity (동일성) 은 중요하지 않을 때 겍체를 어떤식으로 설계할까?
객체가 생성될 때 객체의 상태를 설정한다. 그리고, 이 상태가 절대 변할 수 없도록 해라. 이 객체에 대해 수행되는 연산은 언제나 새로운 객체를 반환하게 만들어라.
별칭 문제란, 두 객체가 제 삼의 다른 객체에 대한 참조를 공유하는데, 한 객체가 공유 객체의 상태를 변화시키면, 다른 객체가 영향을 받는 문제이다. 해결하기 위한 방법으로,

  1. 의존하는 객체애 대한 참조를 외부로 알리지 않는 방법이다. 그 대신 객체에 대한 복사본을 제공한다. 이 방법의 문제는, 수행 시간이나 메모리 공간 측면에서 비효율적이다. 또한, 공유 객체의 상태 변화를 공유할 수 없다.
  2. 옵저버 패턴. 의존하는 객체에 자기를 등록해 놓고, 객체의 상태가 변하면 통지를 받는다. 이 방법의 문제는, 제어 흐름이 어렵고 의존성을 설정하고 제거하기 위한 로직이 지저분해진다.
  3. 객체를 덜 객체답게 취급. 객체는 시간의 흐름에 따라 변할 수 있는 상태를 가진다. 그런데, 이러한 특징을 제거해버려라. 이것이 값 객체이다.

모든 값 객체는 동등성을 구현해야한다. 만약,

  1. 계약을 표현하는 객체가 두 개가 있는데, 둘이 서로 같은 객체가 아니면 이 둘은 동등한것이 아니라 다른것이다.
  2. 하지만, 5프랑 짜리 동전 두개가 있는데, 이 동전들이 동일한 (identity) 동전인지는 중요하지 않다. 5프랑은 5프랑이다. 이들은 동등해야한다.(equality)
널 객체

객체의 특별한 상황은 어떻게 표현하나?

그 상황을 표현하는 새로운 객체를 만들어라. Java.io.File 에는 getSesurityManager() 의 반환값이 null 인지 항상 검사를 한다. 다른 방법은 새로운 클래스를 만드는 것이다.

1
2
3
public static SecurityManager getSecurityManager(){
return security == null ? new LaxSecurity() : security;
}
템플릿 메서드

작업 순서는 변하지 않지만, 각 작업 단위에 대한 미래의 개선 가능성을 열어두고 싶으면?
다른 메서드들을 호출하는 내용으로만 이루어진 메서드를 만들어라.

플러거블 객체

변이를 어떻게 표현?
명시적인 조건을 사용해라. 다음은 중복 조건문들이 정의된 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class SelectionTool {
Figure selected;

public void mouseDown() {
selected = findFigure();

if (selected != null) {
select(selected);
}
}

public void mouseMove() {
if (selected != null) {
move(selected);
} else {
moveSelectionRectangle();
}
}

public void mouseUp() {
if (selected == null) {
selectAll();
}
}
}

해법으로, 다음과 같이 플러거블 객체인 SelectionMode 를 만들어라.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SelectionTool {
SelectionMode mode; // 플러거블 객체

public void mouseDown() {
selected = findFigure();

if (selected != null) {
mode = new SingleSelection(selected); // 구현체
} else {
mode = MultipleSelection(); // 구현체
}
}

public void mouseMove() {
mode.mouseMove();
}

public void mouseUp() {
mode.mouseUp();
}
}
플러거블 셀렉터

인스턴스 별로 서로 다른 메서드가 동적으로 호출하게 하려면?
메서드의 이름을 저장하고 있다가 그 이름에 해당하는 메서드를 동적으로 호출해라. 다음과 같이 새로운 종류 출력문을 추가할 때마다 switch 문을 변경할 것인가.

1
2
3
4
5
6
7
8
9
10
void print() {
switch (printMessage) {
case "printHTML":
printHtml();
break;
case "printXML":
printXML();
break;
}
}

플러거블 셀렉터는, 리플랙션을 이용해서 동적으로 메서드를 호출한다.

1
2
3
4
void print() {
Method method = getClass().getMethod(printMessage, null);
method.invoke(this, new CLass[0]);
}

메서드를 달랑 한개만 가지는 하위 클래스들이 여러 개 있을 때, 확실히 직관적인 상황에서 코드 정리 목적으로 사용해라.

팩토리 메서드

다음 메서드가, 팩토리 메서드이다. 객체를 생성하기 때문이다.

1
2
3
static Dollar dollar(int amount) {
return new Dollar(amount);
}

클라이언트는 다음과 같이 사용한다.

1
Dollar five = Money.dollar(5);

팩토리 메서드의 단점은, indirection 이다. 메서드가 생성자처럼 생기지 않았지만, 이 안에서 객체를 생성한다는 사실을 기억해야한다. 유연함이 필요 없다면, 생성자를 써라.

사칭 사기꾼

기존의 코드에 새로운 변이를 도입하려면?
기존의 객체와 같은 프로토콜(인터페이스) 를 갖지만 구현은 다른 새로운 객체를 추가해라.
예를 들면,

  1. 널 객체 : 데이터가 없는 상태를, 데이터가 있는 상태와 동일하게 취급 가능
  2. 컴포지트 : 객체의 집합을 단일 객체 처럼 취급 가능.
컴포지트

하나의 객체가 다른 객체 목록의 행위를 조합한 것처럼 행동하게 하려면?
객체 집합을 나타내는 객체를 단일 객체에 대한 임포스터로 구현해라. ( Folder 는 Folder 를 포함한다. )

수집 매개변수

여러 객체에 걸쳐 있는 오퍼레이션의 결과를 수집하라면?
결과가 수집될 객체를 각 오퍼레이션의 매개 변수로 추가해라.

1
2
3
4
5
6
7
8
9
10
11
12
13
String toString() {
IndentingStream writer = new IndentingStream();
toString(writer);

return writer.contents();
}

void toString(IndentingStream writer) {
writer.println("+");
writer.index();
augend.toString(writer);
...
}

테스트 주도 개발 <켄트 벡>

Comments