jongfeel / Activity

Activity log of mentoring or study life
MIT License
1 stars 0 forks source link

오브젝트, CHAPTER 05 책임 할당하기, 2024-08-11 #449

Closed jongfeel closed 1 month ago

jongfeel commented 1 month ago

CHAPTER 05 책임 할당하기

01 책임 주도 설계를 향해

데이터 중심 -> 책임 중심의 설계로 전환하기 위해 두 원칙을 따라야 함

데이터보다 행동을 먼저 결정하라

객체는 협력에 참여하기 위해 존재하며 협력 안에서 수행하는 책임이 객체의 존재 가치를 증명한다.

질문의 순서를 다음과 같이 한다. "이 객체가 수행해야 하는 책임은 무엇인가" 이후 "이 책임을 수행하는 데 필요한 데이터는 무엇인가" 를 결정한다.

협력이라는 문맥 안에서 책임을 결정하라

협력에 적합한 책임을 수확하기 위해서는 객체를 결정한 후에 메시지를 선택하는 것이 아니라 메시지를 결정한 후에 객체를 선택해야 한다.

...
객체를 가지고 있기 때문에 메시지를 보내는 것이 아니다.
메시지를 전송하기 때문에 객체를 갖게 된 것이다. [Metz12].

책임 중심 설계에서는 협력이라는 문맥 안에서 객체가 수행할 책임에 초첨을 맞춘다.

책임 주도 설계

02 책임 할당을 위한 GRASP 패턴

