디자인 패턴

우리가 언제나 새로운 문제를 푸는 것 같지만, 외부의 문제보다 사용하는 도구에 의한 내부의 문제를 푸는 경우가 많고, 공통의 해결책을 가진 문제를 발견하는 경우도 많다.
디자인 패턴의 성공은 객체 프로그래머들이 보는 공통성에 대한 증거이다.

카멘드

간단한 메서드 호출보다 복잡한 형태의 계산 작업에 대한 호출이 필요할 때, 계산 작업에 대한 객체를 생성해 이를 호출한다.
복잡한 계산 작업 호출을 위해 메서드 호출보다 객체를 사용하면 더 구체적이고 조작하기 쉬워진다.
호출 자체를 나타내는 객체를 만드는 것으로, 자바의 Runnable 인터페이스가 하나의 예이다.

값 객체

객체를 공유하였는데, 다른 곳에서 나도 모르게 객체의 값을 바꿀때 생기는 문제를 별칭 문제라 한다.

  • 두 객체가 제 삼의 다른 객체에 대한 참조를 공유하는데, 한 객체가 공유되는 객체의 상태를 변화시키는 것.
  1. 현재 의존하는 객체에 대한 참조를 외부로 보내지 않는 방법.
    • 객체에 대한 복사본을 제공하는 방식.
    • 단점은 수행 시간과 메모리 공간을 차지한다는 점. 객체의 변화를 공유하고 싶은 경우에 사용할 수 없다는 점.
  2. 옵저버 패턴
    • 의존하는 객체에 자신을 등록하고 객체의 상태가 변하면 통지를 받는 방법.
    • 흐름 이해를 어렵게하고, 로직이 지저분해질 수 있음.
  3. 값 객체
    • 객체를 값처럼 사용하는 것.
    • 모든 오퍼레이션은 기존의 객체는 변하지 않는 값으로 두고, 새로운 객체를 반환해야 함.
    • 예를들면 500원 동전 두 개는 동등성을 띄지만, 동일성을 띄지는 않음.
    • 500원에 500원을 더하면 새로운 1000원 객체를 돌려줘야지, 500원 값 객체의 값이 1000원으로 바뀌는 것이 아님.

널 객체

객체의 특정 상황을 표현하고자 할 때는, 특별한 상황에 맞는 객체를 만들면 된다.
널 체크를 계속하는 것이 보기 싫은 상황에서 아래와 같은 코드들로 널 상황에서 반환하는 객체를 통해 널 체크를 하지 않을 수 있다.

TestObject obj = System.getTestObject();
if (obj != null)
  obj.doSomething();

위와 같은 코드를 아래와 같이 수정한다

// NullObject class
public void doSomething() {}

// TestObject class
public static TestObject getTestObject() {
    return obj == null ? new NullObject() : obj;
}

TestObject obj = System.getTestObject();
... // do something

이런 코드는 잘못쓰이면 조건문 관련 2line을 없애고자 10line 가량을 생산해낼 수 있기 때문에, 논쟁이 있다.

템플릿 메서드

작업 순서는 변하지 않으면서, 각 작업 단위에 대한 개선 가능성을 열어두고 싶은 경우 이를 표현하는 방법이다.
다른 매서드들에 대한 호출로만 이루어진 메서드를 만든다.
템플릿 메서드는 고전적인 순서 등을 표현하는데 좋다.

  • 입력/처리/출력
  • 메시지 보내기/응답 받기
    템플릿 메서드는 초기 설계부터 구현하기 보다는 경험에 의해 구현되는 것이 좋다.
  • 두 하위 클래스의 연산 순서가 같은 것을 발견하면, 다른 부분을 추출하면서 남는 것이 템플릿 메서드

플러거블 객체

변이(같은 종, 다른 특성)을 어떻게 나타내는가에 대하여, 조건문으로 처리할 수 있지만, 여러 곳에서 지저분한 조건문이 사용된다면 이를 객체로 변환할 수 있다.

if (selected) { /* do something */}
else { /* do another thing */}

예를 들어 위와 같은 구문을 mode가 selected이냐에 따라 여러 함수에서 계속해서 처리하고 있었다면,

if (selected) mode = singleSelection();
else mode = multipleSelection();

과 같이 mode를 구현하는 객체들을 만들어 내부적으로 함수를 다르게 설정하면 된다.
플러거블이라는 거창한 이름이… 당연한 이런 구현에 왜 필요한가 싶다.

플러거블 셀렉터

인스턴스 별로 서로 다른 메서드를 동적으로 호출하는 방법.

  1. 상속을 통해 하위 클래스를 각각 만듦.
    • 서로 다른 하위 클래스가 열 개가 있다면 이는 작은 변이를 다루기엔 무거운 기법.
  2. switch 문을 갖는 하나의 클래스를 가짐.
    • 각 객체마다 값을 가지고, 이 값에 따른 switch문을 처리하는 클래스를 가지는 기법.
  3. 플러거블 셀렉터
    • 리플렉션을 이용해 동적으로 메서드 호출.
    • 메서드를 직관적으로 추적하기 어렵기 때문에 과용해서는 안됨.
    • 메서드를 딱 하나 가지는 여러 하위 클래스의 뭉치들에서 사용하기 좋음.

팩토리 메서드

새 객체를 만들때 유연성을 원하는 경우, 생성자 대신 메서드를 통해 객체를 생성하는 방법이다.
메서드라는 인디렉션을 도입하여 유연성을 갖게되지만, 유연성이 필요한 경우에만 사용하여야하며, 그렇지 않을 때는 생성자를 사용하는 것으로 충분하다.

컴포지트

하나의 객체가 다른 객체 목록의 행위를 조합한 것처럼 행동하게 만들려고 할 때, 객체 집합을 나타내는 객체를 단일 객체에 대한 임포스터(사기꾼?)로 구현한다. 컴포지트 패턴은 일종의 프로그래머의 트릭이다. Folder가 Folder를 포함하고, Drawing은 Drawing을 포함하는 것 등은 실세계와 잘 들어맞지 않지만, 코드를 훨씬 단순하게 만든다.

수집 매개 변수

log에 append하듯이, stream에 write하듯이, 이어서 사용하는 것.
android phone의 dump 로그와 유사.

싱글톤

싱글톤은 어디서든 하나의 객체를 가져서 값을 동일하게 하는 디자인 패턴. 전역변수를 제공하지 않는 곳에서 전역변수처럼도 사용할 수 있다고 한다.


리팩토링

아무리 큰 변화라도 작은 단계들로 변화시키는 방법.
일반적으로 리팩토링은 어떤 상황에서도 프로그램의 의미론을 변경해서 안되지만, TDD에서는 통과하는 테스트 안에서 상수를 변수로 바꾸는 등의 행동들도 리팩토링이라고 부른다. 이런 행위들이 테스트 통과에 변화를 주지 않기 때문이다.

차이점 일치시키기

비슷해 보이는 두 코드 조각을 합치기 위해서, 단계적으로 두 코드가 닮아가게끔 수정한다. 완전히 동일해지면 둘을 합친다.

  • 비슷한 반복문, 비슷한 분기, 비슷한 클래스를 동일하게 만들고 제거
    리팩토링 중 추론이 길어지면 가정으로 넘어가기도 하는데, 이런 작업을 명확하게 하기 위해 작은 단계를 밟아가는 것이다.

변화 격리하기

객체나 메서드 일부만을 바꾸려고 할 때, 바꿀 부분을 격리함. 외과의가 수술부위만 두고 나머지는 천으로 가리는 것처럼..

  • 메서드 추출, 객체 추출 등

데이터 이주시키기