Community

오브젝트 5장 - 책임 할당하기

GRASP 패턴을 잘 이해하면 응집도, 결합도, 캡슐화 같은 다양한 기준에 따라 결과를 트레이드오프 할 수 있는 기준을 세울 수 있다. 1 책임 주도 설계를 향해 따라야 하는 두가지 원칙 * 데이터보다 행동을 먼저 결정하라 * 협력이라는 문맥 안에서 책임을 결정하라 데이터 보다 행동을 먼저 결정하라 * 질문의 순서를 변경하라 * 이 객체가 수행해야하는 책임은 무엇인가'를 결정하고 '책임을 수행하는데 필요한 데이터는 무엇인가'를 결정하라. 협력이라는 문맥 안에서 책임을 결정하라 * 객체에게 할당된 책임의 품질은 협력에 적합한 정도로 결정된다. * 책임은 객체의 입장이 아니라 객체가 참여하는 협력에 적합해야 한다. * 협력에 적합한 책임을 찾기 위해서는 메시지를 결정한 후에 객체를 선택해야한다. 2 책임 할당을 위한 GRASP 패턴 GRASP : 크레이그 라만이 제안. General Responsibility Assignment Software Pattern (일반적인 책임 할당을 위한 소프트웨어 패턴)의 약자로 객체에게 책임을 할 당할 때 지침으로 삼을 수 있는 원칙들의 집합을 패턴 형식으로 정리한 것 도메인 개념에서 출발하기 * 설계를 시작하기 전에 도메인에 대한 개략적인 모습을 그려 보는 것이 유용하다. * 어떤 책임을 할당해야 할 때 가장 먼저 고민해야하는 유력한 후보는 도메인 개념이다. * 시작 시점에는 참고만 할 수 있을 정도면 된다. * 도메인 개념을 정리하는데 너무 많은 시간을 들이지 말고 빠르게 설계와 구현을 진행하라. 정보 전문가에게 책임을 할당하라 * 책임 주도 설계 : 애플리케이션이 제공해야 하는 기능을 애플리케이션의 책임으로 생각하는 것 * 사용자에게 제공 해야하는 기능은 영화를 예매하는 것 * 애플리케이션은 영화를 예매할 책임이 있다. * 메시지는 메시지를 전송할 객체의 의도를 반영해서 결정한다 * 메시지를 전송할 객체는 무엇을 원하는가? -> '예매하라' * 메시지를 수신할 객체는 누구인가? * 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당한다 * GRASP에서는 Information Expert (정보 전문가) 패턴이라 부른다. * 영화를 예매하는데 필요한 정보를 가장 많이 알고 있는 객체인 '상영'에 책임을 할당 * 예매하라 메시지를 완료하기 위해서는 예매 가격을 계산해야 하는데 '상영'객체는 이를 알지 못하므로 '가격을 계산하라'는 새로운 메시지를 외부에 요청한다. * 영화 가격 계산에 대한 정보를 잘 잘고 있는 전문가 객체는 '영화' 이다. '상영'은 '영화'에 가격을 계산하도록 메시지를 보낸다. * 영화는 할인여부를 판단할 수 없으므로 '할인 여부를 판단하라'라는 메시지를 '할인조건'에 책임을 할당하고 메시지를 보낸다. * Information Expert 패턴은 객체에게 책임을 할당할 때 가장 기본이 되는 원칙이다. 높은 응집도와 낮은 결합도 * 책임을 할당할 수 있는 다양한 대안들이 존재한다면 응집도와 결합도 측면에서 더 나은 대안을 선택하는 것이 좋다. * GRASP에서는 이를 Low Coupling(낮은 결합도)패턴과 High Cohesion(높은 응집도)패턴이라 부른다. * 낮은 결합도는 모든 설계 결정에서 염두해 둬야 하는 원리이다. 현재의 책임 할당을 검토하거나 여러 설계 대안들이 있을 때 낮은 결합도를 유지할 수 있는 설계를 선택하라. * 어떻게 복잡성을 관리할 수 있는 수준으로 유지할 것인가? 높은 응집도를 유지할 수 있게 책임을 할당하라. 높은 응집도는 낮은 결합도와 마찬 가지로 모든 설계 결정에서 염두해 둬야 하는 원리이다. * 책임을 할당하고 코드를 작성하는 매순간 마다 Low Coupling과 High Cohesion의 관점에서 검토하면 단순하면서도 재사용가능하며 유연한 설계를 얻을 수 있을 것이다. 창조자에게 객체 생성 책임을 할당하라 * 영화 예매 협력의 최종 결과물은 Reservation 인스턴스를 생성하는 것이다. * 협력에 참여하는 어떤 객체에게는 Reservation 인스턴스를 생성할 책임을 할당해야 한다는 것을 의미한다. * GRASP에서는 Creator(창조자) 패턴이라고 한다. * 객체 A를 생성해야할 때 어떤 객체에게 객체 생성 책임을 할당해야 하는가? 아래 조건을 최대한 많이 만족하는 B에게 객체 생성 책임을 할당하라. * B가 A객체를 포함하거나 참조한다. * B가 A객체를 기록한다. * B가 A객체를 긴밀하게 사용한다. * B가 A객체를 초기화 하는데 필요한 데이터를 가지고 있다. * Creator 패턴의 의도는 어떤 방식으로든 생성되는 객체와 연결되거나 관련될 필요가 있는 객체에 해당 객체를 생성할 책임을 맡기는 것이다. * Creator 패턴은 이미 존재하는 객체 사이의 관계를 이용하므로 낮은 결합도를 유지할 수 있게 해준다. * 영화 예매 도메인에서 Reservation을 잘 알고 있거나, 긴밀하게 사용하거나, 초기화에 필요한 데이터를 가지고 있는 객체는 무엇인가? 바로 상영(Screening)이다. 03 구현을 통한 검증 * Screening 구현 부터 시작 * Screening은 `예매하라` 메시지에 응답할 수 있어야한다. * Reservation을 반환하는 reserve 메서드 구현 * 상영시간 whenScreended, 순번 sequence, 영화가격계산을 위한 영화 Movie를 필드로 포함 * reserve 메서드에서는 calculateFee 함수를 활용하여 영화 가격을 계산 * screening에서 movie에 전송하는 메시지의 시그니처를 calculateMovieFee 로 선언함. 수신자가 아닌 송신자인 Screening의 의도를 표현 * Movie는 메시지에 응답하기 위해 calculateMovieFee메서드를 구현 * 요금을 계산하기 위해 기본금액(fee), 할인 조건(discountCondition), 할인 정책등의 정보를 알아야한다. * 할인 금액과 할인 비율을 Movie의 변수로 선언 * Movie에 어떤 할인 정책이 적용된 것인지 알기위해 movieType 도 변수로 포함 * 실제로 할인 요금을 계산하는 calculateDiscountAmount 메서드는 movieType의 값에 따라 적절한 메서드를 호출 * Movie는 각 DiscountCondition 할인 여부에 따라 메시지를 전송. * DiscountCondition은 해당 메시지를 처리하기 위해 isSatisfiedBy 메서드를 구현 * DiscountCondition은 기간 조건을 위한 요일, 시작시간, 종료시간과 순번 조건을 위한 상영 순번을 변수로 포함한다. * 추가로 할인 조건의 종류(type)을 인스턴스 변수로 포함 * isSatisfiedBy메서드는 type의 값에 따라 적절한 메서드를 호출한다 * DiscountCondition은 할인 조건을 판단하기 위해 Screening의 상영 시간과 상영 순번을 알아야한다. DiscountCondition 개선 DiscountCondition은 * 새로운 할인 조건추가시 * 순번 조건을 판단하는 로직이 변경될 때 * 기간 조건을 판단하는 로직이 변경되는 경우 변경이 필요하다. 변경의 이유가 3가지이므로 응집도가 낮다. 변경의 이유에 따라 클래스를 분리해야한다. 변경의 이유를 찾는 것은 어렵다. * 코드를 통해 변경의 이유를 파악할 수 있는 첫번째 방법은 인스턴스 변수가 초기화 되는 시점을 살펴보는 것이다. 초기화 되는 속성의 그룹을 기준으로 클래스를 분리 * 두번째 방법은 메서드들이 인스턴스 변수를 사용하는 방식을 살펴보는 것이다. 타입 분리하기 * DiscountCondition의 가장 큰 문제는 순번 조건과 기간 조건이라는 두 개의 독립적인 타입이 하나의 클래스 안에 공존하고 있다는 점이다. -> 클래스의 분리 * SequenceCondition과 PeriodCondition으로 분리 하면 앞에 언급한 문제들을 해결 할 수 있다. * 분리후에는 Movie가 SequenceCondition과 PeriodCondition에 의존하게 되어 다른 문제가 생겼다. 다형성을 통해 분리하기 * Movie입장에서 보면 SequenceCondition과 PeriodCondition은 아무 차이도 없다. 둘 모두 할인 여부를 판단하는 책임을 수행하고 있을 뿐이다. * SequenceCondition과 PeriodCondition이 동일한 책임을 수행한단느 것은 동일한 역할을 수행한다는 것이다. SequenceCondition과 PeriodCondition에 역할의 개념을 적용하면 Movie가 구체적인 클래스는 모르는채 역할에 대해 결합되도록 의존성을 제한할 수 있다. * 객체의 다입에 따라 행동이 변한다면 타입을 분리하고 변화하는 행동을 닥 타입의 책임으로 할당하라. GRASP에서는 이를 Polymorphism (다형성) 패턴이라고 부른다. 변경으로부터 보호하기 * 새로운 할인 조건을 추가하는 경우에 어떻게 되나? * DiscountCondition이라는 역할이 Movie로 부터 SequenceCondition과 PeriodCondition의 존재를 감춘다. * 즉 새로운 타입이 추가 되더라도 Movie는 영향을 받지 않는다. * 이처럼 변경을 캡슐화 하도록 책임을 할당하는 것을 GRASP에서는 Protected Variations (변경 보호) 패턴 이라고 부른다. * 설계에서 변화는 것이 무엇인지 고려하고 변하는 개념을 캡슐화하라. * 변경될 가능성이 높은 것을 캡슐화 하라. Movie 클래스 개선하기 * Movie도 타입이 변하면 코드를 변경해야한다. * Movie의 타입이 늘어나더라도 Screening에 영향을 미치지 않게 할 수 있다. * Movie의 경우에는 구현을 공유할 필요가 있으므로 추상 클래스를 이용해 역할을 구현한다. 변경과 유연성 * 설계를 주도하는 것은 변경이다. * 개발자의 대비방법 2가지 * 코드를 이해하고 수정하기 쉽도록 단순하게 설계하는 것 * 코드를 수정하지 않고도 변경을 수용할 수 있도록 코드를 유연하게 만드는 것 * 대부분의 경우 전자가 더 좋은 방법이지만, 유사한 변경이 반복적으로 발생하고 있다면 복잡성이 상승하더라도 유연성을 추가하는 두 번째 방법을 택하자. * ex) 영화에 설정된 할인 정책을 런타임에 변경할 수 있어야 한다는 요구사항이 추가 된다면 상속이 아닌 합성을 사용해야한다. 04 책임 주도 설계의 대안 * 책임 주도 설계에 익숙해지기 위해서는 부단한 노력과 시간이 필요하다. * 책임과 객체사이에서 방황할때 돌파구를 찾기 위해 선택하는 방법은 최대한 빠르게 기능을 수행하는 코드를 작성하는 것이다. * 우선 실행되는 코드를 얻고 난 후에 코드 상에 명확하게 드러나는 책임들을 올바른 위치로 이동시키면 생각보다 훌륭한 설계를 얻게되는 경우가 종종있다. 메서드 응집도 매우 긴 메서드(몬스터 메서드)의 문제 * 어떤 일을 수행하는지 파악하기 힘들고, 코드를 이해하는데 많은 시간이 걸린다. * 내부 변경이 필요할 때 어디를 수정해야하는지 파악하기 어렵다. * 일부 로직 변경시 나머지 부분에서 버그가 발생할 확률이 높다. * 로직의 일부만 재사용하는 것이 불가능 * 코드를 재사용하기 위해 복사 붙이기를 사용하게 되어 코드 중복을 초래한다. 객체를 자율적으로 만들자 * 어떤 메서드를 어떤 클래스로 이동 시킬까? * 객체 자신이 소유하고 있는 데이터를 스스로 처리하게 만들자. 책임 주도 설계 방법에 익숙하지 않다면 일단 데이터 중심으로 구현한후 이를 리팩터링 하여도 유사한 결과를 얻을 수 있다. 처음부터 책임 주도 설계 방법을 따르기 보다는 동작하는 코드를 작성한 후에 리팩터링 하는 것이 더 훌륭한 결과물을 낳을 수도 있다. 캡슐화, 결합도, 응집도를 이해하고 훌륭한 객체지향 원칙을 적용하기 위해 노력한다면 책임 주도 설계 방법을 단계적으로 따르지 않더라도 유연하고 깔끔한 코드를 얻을 수 있을 것이다. 오브젝트 1장 https://careerly.co.kr/comments/92380 오브젝트 2장 https://careerly.co.kr/comments/92467 오브젝트 3장 https://careerly.co.kr/comments/92725 오브젝트 4장 https://careerly.co.kr/comments/93990

알림

알림이 없습니다