크레이그 라만((Craig Larman)이 패턴 형식으로 제안한 GRASP 패턴[LArman04] "General Responsibility Assignment Software Pattern(일반적인 책임 할당을 위한 소프트웨어 패턴)"의 약자이다.

도메인 개념에서 출발하기

도메인 안에 많은 개념이 존재하며 이 개념들을 책임 할당의 대상으로 사용하면 코드에 도메인의 모습을 투영하기가 좀 더 수월해진다.

설계를 시작하는 단계에서는 개념들의 의미와 관계가 정확하거나 완벽할 필요가 없고 출발점이 필요할 뿐이다. 이 단계에서는 책임을 할당받을 객체들의 종류와 관계에 대한 유용한 정보를 제공할 수 있다면 충분하다.

설계를 시작하는 것이 중요하지 도메인 개념들을 완벽하게 정리하는 게 중요하지 않다. 빠르게 설계와 구현을 진행하자.

올바른 도메인 모델이란 존재하지 않는다.

... 필요한 것은 도메인을 그대로 투영한 모델이 아니라 구현에 도움이 되는 모델이다. 다시 말해서 실용적이면서도 유용한 모델이 답이다.

정보 전문가에게 책임을 할당하라

객체에게 책임을 할당하는 첫 번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당한다. GRASP에서는 이를 INFORMATION EXPERT(정보 전문가) 패턴이라고 부른다.

INFORMATION EXPERT 패턴

... 책임을 정보 전문가. 즉, 책임을 수행하는 데 필요한 정보를 가지고 있는 객체에게 할당하라. 이 패턴은 객체가 자율적인 존재여야 한다는 사실을 알려준다. 정보를 알고 있는 객체만이 책임을 어떻게 수행할지 스스로 결정할 수 있기 때문이다.

높은 응집도와 낮은 결합도

높은 응집도와 낮은 결합도는 객체에 책임을 할당할 때 항상 고려해야 하는 기본 원리다. 두 협력 패턴 중에서 높은 응집도와 낮은 결합도를 얻을 수 있는 설계가 있다면 그 설계를 선택해야 한다.

LOW COUPLING 패턴

의존성을 낮추고 변화의 영향을 줄이며 재사용성을 증가시킨다. 설계의 전체적인 결합도가 낮게 유지되도록 책임을 할당한다. 현재의 책임 할당을 검토하거나 여러 설계 대안들이 있을 때 낮은 결합도를 유지할 수 있는 설계를 선택하라.

HIGH COHESION 패턴

어떻게 복잡성을 관리할 수 있는 수준으로 유지할 것인가? 높은 응집도를 유지할 수 있게 책임을 할당하라.

창조자에게 객체 생성 책임을 할당하라

영화 예매 협력의 마지막은 Reservation 인스턴스가 생성되는 것이다. 협력에 참여하는 어떤 객체는 Reservation 인스턴스를 생성할 책임을 할당해야 한다는 걸 의미한다. GRASP의 CREATOR 패턴은 책임 할당 패턴으로 객체를 생성할 책임을 어떤 객체에게 할당할지에 대한 지침을 제공한다.

CREATOR 패턴의 의도는 생성되는 객체와 연결되거나 관련될 필요가 있는 객체에 해당 객체를 생성할 책임을 맡기는 것이다. 그래서 두 객체는 서로 결합된다. 이미 결합되어 있는 객체에게 생성 책임을 할당하는 것은 설계의 전체적인 결합도에 영향을 미치지 않는다. CREATOR 패턴은 이미 존재하는 객체 사이의 관계를 이용하므로 설계가 낮은 결합도를 유지할 수 있게 한다.

03 구현을 통한 검증

DiscountCondiion 개선하기

DiscountCondition은 서로 다른 세 가지 이유로 변경될 가능성이 있다.

하나 이상의 변경 이유를 가지고 있으므로 응집도가 낮다고 볼 수 있다. 그렇다는 건 서로 연관성이 없는 기능이나 데이터가 하나의 클래스 안에 뭉쳐져 있다는 것을 의미한다. 낮은 응집도로 인해 문제를 해결하려면 변경의 이유에 따라 클래스를 분리해야 한다.

타입 분리하기

DiscountCondition을 SequenceCondition과 PeriodCondition이라는 두 개의 클래스로 분리한다.

다형성을 통해 분리하기

역할의 개념을 적용해 보면 Movie 입장에서 SequenceCondition과 PeriodCondition이 동일한 책임을 수행하고 있고 동일한 역할임을 알 수 있다. 역할은 대체 가능하므로 역할에 대해서만 결합되도록 의존성을 제한할 수 있다.

객체의 타입에 따라 변하는 행동이 있다면 타입을 분리하고 변화하는 행동을 각 타입의 책임으로 할당한다. GRASP에서는 이를 POLYMORPHISM(다형성) 패턴이라고 한다.

변경으로부터 보호하기

Movie 관점에서 DiscountCondition의 타입이 캡슐화된다는 것은 새로운 DiscountCondition 타입을 추가해도 Movie가 영향을 받지 않는다는 것을 의미한다. 변경을 캡슐화하도록 책임을 할당하는 것을 GRASP에서는 PROTECTED VARIATIONS(변경 보호) 패턴이라고 한다. PROTECTED VARIATIONS 패턴은 책임 할당의 관점에서 캡슐화를 설명한다. "설계에서 변하는 것이 무엇인지 고려하고 변하는 개념을 캡슐화하라[GOF94]"라는 객체지향의 오랜 격언은 PROTECTED VARIATIONS 패턴의 본질을 잘 설명해 준다.

Movie 클래스 개선하기

Movie도 역할의 개념을 도입해서 협력을 다형적으로 만들면 된다. POLYMORPHISM 패턴을 사용해 서로 다른 행동을 타입별로 분리하면 다형성의 혜택을 누릴 수 있다.

데이터가 아닌 책임을 중심으로 설계해야 한다. 객체에게 중요한 것은 상태가 아니라 행동이다. 객체지향 설계의 기본은 책임과 협력에 초점을 맞추는 것이다.

도메인의 구조가 코드의 구조를 이끈다

도메인 모델은 단순히 설계에 필요한 용어를 제공하는 것을 넘어 코드 구조에도 영향을 미친다. 변경 역시 도메인 모델의 일부이다. 도메인 모델에는 변하는 개념과 이들 사이의 관계가 있다. 도메인 모델에 할인 정책과 할인 조건이 변경될 수 있다에 대한 직관이 반영되어 있으므로 이 직관으로 유연한 설계를 이끌 수 있다. 구현을 가이드할 수 있는 도메인 모델을 선택하라. 객체지향은 도메인 개념과 구조를 반영한 코드를 만들기 때문에 도메인 구조가 코드의 구조를 만드는 건 바람직하다.

변경과 유연성

새로운 할인 정책이 추가될 때 마다 번거롭고 오류가 발생하기 쉬운 구조라면 상속 대신 합성을 사용한다. 합성을 쓰면 새로운 할인 정책이 추가되도 할인 정책을 변경하는 데 필요한 추갖거인 코드를 작성할 필요가 없다.

유연성은 의존성 관리의 문제다. 요소들 사이의 의존성의 정도가 유연성의 정도를 결정한다.

코드의 구조가 도메인 구조에 대한 새로운 통찰력을 제공한다.

할인 정책이 변경된다는 건 도메인의 중요한 요구사항이다. 그러면 할인 정책을 코드 상에 명시적으로 드러냈을 때 도메인 모델 역시 바뀌어야 한다. 도메인 모델은 도메인의 개념과 관계를 모아 놓은 것이 아니다. 도메인 모델은 구현과 밀접한 관계를 맺어야 한다. 도메인 모델은 코드에 대한 가이드를 제공하고 코드의 변화에 맞춰 함께 변화해야 한다.

04 책임 주도 설계의 대안

책임 주도 설계에 익숙해지기 위해서는 노력과 시간이 필요하다. 데이터가 아닌 책임 관점에서 사고하기 위해서는 충분한 경험과 학습이 필요하다.

책임과 객체 사이에서 돌파구를 찾기 위해 선택하는 방법은 최대한 빠르게 목적한 기능을 수행하는 코드를 작성하는 것이다. 아무것도 없이 책임과 협력에 관해 고민하기 보다는 실행되는 코드를 얻고 난 후에 코드 상에 드러나는 책임들을 올바른 위치로 이동시키는 것이다. 그러면 생각보다 훌륭한 설계를 얻게 되는 경우가 종종 있다.

다만 코드 수정 후에 겉으로 드러나는 동작이 바뀌어서는 안된다. 이를 리팩터링이라고 부른다.

객체 디자인에서 가장 기본이 되는 것 중의 하나는 책임을 어디에 둘지를 결정하는 것이다.
십년 이상 객체를 가지고 일했지만 처음 시작할 때는 여전히 적당한 위치를 찾지 못한다.
이런 점이 늘 나를 괴롭혔지만, 이런 경우에는 리팩터링을 하면 된다는 걸 알게 되었다 [Fowler 1999a]

메서드 응집도

ReservationAgency.reserve() 메서드는 길이가 너무 길고 이해하기 어렵다. 긴 메서드는 다양한 측면에서 코드의 유지보수에 부정적인 영향을 미친다. 마이클 페더스(Michael Feathers)는 이런 메서드를 몬스터 메서드(Monster method)라고 부른다.

메서드가 명령문들의 그룹으로 구성되고 그룹마다 주석을 추가해야 할 필요가 있다면 응집도가 낮은 것이므로 메서드를 작게 분해해서 각 메서드의 응집도를 높여야 한다.

응집도 높은 메서드는 변경되는 이유가 하나여야 한다. 클래스가 작고, 목적이 명확한 메서드들로 구성돼 있다면 변경을 처리하기 위해 어떤 메서드를 수정해야 하는지를 쉽게 판단할 수 있다. 또한 메서드의 크기가 작고 목적이 분명하기 때문에 재사용하기도 쉽다. 작은 메서드들로 조합된 메서드는 마치 주석들을 나열한 것처럼 보이기 때문에 코드를 이해하기도 쉽다. [Fowler99a] 인용

객체를 자율적으로 만들자

메서드들을 적절한 클래스로 이동시켜 보면서 캡슐화, 응집도, 결합도의 측면에서 이동시킨 메서드의 적절성을 판단해 본다. 메서드를 이동시키면서 어떤 메서드가 어떤 클래스에 위치해야 하는지에 대한 감을 잡으면서 다양한 기능을 책임 주도 설계 방식에 따라 설계하고 구현한다.

책임 주도 설계 방법에 익숙하지 않아도 데이터 중심으로 구현한 후 이를 리팩터링 해도 유사한 결과를 얻을 수 있다. 캡슐화, 결합도, 응집도를 이해하고 객체지향 원칙을 적용하기 위해 노력한다면 책임 주도 설계 방법을 단계적으로 따르지 않아도 유연하고 깔끔한 코드를 얻을 수 있다.

jongfeel commented 1 month ago

읽은 내용 리뷰를 위한 간략 정리