본문 바로가기
SW

테스트 주도 개발(Test-Driven Development:By Example) - 3부 : 테스트 주도 개발의 패턴 (30장)

by 라꾸스떼(YR) 2020. 2. 18.
반응형

[30장] 디자인 패턴

패턴의 주요한 통찰이 하나 있으니, 우리가 언제나 완전히 다른 문제들을 해결하는 것 같지만 우리가 푸는 문제 대다수는 사용하는 도구에 의해 생기는 것이지 직면한 외부의 문제 때문에 생기는 것이 아니라는 점이다. 이런 이유로, 심지어 외부적 문제 해결 컨텍스트가 엄청나게 다양하더라도 공통의 해결책을 가진 공통의 문제를 발견할 것을 기대할 수 있다.

디자인 패턴의 엄청난 성공은 객체 프로그래머들이 보는 공통성에 대한 증거다.

TDD에서는 설계를 디자인 패턴과는 조금 다른 관점으로 본다.

-커맨드:계산 작업에 대한 호출을 메시지가 아닌 객체로 표현한다.

-값 객체:객체가 생성된 이후 그 값이 절대로 변하지 안헥 하여 별칭 문제가 발생하지 않게 한다.

-널 객체:계산 작업의 기본 사례를 객체로 표현한다.

-템플릿 메서드:계산 작업의 ㅂ녀하지 않는 순서를 여러 추상 메서드로 표현한다. 이 추상 메서드들은 상속을 통해 특별한 작업을 수행하게끔 구체화된다.

-플러거블 객체:둘 이상의 구현을 객체를 호출함으로써 다양성을 표현한다.

-플러거블 셀렉터:객체별로 서로 다른 메서드가 동적으로 호출되게함으로써 필요 없는 하위 클래스의 생성을 피한다.

-팩토리 메서드:생성자 대신 메서드를 호출함으로써 객체를 생성한다.

-임포스터:현존하는 프롵콜을 갖는 다른 구현을 추가하여 시스템에 변이를 도입한다.

-컴포지트:하나의 객체로 여러 객체의 행위 조합을 표현한다.

-수집 매개 변수:여러 다른 객체에서 계산한 결과를 모으기 위해 매개 변수를 여러 곳으로 전달한다.

 

<커맨드>

간단한 메서드 호출보다 복잡한 형태의 계산 작업에 대한 호출이 필요하다면 어떻게 해야 할까? 계산 작업에 대한 객체를 생성하여 이를 호출하면 된다.

메시지를 보낸다는 것은 쉽게 말해서 메서드를 호출한다는 의미와 비슷하다.

메시지 하나를 보내는 것보다 호출이 조금 더 구체적이고 또 조작하기 쉬워지려면, 바로 객체가 해답이 된다.

 

<값 객체>

널리 공유해야 하지만 동일성(identity)은 중요하지 않을 때 객체를 어떤 식으로 설계할 수 있을까? 객체가 생성될 때 객체의 상태를 설정한 후 이 상태가 절대 변할 수 없도록 한다. 그리고 이 객체에 대해 수행되는 연산은 언제나 새로운 객체를 반환하게 만든다.

고전적인 별칭 문제. 두 객체가 제삼의 다른 객체에 대한 참조를 공유하고 있는데, 한 객체가 공유되는 객체의 상태를 변화시키면 나머지 다른 객체는 공유 객체의 상태에 의존하지 않는 편이 차라리 나을 것이다.

별칭 문제 해결 방법으로 한 가지는 현재 의존하는 객체에 대한 참조를 결코 외부로 알리지 않는 방법이다. 그 대신 객체에 대한 복사본을 제공하는 것이다. 또 다른 방법은 옵저버 패턴을 사용하는 것이다. 의존하는 객체에 자기를 등록해 놓고, 객체의 상태가 변하면 통지를 받는 방법이다.

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

동일성(identity)와 동등성(equality)은 서로 다르다. 5백원 동전 두 개가 서로 동등할지라도 동일하지는 않다.

 

<널 객체>

프로토콜이란 인터페이스와 비슷한 뜻이다. 같은 프로토콜을 제공한다는 말의 의미는 같은 인터페이스를 갖거나 해당 클래스의 하위 클래스가 되어야 한다는 뜻이다.

 

<템플릿 메서드>

작업 순서는 변하지 ㅇ낳지만 각 작업 단위에 대한 미래의 개선 가능성을 열어두고 싶은 경우 이를 어떻게 표현할 것인가? 다른 메서드들을 호출하는 내용으로만 이루어진 메서드를 만든다.

상위 클래스에는 다른 메서드를 호출하는 내용으로만 이루어진 메서드를 만들고, 하위 클래스에서는 이 각각의 메서드를 서로 다른 방식으로 구현한다.

템플릿 메서드를 만들 때 한 가지 문제는 하위 클래스를 위한 기본 구현을 제공할 것인가 말 것인가 하는 것이다.

