길을 지나가는 사람에게 물어보는 방법은 기능적이고 해결책 지향적인 접근법,
길을 지도를 보고 찾아가는 방법은 구조적이고 문제 지향적인 접근법이다.
지도는 구체적인 기능이 아닌 구조를 제공하며, 다양한 목적을 위해 재사용될 수 있다.
지도는 기능에 비해 상대적으로 잘 변하지 않는 안정적인 지형 정보를 기반으로 하기 때문에, 요구사항이 변함에도 수용할 수 있다.
즉, 기능이 아니라 구조를 기반으로 모델을 구축하는 편이 좀 더 범용적이고 이해하기 쉬우며 변경에 안정적이다.
객체지향은 안정적인 구조를 기반으로 시스템을 구조화한다.
자주 변경되는 기능이 아니라 안정적인 구조를 따라 역할, 책임, 협력을 구성하라.
모든 소프트웨어 제품의 설계에는 기능과 구조의 측면이 존재하며 조화를 이루도록 해야 한다.
- 기능 측면: 제품이 사용자를 위해 무엇을 할 수 있는지
- 구조 측면: 제품의 형태가 어떠해야 하는지
소프트웨어를 개발하는 초기 단계에는 기능에 초점을 맞춰야 한다.
훌륭한 기능이 충분조건이라면, 훌륭한 구조는 필요조건이다.
단순하며 유지보수하기 쉬운 설계는 사용자의 변하는 요구사항을 반영할 수 있도록 하는 기반이 된다.
좋은 설계는 나중에라도 변경할 수 있는 여지를 남겨 놓는 설계다.
변경에 대비하고 변경의 여지를 남겨 놓는 가장 좋은 방법은 안정적인 구조를 중심으로 설계하는 것이다.
객체지향은 안정적인 객체 구조를 바탕으로 시스템 기능을 객체 간의 책임으로 분배한다.
객체지향을 위해서는 사용자에게 제공할 기능과 기능을 담을 안정적인 구조가 필요하다.
구조
구조는 사용자나 이해관계자들이 도메인에 관해 생각하는 개념과 개념들 간의 관계로 표현한다.
구조를 수집하고 표현하기 위한 기법을 도메인 모델링이라고 하며 결과물을 도메인 모델이라고 한다.
도메인 모델
사용자가 프로그램을 사용하는 대상 분야를 도메인이라고 한다.
도메인 모델이란 도메인에 관한 지식을 선택적으로 단순화하고 의식적으로 구조화한 형태다.
은행 도메인: 고객과 계좌 사이의 돈의 흐름
게임 도메인: 캐릭터와 몬스터, 몬스터가 떨구는 아이템 간의 관계
도메인 모델은 이해관계자들이 바라보는 멘탈 모델이다.
도널드 노먼은 제품에 관한 모든 것이 사용자들의 멘탈 모델과 일치해야 한다고 주장하며,
멘탈 모델을 사용자 모델, 디자인 모델, 시스템 이미지의 세 가지로 구분한다.
사용자 모델과 디자인 모델은 직접적으로 상호작용할 수 없으며, 최종 제품인 시스템 그 자체를 통해서만 가능하다.
도메인 모델은 이 세가지를 포괄하도록 추상화한 소프트웨어 모델이다. 즉, 소프트웨어에 대한 멘탈 모델이다.
즉, 최종 코드는 사용자가 도메인을 바라보는 관점을 반영해야 한다.
객체지향을 사용하면 사용자들이 이해하는 도메인의 구조와 최대한 유사하게 코드를 구조화할 수 있다.
이러한 특징을 연결완전성, 또는 표현적 차이라고 한다.
표현적 차이
소프트웨어 객체가 현실 객체를 왜곡하더라고 현실 객체의 특성을 토대로 구축된다.
이처럼 소프트웨어 객체와 현실 객체 사이의 의미적 거리를 가리켜 표현적 차이 또는 의미적 차이라고 한다.
핵심은 은유를 통해 이 차이를 최대한 줄이는 것이다. 은유 대상은 바로 도메인 모델이다.
코드는 도메인 모델의 개념과 관계를 은유해야 한다.
안정적인 구조를 제공하는 도메인 모델을 기반으로 소프트웨어의 구조를 설계하면,
변경에 유연하게 대응할 수 있는 탄력적인 소프트웨어를 만들 수 있다.
정기예금 도메인 모델
은행 도메인의 관점에서 정기예금은 금융상품의 일종이다.
정기예금은 예금 기간과 해지된 예금인지 여부를 나타내는 속성을 포함한다.
계좌는 특정한 정기예금에 속하며 예금액을 보관한다.
이자율은 특정한 정기예금에 대해 이자를 계산하기 위한 금리 정보를 포함한다.
이자는 만기 시 계좌의 예금액에 따라 결정된다. 다라서 계좌와 이자 사이에는 0 또는 1의 다중성이 존재한다.
정기예금의 기간이 반드시 이자의 지급일자와 동일하지는 않기 때문에 지급일자 정보를 별도로 보관해야 한다.
정기예금 상품을 매우 단순화했지만 일반적으로 사람들이 생각하는 개념과 규칙을 모두 포함한다.
이 모델에서 사용하고 있는 개념의 정의와 속성은 쉽게 바뀌지 않을 것이다.
이처럼 도메인 모델은 안정적인 구조를 제공한다.
어쨌거나 실제로 사용자에게 중요한 것은 도메인 모델이 아니라 소프트웨어의 기능이다.
기능
기능은 사용자의 목표를 만족시키기 위해 책임을 수행하는 시스템의 행위로 표현한다.
기능을 수집하고 표현하기 위한 기법을 유스케이스 모델링이라고 하며 결과물을 유스케이스라고 한다.
유스케이스
사용자의 목표를 달성하기 위해 사용자와 시스템 간에 이뤄지는 상호작용의 흐름을 텍스트로 정리한 것이다.
유스케이스는 시스템의 이해관계자들 간의 계약을 행위 중심으로 파악한다.
유스케이스는 이해관계자들 중 일차 액터의 요청에 대한 시스템의 응답이다.
요청과 관계되는 조건에 따라 서로 다른 시나리오가 전개될 수 있으며, 유스케이스는 시나리오를 묶어 준다.
일차 액터란 시스템의 서비스 중 하나를 요청하는 이해관계자다.
시스템과 연동하는 외부 시스템 역시 일차 액터다.
사용자 목표가 유스케이스의 핵심이다.
유스케이스는 공통의 사용자 목표를 통해 강하게 연관된 시나리오의 집합이다.
정기예금 이자 계산 유스케이스
정기예금 도메인에서 시스템은 중도 해지시 지급할 이자를 계산할 수 있는 기능을 제공해야 한다.
유스케이스명: 중도 해지 이자액을 계산한다
일차 액터: 예금주
주요 성공 시나리오:
1. 예금주가 정기예금 계좌를 선택한다.
2. 시스템은 정기예금 계좌 정보를 보여준다.
3. 예금주가 금일 기준으로 예금을 해지할 경우 지급받을 수 있는 이자 계산을 요청한다.
4. 시스템은 중도 해지 시 지급받을 수 있는 이자를 계산한 후 결과를 사용자에게 제공한다.
확장:
3a. 사용자는 해지 일자를 다른 일자로 입력할 수 있다.
1. 유스케이스는 사용자와 시스템 간의 상호작용을 보여주는 텍스트다. 다이어그램이 아니다.
2. 유스케이스는 하나의 시나리오가 아니라 여러 시나리오들의 집합이다.
시나리오는 유스케이스를 통해 시스템을 사용하는 하나의 특정한 이야기 또는 경로다.
시나리오를 유스케이스 인스턴스라고도 한다.
위 유스케이스는 2개의 시나리오를 포함한다. (당일까지의 이자액 계산, 특정 일자까지의 이자액 계산)
확장 시나리오의 3a라는 라벨은 주요 성공 시나리오의 3에 대한 대안적인 흐름이다.
3. 유스케이스는 단순한 피처 목록과 다르다. 피처는 기능 목록을 단순하게 나열한 것이다.
위 유스케이스에서 피처는 '시스템은 정기예금 정보를 보여준다', '시스템은 당일이나 현재 일자의 이자를 계산한다'이다.
두 피처를 '중도 해지 이자액을 계산한다'라는 유스케이스로 묶는 것이다.
유스케이스는 이야기를 통해 연관된 기능들을 함께 묶는다.
4. 유스케이스는 사용자 인터페이스와 관련된 세부 정보를 포함하지 말아야 한다.
위 유스케이스에 사용자가 해지 일자를 선택하기 위해 어떻게 사용자 인터페이스를 구성해야 하는지에 대한 정보는 없다.
유스케이스는 자주 변경되는 사용자 인터페이스 요소는 배제하고 사용자 관점에서 시스템의 행위에 초점을 맞춘다.
이러한 유스케이스 형식을 본질적인 유스케이스라고 한다.
5. 유스케이스는 내부 설계와 관련된 정보를 포함하지 않는다.
유스케이스는 설계 기법도, 객체지향 기법도 아니다.
유스케이스는 시스템의 외부 관점만을 표현한다. 유스케이스로부터 시스템의 내부 구조를 유추하지는 못한다.
유스케이스는 단지 기능적 요구사항을 사용자의 목표라는 문맥을 중심으로 묶기 위한 정리 기법이다.
유스케이스와 객체의 구조 사이에는 커다란 간격이 존재한다.
유스케이스에서 객체 설계로의 전환은 경험과 상식과 의사소통을 기반으로 한 창조 작업이다.
기능과 구조의 통합, 책임-주도 설계
도메인 모델은 안정적인 구조를 개념과하기 위한 도구, 유스케이스는 불안정한 기능을 서술하기 위한 도구다.
유스케이스에 정리된 시스템의 기능을 도메인 모델을 기반으로 한 객체들의 책임으로 분배해야 한다.
시스템은 사용자로부터의 메시지를 수행하기 위해 책임을 수행하는 거대한 자율적인 객체다.
이 책임은 시스탬 내 더 작은 객체들의 협력을 통해 구현된다.
책임-주도 설계는 이 지점부터 적용된다.
첫 번째 메시지는 시스템의 기능을 시스템의 책임으로 바꾼 후 얻어진 것이다.
도메인 모델은 구조를, 유스케이스는 협력의 출발점인 시스템 책임을 제공한다.
책임-주도 설계 방법은 유스케이스와 도메인 모델을 통합한다.
이자 계산 기능 구현
위의 유스케이스의 3, 4번과 관련된 기능을 구현해보자.
3. 예금주가 금일 기준으로 예금을 해지할 경우 지급받을 수 있는 이자 계산을 요청한다.
4. 시스템은 중도 해지 시 지급받을 수 있는 이자를 계산한 후 결과를 사용자에게 제공한다.
도메인 모델을 기반으로 이자 계산이라는 시스템 책임을 분할하고 객체들에게 할당하자.
정기예금은 해지 일자를 전달받아 이자 계산을 시작하는 책임을 맡는다.
해당 일자가 약정 기간에 포함되는지 확인한 후 포함될 경우 계좌에게 이자 계산을 요청한다.
계좌는 예금액과 해지 일자를 이자율에게 전달해서 이자를 계산하게 한다.
이자율은 전달받은 예금액과 해지 일자를 이용해 이자액을 계산한 후 이자를 생성해서 반환한다.
객체를 클래스로, 책임을 메서드로 변환함으로써 이자 계산 기능을 구현할 수 있다.
도메인 모델의 속성을 인스턴스 변수로, 책임을 메서드로 변환했다.
시스템의 기능은 이처럼 객체들의 협력 관계를 통해 구현된다.
왜 이자를 계산하는 책임을 이자율 객체에 할당하는가?
책임 할당의 기본 원칙은 책임을 수행하는 데 필요한 정보를 가진 객체에게 그 책임을 할당하는 것이다.
이는 관련된 상태와 행동을 함께 캡슐화하는 자율적인 객체를 낳는다.
유스케이스에서 객체들의 협력으로 이어지는 흐름은 객체 안에 다른 객체를 포함하는 재귀적 합성이라는 객체지향의 기본 개념을 보여준다.
객체에 대한 재귀는 객체지향의 개념을 모든 추상화 수준에서 적용 가능하게 하는 동시에,
객체지향 패러다음을 어떤 곳에서든 일관성 있게 적용할 수 있게 한다.
도메인 모델이 안정적인 이유는 다음과 같다.
- 도메인 모델을 구성하는 개념은 비즈니스가 없어지거나 완전히 개편되지 않는 한 인정적으로 유지된다.
- 도메인 모델을 구성하는 개념 간의 관계는 비즈니스 규칙을 기반으로 하기 때문에 정책이 크게 변경되지 않는 한 안정적으로 유지된다.
이자 계산 기능의 변경
기존의 단리 이자 방식뿐만 아니라 복이 이자 방식을 추가하기로 했다고 하자.
두 방식을 유연하게 선택할 수 있어야 한다.
단리 이자 규칙과 복리 이자 규칙은 계좌 이자 계산을 위한 STRATEGY 페턴의 한 예다.
InterestRate 클래스를 추상 클래스로 변경하고,
단리 이자를 계산하는 SimpleInterest와 복리 이자를 계산하는 CompoundInterest가 InterestRate를 상속받게 했다.
InterestRate에 정의된 createInterest() 메시지를 수신할 경우 객체의 타입에 따라 실행될 메서드가 선택된다.
인터페이스를 정의하는 추상 클래스와 인터페이스를 구현하는 구체적인 클래스 간의 상속 관계는 다형성을 구현한다.
이자 계산 기능이 변경되어도 대부분의 클래스 구조가 그대로 유지된다.
객체지향은 기능의 변경에 대해 유연하게 대응할 수 있는 패러다임이다.
객체지향의 가장 큰 장점은 도메인을 모델링하기 위한 기법과 도메인을 프로그래밍하기 위해 사용하는 기법이 동일하는 점이다.
이 특성이 연결완전성이다.
연결완전성의 역방향 역시 성립한다. 코드의 변경으로부터 도메인 모델의 변경 사항을 유추할 수 있다.
객체지향에서는 코드의 수정이 곧 모델의 수정이다. 이를 가역성이라고 한다.
도메인 모델은 코드와 분리된 별도의 산출물이 아니다.