PPT 챕터
1, 4는 최대한 간략하게 혹은 생략해야할 듯
당일에 두 수업 모두 발표라 다 하면 애들 머리에 들어가지도 않을듯
2장은 책임 할당을 위한 GRASP 패턴을 소개함
3장은 ???을 위한 GRASP 패턴을 소개함
이 부분을 잘 강조해야할 듯
GRASP 패턴 간략히 소개할 때, 같이 하면 좋을듯
1.
책임 주도 설계를 향해
•
데이터보다 행동을 먼저 결정하라
•
협력이라는 문맥 안에서 책임을 결정하라
•
책임 주도 설계
•
GRASP 패턴
2.
책임할당하기
3.
구현을 통함 검증 및 개선
4.
책임 주도 설계의 대안
•
책임에 초점을 맞춰서 설계할 때 직면하는 가장 큰 어려움은 어떤 객체에게 어떤 책임을 할당할지 결정하기 쉽지 않다는 것
•
동일한 문제를 해결할 수 있는 다양한 책임 할당 방법이 존재하며, 어떤 방법이 최선인지는 상황과 문맥에 따라 달라짐
•
따라서, 올바른 책임을 할당하기 위해서는 다양한 관점에서 설계를 평가할 수 있어야함
이번 장에서 살펴볼, GRASP 패턴은 책임 할당의 어려움을 해결하기 위한 답을 제시해줄 수 있음
1. 책임 주도 설계를 향해
•
책임 중심 설계를 위해서는 아래의 두 가지 원칙을 따라야함
◦
데이터보다 행동을 먼저 결정하라
◦
협력이라는 문맥 안에서 책임을 결정하라
1-1. 데이터보다 행동을 먼저 결정하라
•
클라이언트의 관점에서 객체가 수행하는 행동이란 곧 객체의 책임을 의미함
•
객체는 협력에 참여하기 위해 존재하며 협력 안에서 수행하는 책임이 객체의 존재가치를 증명함
•
데이터는 객체가 책임을 수행하는 데 필요한 재료를 제공할 뿐임
•
객체의 데이터에서 행동으로 무게 중심을 옮기는 기본적인 방법:
1.
객체의 책임을 먼저 결정함 ("이 객체가 수행하는 책임은 무엇인가")
2.
후에 객체의 상태를 결정함 ("이 책임을 수행하는 데 필요한 데이터는 무엇인가")
•
객체지향 설계에서 가장 중요한 것은 적절한 객체에게 적절한 책임을 할당하는 능력임
•
그렇다면 객체에게 어떤 책임을 할당해야 하는가?
⇒ 해결의 실마리는 협력임
1-2. 협력이라는 문맥 안에서 책임을 결정하라
•
객체에게 할당된 책임의 품질은 협력에 적합한 정도로 결정됨
•
책임은 객체의 입장이 아니라 객체가 참여하는 협력에 적합해야 함
◦
객체 입장에서는 책임이 조금 어색해 보이더라도 협력에 적합하다면 그 책임은 좋은 것임
•
협력에 적합한 책임이란 메시지 수신자가 아니라 메시지 전송자에게 적합한 책임을 의미함
◦
협력을 시작하는 주체는 메시지 전송자이기 때문
•
협력에 적합한 책임을 할당하기 위해서는 객체를 결정한 후에 메시지를 선택하는 것이 아니라 메시지를 결정한 후에 객체를 선택해야함
•
메시지가 존재하기 때문에 그 메시지를 처리할 객체가 필요한 것임
•
객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택하게 해야함
객체를 가지고 있기 때문에 메시지를 보내는 것이 아니다. 메시지를 전송하기 때문에 객체를 갖게 된 것이다
- Sandi.Metz -
•
클라이언트는 어떤 객체가 메시지를 수신할지 알지 못함
•
클라이언트는 단지 임의의 객체가 메시지를 수신할 것이라는 사일을 믿고 자신의 의도를 표현한 메시지를 전송할 뿐임
•
메시지를 먼저 결정하기 때문에 메시지 송신자는 메시지 수신자에 대해 어떠한 가정도 할 수 없음
•
⇒ 메시지 전송자의 관점에서 메시지 수신자가 깔끔하게 캡슐화 되는 것임
•
책임 중심의 설계가 응집도가 높고 결합도가 낮으며 변경하기 쉽다고 말하는 이유가 여기에 있음
협력이라는 문맥에서 적절한 책임이란 곧 클라이언트의 관점에서 적절한 책임을 의미함
1-3. 책임 주도 설계
•
책임 주도 설계의 흐름:
1.
시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악한다.
2.
시스템 책임을 더 작은 책임으로 분할한다.
3.
분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당한다.
4.
객체가 책임을 수행하는 도중 다른 객체의 도움이 필요한 경우 이를 책일질 적절한 객체 또는 역할을 찾는다.
5.
해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력하게 한다.
•
책임 중심 설계의 핵심은 책임을 결정한 후에 책임을 수행할 객체를 결정하는 것
•
그리고, 협력에 참여하는 객체들의 책임이 어느 정도 정리될 때까지 객체의 내부 상태에 대해 관심을 가지지 않는 것
이번 장에서는 책임 주도 설계 방법을 좀 더 자세히 살펴볼것임
2. 책임 할당을 위한 GRASP 패턴
•
다양한 책임 할당 기법 중 대중적으로 가장 널리 알려진 것은 크레이그 라만(Craig Larman)이 패턴 형식으로 제안한 GRASP 패턴임
•
GRASP(General Responsibility Assignment Software Pattern)은 객체에게 책임을 할당할 때 지침으로 삼을 수 있는 원칙들의 집합을 패턴으로 정리한 것
◦
INFORMATION EXPERT(정보 전문가) 패턴
◦
LOW COUPLING(낮은 결합도) 패턴
◦
HIGH COHESION(높은 응집도) 패턴
◦
CREATOR(창조자) 패턴
◦
POLYMORPHISM(다형성) 패턴
◦
PROTECTED VARIATIONS(변경 보호) 패턴
2-1. 도메인 개념에서 출발하기
•
설계를 시작하기 전에 도메인에 대한 개략적인 모습을 그려 보는 것이 유용함
•
도메인 안에는 무수히 많은 개념들이 존재하며 이 도메인 개념들을 책임 할당의 대상으로 하면 코드에 도메인의 모습을 투영하기가 좀 더 수월해짐
[그림 5-1] 영화 예매 시스템을 구성하는 도메인 개념
•
설계를 시작하는 단계에서는 개념들의 의미와 관계가 정확하거나 완벽할 필요가 없음
•
이 단계에서는 책임을 할당받을 객체들의 종류와 관계에 대한 유용한 정보를 제공할 수 있다면 충분함(=출발점)
•
설계를 시작하기 위해 참고할 수 있는 개념들의 모음 정도로 간주하는 것이 좋음
•
도메인 개념을 정리하는 데 너무 많은 시간을 들이지 말고 빠르게 설계와 구현을 진행하는 것이 좋음
•
도메인 모델은 도메인을 개념적으로 표현한 것이지만 그 안에 포함된 개념과 관계는 구현의 기반이 되어야 함
•
이것은 도메인 모델이 구현을 염두에 두고 구조화되는 것이 바람직하다는 것을 의미함
•
반대로 유연성이나 재사용성 들과 같이 실제 코드를 구현하면서 얻게 되는 통찰이 역으로 도메인에 대한 개념을 바꾸기도 함
⇒ 이것은 올바른 도메인 모델이란 존재하지 않는다는 사실을 보여줌
•
필요한 것은 도메인을 그대로 투영한 모델이 아니라 구현에 도움이 되는 모델임
2-2. 정보 전문가에게 책임을 할당하라
•
책임 주도 설계 방식의 첫 단계는 애플리케이션이 제공해야 하는 기능을 애플리케이션의 책임으로 생각하는 것
•
이 책임을 애플리케이션에 대해 전송된 메시지로 간주하고 이 메시지를 책임질 첫 번째 객체를 선택하는 것으로 설계를 시작
•
영화예매 시스템은 영화를 예매할 책임이 있음
•
이 책임을 수행하는데 필요한 메시지를 결정해야함
•
이 때, 메시지는 메시지를 수신할 객체가 아니라 메시지를 전송할 객체의 의도를 반영하여 결정해야 함
•
아래의 두 가지 질문을 통해 책임을 수행하기 위한 메시지와 객체를 선택해야함
1.
메시지를 전송할 객체는 무엇을 원하는가?
2.
메시지를 수신할 적합한 객체는 누구인가?
•
객체는 상태와 행동을 통합한 캡슐화의 단위
•
정보를 알고 있는 객체만이 책임을 어떻게 수행할지 스스로 결정할 수 있음
•
객체에게 책임을 할당하는 첫 번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것
⇒ INFORMATION EXPERT(정보 전문가) 패턴
•
INFORMATION EXPERT 패턴은 객체가 자신이 소유하고 있는 정보와 관련된 작업을 수행한다는 일반적인 직관을 표현한 것
•
단, 여기서 이야기하는 정보는 데이터와 다르다는 사실에 주의해야함
•
책임을 수행하는 객체가 정보를 '알고' 있다고 해서 그 정보를 '저장'하고 있을 필요는 없음
•
객체는 해당 정보를 제공할 수 있는 다른 객체를 알고 있거나 필요한 정보를 계산해서 제공할 수도 있음
•
영화 예매를 위한 정보 전문가인 상영(Screening)에게 예매를 위한 책임을 할당하자
•
이제부터는 Screening의 내부로 들어가 메시지를 처리하기 위해 필요한 절차와 구현을 고민해봐야함
•
단, Screening이 책임을 수행하는 데 필요한 작업을 구상해보고 처리할 수 없는 작업이 무엇인지를 가릴 정도의 수준이면 됨
•
만약 스스로 처리할 수 없는 작업이 있다면 외부에 도움을 요청해야함
•
이 요청이 외부로 전송해야 하는 새로운 메시지가 되고, 새로운 객체의 책임으로 할당됨
•
이 같은 연쇄적인 메시지 전송과 수신을 통해 협력 공동체가 구성됨
•
위 작업을 반복하면 아래와 같은 협력 공동체를 얻을 수 있음
•
INFORMATION EXPERT 패턴은 객체에게 책임을 할당할 때 가장 기본이 되는 책임 할당 원칙임
•
INFORMATION EXPERT 패턴은 '객체란 상태와 행동을 함께 가지는 단위'라는 객체지향의 가장 기본적인 원리를 책임 할당의 관점에서 표현함
•
INFORMATION EXPERT 패턴을 따르는 것만으로도 자율성이 높은 객체들로 구성된 협력 공동체를 구축할 가능성이 높아짐
2-3. 높은 응집도와 낮은 결합도
•
위 설계는 기능적인 측면에서만 놓고 보면 동일하다
•
차이점이라면 DiscountCodition과 협력하는 객체가 Movie가 아니라 Screening이라는 것뿐임
•
책임을 할당할 수 있는 다양한 대안들이 존재한다면 무엇을 선택해야할까?
•
이 때는, 높은 응집도와 낮은 결합도를 얻을 수 있는 설계를 선택해야함
•
GRASP에서는 이를 LOW COUPLING(낮은 결합도) 패턴과 HIGH COHESION(높은 응집도) 패턴이라고 칭함
LOW COUPLING 패턴 관점
[그림 5-1] 영화 예매 시스템을 구성하는 도메인 개념
•
DiscountCondition이 Movie와 협력하는 것이 좋을까, 아니면 Screening과 협력하는 것이 좋을까?
•
위의 도메인 개념을 살펴보면, Movie와 DiscountCondition은 이미 결합돼 있음
•
때문에 Movie를 DiscountCondition과 협력하게 하면 설계 전체적으로 결합도를 추가하지 않고도 협력을 완성할 수 있음
•
하지만 Screening이 DiscountCondition과 협력할 경우에는 Screening과 DiscountCondition 사이에 새로운 결합도가 추가됨
•
LOW COUPLING 패턴의 관점에서는 전자가 더 나은 설계대안임
HIGH COHESION 패턴 관점
•
Movie의 주된 책임은 영화 요금을 계산하는 것
•
따라서 영화 요금을 계산하는 데 필요한 할인 조건을 판단하기 위해,
Movie가 DiscountCondition과 협력하는 것은 응집도에 아무런 해를 끼치지 않음
•
반면에 후자의 경우 Screening은 DiscountCondition이 할인 여부를 판단할 수 있고
Movie가 이 할인 여부를 필요로 한다는 사실을 알고 있어야함
◦
전자의 경우 영화요금을 얻기 위해 Screening은 '계산하라'라는 메시지를 Movie에게 보내기만 하면 됬음
•
다시 말해, 예매 요금을 계산하는 방식이 변경될 경우 Screening도 함께 변경되어야함
LOW COUPLING 패턴과 HIGH COHESION 패턴은 설계를 진행하면서 책임과 협력의 품질을 검토하는 데 사용할 수 있는 중요한 평가 기준임
2-4. 창조자에게 객체 생성 책임을 할당하라
•
영화 예매 협력의 최종 결과물은 Reservation 인스턴스를 생성하는 것
◦
특정 객체에게 이에 대한 책임을 할당해야함
•
GRASP의 CREATOR(창조자) 패턴은 객체를 생성할 책임을 어떤 객체에게 할당할지에 대한 지침을 제공
•
CREATOR(창조자) 패턴: 객체 A를 생성해야 할 때, 아래 조건을 최대한 만족하는 B에게 객체 생성 책임을 할당하라
◦
B가 A 객체를 포함하거나 참조한다.
◦
B가 A 객체를 기록한다.
◦
B가 A 객체를 긴밀하게 사용한다.
◦
B가 A 객체를 초기화하는 데 필요한 데이터를 가지고 있다(이 경우 B는 A에 대한 정보 전문가다).
•
CREATOR 패턴의 의도는 어떤 방식으로든 생성되는 객체와 연결되거나 관련될 필요가 있는 객체에 해당 객체를 생성할 책임을 맡기는 것
•
생성될 객체에 대해 잘 알고 있어야 하거나 그 객체를 사용해야 하는 객체는 어떤 방식으로든 생성될 객체와 연결될 것임
⇒ 다시 말해서 두 객체는 서로 결합된다.
•
이미 결합돼 있는 객체에게 생성 책임을 할당하는 것은 설계의 전체적인 결합도에 영향을 미치지 않음
•
결과적으로 CREATOR 패턴은 이미 존재하는 객체 사이의 관계를 이용하기 때문에 설계가 낮은 결합도를 유지할 수 있게 함
•
Screening은 예매에 대한 정보 전문가이며, 예매 요금을 계산하는 데 필수적인 Movie도 알고 있음
•
따라서 Screening을 Reservation의 CREATOR로 선택하는 것이 적절함
•
현재까지의 책임 분배는 설계를 시작하기 위한 대략적인 스케치에 불과함
•
실제 설계는 코드를 작성하는 동안 이루어짐
•
협력과 책임이 제대로 동작하는지 확인할 수 있는 유일한 방법은 코드를 작성하고 실행해 보는 것뿐임
3. 구현을 통한 검증
앞서 할당한 책임을 바탕으로 코드를 구현하면 아래와 같음
Screening.java
•
Screening을 구현하는 과정에서 Movie에 전송하는 메시지의 시그니처를 calculateMovieFee(Screening screening)으로 선언함
•
이 메시지는 수신자인 Movie가 아니라 송신자인 Screening의 의도를 표현한 것임
여기서 중요한 것은 Screening이 Movie의 내부 구현에 대한 어떤 지식도 없이 전송할 메시지를 결정했다는 것. 이처럼 Movie의 구현을 고려하지 않고 필요한 메시지를 결정하면 Movie의 내부 구현을 깔끔하게 캡슐화할 수 있음
•
이처럼 메시지가 객체를 선택하도록 책임 주도 설계의 방식을 따르면 캡슐화와 낮은 결합도라는 목표를 비교적 손쉽게 달성할 수 있음
Movie.java
DiscountCondition.java
책임 할당을 위한 GRASP 패턴을 적용하여 코드를 구현하였지만 아직 몇 가지 문제점이 숨어 있음
3-1. DiscountCondition 개선하기
•
DiscountCondition은 다음과 같이 서로 다른 세 가지 이유로 변경될 수 있음
◦
새로운 할인 조건 추가
isSatisfiedBy 메서드 안의 if ~ else 구문을 수정해야 한다. 물론 새로운 할인 조건이 새로운 데이터를 요구한다면 DiscountCondition에 속성을 추가하는 작업도 필요하다.
◦
순번 조건을 판단하는 로직 변경
isSatisfiedBySequence 메서드의 내부 구현을 수정해야 한다. 물론 순번 조건을 판단하는 데 필요한 데이터가 변경된다면 DiscountCondition의 sequence 속성 역시 변경해야 할 것이다.
◦
기간 조건을 판단하는 로직이 변경
isSatisfiedByPeriod 메서드의 내부 구현을 수정해야 한다. 물론 기간 조건을 판단하는 데 필요한 데이터가 변경된다면 DiscountCondition의 dayOfWeek, startTime, endTime 속성 역시 변경해야 할 것이다.
•
DiscountCondition은 하나 이상의 변경 이유를 가지기 때문에 응집도가 낮음
•
낮은 응집도가 초래하는 문제를 해결하기 위해서는 변경의 이유에 따라 클래스를 분리해야함
•
설계를 개선하기 작업으로 변경의 이유가 하나 이상인 클래스를 찾는 것으로부터 시작하는 것이 좋음
•
하지만 클래스 안에서 변경의 이유를 찾는 것은 생각보다 어려움
•
다행히도 변경의 이유가 하나 이상인 클래스에는 위험 징후를 또렷하게 드러내는 몇 가지 패턴이 존재함
인스턴스 변수가 초기화되는 시점 살펴보기
•
코드를 통해 변경의 이유를 파악할 수 있는 첫 번째 방법은 인스턴스 변수가 초기화되는 시점을 살펴보는 것
•
응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함께 초기화함
•
반면 응집도가 낮은 클래스는 객체의 속성 중 일부만 초기화하고 일부는 초기화되지 않은 상태로 남겨짐
⇒ 따라서 함께 초기화되는 속성을 기준으로 코드를 분리해야 함
메서드들이 인스턴스 변수를 사용하는 방식 살펴보기
•
두 번째 방법은 메서드들이 인스턴스 변수를 사용하는 방식을 살펴보는 것
•
모든 메서드가 객체의 모든 속성을 사용한다면 클래스의 응집도는 높다고 볼 수 있음
•
반대의 상황은 응집도가 낮다고 볼 수 있음
⇒ 이 경우 속성 그룹과 해당 그룹에 접근하는 메서드 그룹을 기준으로 코드를 분리해야함
3-2. 타입 분리하기
아래의 코드는 DiscountCondition에서 분리된 SequenceCondition과 PeriodCondition임
•
클래스를 분리함으로써 앞서 언급된 문제점들이 모두 해결됨
•
하지만 클래스를 분리함으로써 Movie가 두 개의 서로 다른 클래스와 협력해야하는 새로운 문제가 발생함
•
이로 인해 새로운 아래와 같은 문제를 야기함
•
첫 번째 문제는 Movie 클래스가 PeriodCondition과 SequenceCondition 클래스 양쪽 모두에게 결합된다는 것
◦
클래스를 분리한 후에 설계의 관점에서 전체적인 결합도가 높아짐
•
두 번째 문제는 수정 후에 새로운 할인 조건을 추가하기가 더 어려워졌다는 것
◦
새로운 클래스를 담기 위한 List를 Movie의 인스턴스 변수로 추가해야함
◦
List를 이용해 할인 조건을 만족하는지 여부를 판단하는 메서드도 추가해야함
DiscountCondition의 입장에서 보면 응집도가 높아졌지만, 변경과 캡슐화라는 관점에서 보면 전체적으로 설계의 품질이 나빠짐
3-3. 다형성을 통해 분리하기
•
Movie의 입장에서 보면 SequenceCondition과 PeriodCondition 모두 할인 여부를 판단하는 동일한 책임을 수행하고 있음
•
Movie의 입장에서 SequenceCondition과 PeriodCondition이 동일한 책임을 수행한다는 것은 동일한 역할을 수행한다는 것을 의미
•
역할은 협력 안에서 대체 가능성을 의미하기 때문에 두 클래스에 역할의 개념을 적용하면
•
Movie가 구체적인 클래스는 알지 못한 채 오직 역할에 대해서만 결합되도록 의존성을 제한할 수 있음
•
자바에서는 일반적으로 역할을 구현하기 위해 추상 클래스나 인터페이스를 사용함
•
역할을 대체할 클래스들 사이에서 구현을 공유해야 할 필요가 있다면 추상 클래스를 사용
•
구현을 공유할 필요 없이 역할을 대체하는 객체들의 책임만 정의하고 싶다면 인터페이스를 사용
위 내용을 바탕으로 역할을 구현하는 DiscountCondition 인터페이스 작성
•
이제 Movie는 협력하는 객체의 구체적인 타입을 몰라도 상관 없음
•
협력하는 객체가 DiscountCondition 역할을 수행할 수 있고 isSatisfiedBy 메시지를 이해할 수 있다는 사실만 알고 있어도 충분함
•
DiscountCondition의 경우에서 알 수 있듯이 객체의 암시적인 타입에 따라 행동을 분기해야 한다면 암시적인 타입을 명시적인 클래스로 정의하고 행동을 나눔으로써 응집도 문제를 해결할 수 있음
•
다시 말해 객체의 타입에 따라 변하는 행동이 있다면 타입을 분리하고 변화하는 행동을 각 타입의 책임으로 할당하라는 것
•
GRASP에서는 이를 POLYMORPHISM(다형성) 패턴이라고 부름
[그림 5.6] 역할을 이용해 다형적으로 협력하는 Movie와 DiscountCondition
3-4. 변경으로부터 보호하기
•
새로운 할인 조건을 추가하는 경우에는 어떻게 될까?
•
DiscountCondition이라는 추상화가 구체적인 타입을 캡슐화하고 있음
•
Movie의 관점에서 DiscountCondition의 타입이 캡슐화된다는 것은 새로운 DiscountCondition 타입을 추가하더라도 Movie가 영향을 받지 않는다는 것을 의미함
•
이처럼 변경을 캡슐화하도록 책임을 할당하는 것을 GRASP에서는 PROTECTED VARIATIONS(변경 보호) 패턴이라고 칭함
•
변화가 예상되는 불안정한 지점들을 식별하고 그 주위에 안정된 인터페이스를 형성하도록 책임을 할당하면, 변화와 불안정성이 다른 요소에 나쁜 영향을 미치지 않도록 방지할 수 있음
•
클래스를 변경에 따라 분리하고 인터페이스를 이용해 변경을 캡슐화하는 것은 설게의 결합도와 응집도를 향상시키는 매우 강력한 방법임
하나의 클래스가 여러 타입의 행동을 구현하고 있는 것처럼 보인다면, 클래스를 분해하고 POLYMORPHISM 패턴에 따라 책임을 분산시켜라.
예측 가능한 변경으로 인해 여러 클래스들이 불안정 해진다면 PROTECTED VARIATIONS 패턴에 따라 안정적인 인터페이스 뒤로 변경을 캡슐화하라.
3-5. Movie 클래스 개선하기
•
Movie 역시 DiscountCondition과 유사한 문제를 가지고 있음
•
'금액 할인 정책 영화'와 '비율 할인 정책 영화'라는 두 가지 타입을 하나의 클래스 안에 구현하고 있음
•
이 또한 역할의 개념을 도입해서 협력을 다형적으로 만듦으로써 해결할 수 있음
•
Movie의 경우에는 구현을 공유할 필요가 있으므로 추상클래스를 이용해 역할을 구현할 수 있음
Movie.java
AmountDiscountMovie.java
PercentDiscountMovie.java
NoneDiscountMovie.java
[그림 5.7] 책임 중심의 영화 예매 시스템
도메인 모델과 코드의 구조
•
[그림 5.7]은 [그림 5.1]의 도메인 모델의 구조와 유사함
•
도메인 모델은 단순히 설계에 필요한 용어를 제공하는 것을 넘어 코드의 구조에도 영향을 미침
•
도메인 모델에는 도메인 안에서 변하는 개념과 이들 사이의 관계가 투영돼 있어야함
◦
앞서 작성한 도메인 모델에는 할인 정책과 할인 조건이 변경될 수 있다는 도메인에 대한 직관이 반영돼 있었음
•
도메인 모델을 선택할 때 구현을 가이드할 수 있는 모델을 선택해야함
•
객체지향은 도메인의 개념과 구조를 반영한 코드를 가능하게 만들기 때문에, 도메인의 구조가 코드의 구조를 이끌어 내는 것은 자연스러울뿐만 아니라 바람직한 것임
3-6. 변경과 유연성
•
일반적으로 코드를 이해하고 수정하기 쉽도록 최대한 단순하게 설계하는 것이 좋음
•
하지만 유사한 변경이 반복적으로 발생하고 있다면 복잡성이 상승하더라도 유연성을 추가하는 방법이 더 좋을 수 있음
•
예를 들어, 영화에 설정된 할인 정책을 실행 중에 변경할 수 있어야 한다는 요구사항이 추가됐다고 가정해보자
•
할인 정책을 구현하기 위해 상속을 이용하고 있기 때문에, 실행 중에 새로운 인스턴스를 생성한 후 필요한 정보를 복사해야 함
•
또한, 변경 전후의 인스턴스가 개념적으로는 동일한 객체를 가리키지만 물리적으로 서로 다른 객체이기 때문에 식별자의 관점에서 혼란스러울 수 있음
•
해결 방법은 상속 대신 합성을 사용하는 것
•
아래의 그림은 합성을 활용해 개선된 영화 예매 시스템임
[그림 5.8] 합성을 사용해 유연성을 높인 할인 정책
•
합성을 활용한 후, 할인 정책을 변경하는 것은 Movie에 연결된 DiscountPolicy의 인스턴스를 교체하는 단순한 작업으로 개선됨
코드의 구조가 도메인의 구조에 대한 새로운 통찰력을 제공한다
[그림 5.9] 코드의 변경에 맞춰 수정된 도메인 모델
•
할인 정책이라는 개념을 코드 상에 명시적으로 드러냈다면, 도메인 모델 역시 코드의 관점에 따라 바뀌어야함
•
도메인 모델은 단순히 도메인의 개념과 관계를 모아 놓은 것이 아님
•
도메인 모델은 구현과 밀접한 관계를 맺어야함
•
도메인 모델은 코드에 대한 가이드를 제공할 수 있어야 하며 코드의 변화에 발맞춰 함께 변화해야함
4. 책임 주도 설계의 대안
•
저자는 3장을 읽었음에도 여전히 책임을 할당하는 데 어려움을 느끼고 있다면 그에 대한 대안을 제시함
•
일단 절차형 코드로 실행되는 프로그램을 빠르게 작성한 후 완성된 코드를 객체지향적인 코드로 변경하는 것
•
아무것도 없는 상태에서 책임과 협력에 관해 고민하기 보다는 일단 실행되는 코드를 얻고 난 후에 코드 상에 명확하게 드러나는 책임들을 올바른 위치로 이동시키는 것
•
주의할 점은 코드를 수정한 후에 겉으로 드러나는 동작이 바뀌어서는 안됨
•
캡슐화를 향상시키고, 응집도를 높이고, 결합도를 낮춰야 하지만 동작은 그대로 유지해야함
•
이처럼 겉으로 보이는 동작은 바꾸지 않은 채 내부 구조를 개선하는 것을 리팩터링(Refactoring)이라고 부름
객체 디자인에서 가장 기본이 되는 것 중의 하나(원칙은 아닐지라도)는 책임을 어디에 둘지 결정하는 것이다. 나는 십년 이상 객체를 가지고 일했지만 처음 시작할 때는 여전히 적당한 위치를 찾지 못한다. 늘 이런 점이 나를 괴롭혔지만, 이제는 이런 경우에 리팩터링을 사용하면 된다는 것을 알게 되었다.
- Martin Fowler -
4-1. 메서드 응집도
•
위 코드는 데이터 중심으로 설계된 '영화 예매 시스템'의 ReservationAgency임
•
객체로 책임을 분배할 때 가장 먼저 할 일은 메서드를 응집도 있는 수준으로 분해하는 것
•
긴 메서드(=몬스터 메서드)를 작고 응집도 높은 메서드로 분리하면 적절한 클래스로 이동하기가 더 수월해짐
◦
작은 메서드들로 조합된 메서드는 마치 주석들을 나열한 것처럼 보이기 때문에 코드를 이해하기 쉬움
◦
다만, 작은 메서드는 실제로 이름을 잘 지었을때만 그 진가가 드러나므로, 이름을 지을 때 주의해야 함
아래의 코드는 Reserve라는 몬스터 메서드를 작은 메서드들로 분리한 코드임
•
메서드를 분리하고 나면 public 메서드는 상위 수준의 명세를 읽는 것 같은 느낌이 듬
4-2. 객체를 자율적으로 만들자
•
ReservationAgency의 메서드들의 응집도 자체는 높아졌지만, 이 메서드들을 담고 있는 클래스의 응집도는 여전히 낮음
•
변경의 이유가 다른 메서드들을 적절한 위치로 분배해야 함
•
즉, 각 메서드가 사용하는 데이터를 정의하고 있는 클래스로 이동시켜야함
•
어떤 데이터를 사용하는지를 가장 쉽게 알 수 있는 방법은 메서드 안에서 어떤 클래스의 접근자 메서드를 사용하는 파악하는 것
•
메서드를 다른 클래스로 이동시킬 때는 인자에 정의된 클래스 중 하나로 이동하는 경우가 일반적임
•
데이터를 사용하는 메서드를 데이터를 가진 클래스로 이동시키고 나면 캡슐화와 높은 응집도, 낮은 결합도를 가지는 설계를 얻을 수 있음
여기서 하고 싶은 말은 책임 주도 설계 방법에 익숙하지 않다면 일단 데이터 중심으로 구현한 후 이를 리팩터링하더라도 유사한 결과를 얻을 수 있다는 것이다. 처음부터 책임 주도 설계 방법을 따르는 것보다 동작하는 코드를 작성한 후에 리팩터링하는 것이 더 훌륭한 결과물을 낳을 수도 있다. 캡슐화, 결합 도, 응집도를 이해하고 훌륭한 객체지향 원칙을 적용하기 위해 노력한다면 책임 주도 설계 방법을 단계 적으로 따르지 않더라도 유연하고 깔끔한 코드를 얻을 수 있을 것이다.