두 하위 클래스에서 어떤 연산 순서의 두 가지 변주를 발견하면, 양자가 점차 가까워지도록 둘을 같이 움직여가야 한다. 나머지 메서드들과는 다른 부분을 추출해 내면 남는 것은 템플릿 메서드다. 그 다음 템플릿 메서드를 상위 클래스로 보내고 중복을 제거할 수 있다.

 

<플러거블 객체>

TDD의 두 번재 수칙이 중복을 제거하는 것이기 때문에, 명시적인 조건문이 전염되는 싹을 애초에 잘라버려야 한다. 조건문을 두 뻔재로 볼때가 바로, 객체 설계시의 가장 기초인 플러거블 객체를 끄집어낼 때다.

지저분한 중복 조건문들. 이 경우에 대한 해법은 플러거블 객체를 만드는 것이다.

명시적인 인터페이스를 사용하는 언어에서는 두 플러거블 객체가 동일한 인터페이스를 구현하게 해야 한다.

 

<플러거블 셀렉터>

인스턴스별로 서로 다른 메서드가 동적으로 호출되게 하려면 어떻게 해야 할까? 메서드의 이름을 저장하고 있다가 그 이름에 해당하는 메서드를 동적으로 호출한다. 한 가지 대안은 switch문을 갖는 하나의 클래스를 만드는 것이다. 필드의 값에 따라 서로 다른 메서드를 호출하면 된다.

-인스턴스 생성하는 곳

-switch 문

-메서드 자체

새로운 종류의 출력을 추가할 때마다 출력 메서드를 추가하고 switch문을 바꿔야 한다는 점을 기억해야 한다.

플러거블 셀렉터 해법은 리플랙션을 이용하여 동적으로 메서드를 호출하는 것이다.

메서드를 달랑 한 개만 가지는 하위 클래스들이 한 뭉치나 존재하는, 확실히 직관적인 상황에서 코드를 정리하기 위한 용도로만 플러거블 셀렉터를 사용해야 한다.

 

<팩토리 메서드>

새 객체를 만들 때 유연성을 원하는 경우 객체를 어떻게 생성하는가? 생성자를 쓰는 대신 일반 메서드에서 객체를 생성한다.

메서드라는 한 단계의 인디렉션(indirection)을 추가함으로써 테스트를 변경하지 않고 다른 클래스의 인스턴스를 반환할 수 있는 유연함을 얻었다.

이 메서드를 팩토리 메서드라 부른다. 객체를 생성하기 대문이다. 팩토리 메서드의 단점은 인디렉션에 있다. 메서드가 생성자처럼 생기지 않았지만 그 안에서 객체를 만든다는 사실을 기억해야만 한다. 유연함이 필요할 때에만 팩토리 메서드를 사용해야 한다. 그렇지 않다면 객체를 생성하는 데에는 생성자를 쓰는 것으로 충분하다.

 

<사칭 사기꾼:임포스터>

기존의 코드에 새롱누 변이를 도입하려면 어떻게 해야 할까? 기존의 객체와 같은 프로토콜을 갖지만 구현은 다른 새로운 객체를 추가한다.

그러나 이러한 변이를 도입할 때 여러 메서드를 수정해야 하는 경우가 종종 있다.

리팩토링 중에 나타나는 사칭 사기꾼의 예

-널 객체 : 데이터가 없는 상태를 데이터가 있는 상태와 동일하게 취급할 수 있다.

-컴포지트 : 객체의 집합을 단일 객체처럼 취급할 수 있다.

리팩토링 중에 사칭 사기꾼을 찾아내는 것은 중복을 제거하는 작업을 통해 유도된다. 다른 모든 리팩토링 역시 중복을 제거하는 작업에 의해 유도된다.

 

<컴포지트>

하나의 객체가 다른 객체 목록의 행위를 조합한 것처럼 행동하게 만들려면 어떻게 해야 할까? 객체 집합을 나타내는 객체를 단일 객체에 대한 임포스터로 구현한다.

컴포지트 패턴을 적용하는 것은 프로그래머의 트릭이지 세상 사람들에게 일반적으로 받아들여지는 것은 아니다. 리팩토링에 점차 익숙해지고 있으므로, 중복이 나타나는 그 순간에 컴포지트를 도입해보고 이로 인해 프로그램의 복잡함이 사라지는 지 관찰해볼 수 있다는 점이다.

 

<수집 매개 변수>

여러 객체에 걸쳐 존재하는 오퍼레이션의 결과를 수집하려면 어떻게 해야할까? 결과가 수집될 객체를 각 오퍼레이션의 매개 변수로 추가한다.

수집 매개 변수를 추가하는 것은 컴포지트의 일반적인 귀결이다.

 

<싱글톤>

전역 변수를 제공하지 않는 언어에서 전역 변수를 사용하려면 어떻게 해야 할까? 사용하지 마라.

반응형

댓글