이번 회차에는 @vincent @freebear 님께서 참여했습니다.
이번 회차에서는 서로의 인터페이스 사용 경험에 대해서 많은 이야기를 했습니다.
P 470
객체는 협력을 위해 존재한다. 협력은 객체가 존재하는 이유와 문맥을 제공한다. 잘 설계된 애플리케이션은 이해하기 쉽고, 수정이 용이하며, 재사용 가능한 협력의 모임이다. 객체지향 설계의 목표는 적절한 책임을 수행하는 객체들의 협력을 기반으로 결합도가 낮고 재사용 가능한 코드 구조를 창조하는 것이다.
객체지향 패러다임의 장점은 설계를 재사용할 수 있다는 것이다. 하지만 재사용은 공짜로 얻어지지 않는다. 재사용을 위해서는 객체들의 협력 방식을 일관성 있게 만들어야 한다. 일관성은 설계에 드는 비용을 감소시킨다.
객체들의 협력이 전체적으로 일관성 있는 유사한 패턴을 따른다면 시스템을 이해하고 확장하기 위해 요구되는 정신적인 부담을 줄일 수 있다.
P478
통화 기간에 대한 정보를 가장 잘 알고 있는 객체는 Call이다. 하지만 Call은 통화 기간은 잘 알지 몰라도 기간 자체를 처리하는 방법에 대해서는 전문가가 아니다. 기간을 처리하는 방법에 대한 전문가는 바로 DateTimeInterval이다. 따라서 통화 기간을 일자 단위로 나누는 책임은 DateTimeInterval에게 할당하고 Call이 DateTimeInterval에게 분할을 요청하도록 협력을 설계하는 것이 적절할 것이다.
P485
비일관성은 두 가지 상황에서 발목을 잡는다. 하나는 새로운 구현을 추가해야 하는 상황이고, 또 다른 하나는 기존의 구현을 이해해야 하는 상황이다. 그리고 이 장애물이 문제인 이유는 개발자로서 우리가 수행하는 대부분의 활동이 코드를 추가하고 이해하는 일과 깊숙히 연관돼 있기 때문이다.
P486
결론은 유사한 기능을 서로 다른 방식으로 구현해서는 안 된다는 것이다.
유사한 기능은 유사한 방식으로 구현해야 한다.
P487
기존의 설계가 어떤 가이드도 제공하지 않기 때문에 새로운 기본 정책을 구현해야 하는 상황에서 또 다른 개발자는 또 다른 방식으로 기본 정책을 구현할 가능성이 높다.
P488
일관성 있는 설계를 만드는 데 가장 훌륭한 조언은 다양한 설계 경험을 익히라는 것이다. 풍부한 설계 경험을 가진 사람은 어떤 변경이 중요한지, 그리고 그 변경을 어떻게 다뤄야 하는지에 대한 통찰력을 가지게 된다. 따라서 설계 경험이 풍부하면 풍부할수록 어떤 위치에서 일관성을 보장해야 하고 일관성을 제공하기 위해 어떤 방법을 사용해야 하는지를 직관적으로 결정할 수 있다. 하지만 이런 설계 경험을 단기간에 쌓아 올리는 것은 생각보다 어려운 일이다.
일관성 있는 설계를 위한 두 번째 조언은 널리 알려진 디자인 패턴을 학습하고 변경이라는 문맥 안에서 디자인 패턴을 적용해 보라는 것이다.
다음 장에서 설명하겠지만 디자인 패턴은 특정한 변경에 대해 일관성 있는 설계를 만들 수 있는 경험 법칙을 모아놓은 일종의 설계 템플릿이다. 디자인 패턴을 학습하면 빠른 시간 안에 전문가의 경험을 흡수할 수 있다.
##P489
바뀌는 부분을 따로 뽑아서 캡슐화한다. 그렇게 하면 나중에 바뀌지 않는 부분에는 영향을 미치지 않은 채로 그 부분만 고치거나 확장할 수 있다.
P490
절차지향 프로그램에서 변경을 처리하는 전통적인 방법은 이처럼 조건문의 분기를 추가하거나 개별 분기 로직을 수정하는 것이다.
객체지향은 조금 다른 접근방법을 취한다. 객체지향에서 변경을 다루는 전통적인 방법은 조건 로직을 객체 사이의 이동으로 바꾸는 것이다.
P492
객체지향적인 코드는 조건을 판단하지 않는다. 단지 다음 객체로 이동할 뿐이다.
지금까지 살펴본 것처럼 조건 로직을 객체 사이의 이동으로 대체하기 위해서는 커다란 클래스를 더 작은 클래스들로 분리해야 한다. 그렇다면 클래스르 분리하기 위해 어떤 기준을 따르는 것이 좋을까? 가장 중요한 기준은 변경의 이유와 주기다. 클래스는 명확히 단 하나의 이유에 의해서만 변경돼야 하고 클래스 안의 모든 코드는 함께 변경돼야 한다. 간단하게 말해서 단일 책임 원칙을 따르도록 클래스를 분리해야 한다는 것이다.
P493
변하는 개념을 변하지 않는 개념으로부터 분리하라.
변하는 개념을 캡슐화하라.
P495
GOF의 조언에 따르면 캡슐화란 단순히 데이터를 감추는 것이 아니다. 소프트웨어 안에서 변할 수 있는 모든 '개념’을 감추는 것이다. 개념이라는 말이 다소 추상적으로 들린다면 간단히 다음처럼 생각하라.
캡슐화란 변하는 어떤 것이든 감추는 것이다[Bain08, Shalloway01].
캡슐화의 가장 대표적인 예는 객체의 퍼블릭 인터페이스와 구현을 분리하는 것이다. 객체를 구현한 개발자는 필요할 때 객체의 내부 구현을 수정하기를 원한다. 객체와 협력하는 클라이언트의 개발자는 객체의 인터페이스가 변하지 않기를 원한다. 따라서 자주 변경되는 내부 구현을 안정적인 퍼블릭 인터페이스 뒤로 숨겨야 한다.
P496
캡슐화란 단지 데이터 은닉을 의미하는 것이 아니다. 코드 수정으로 인한 파급효과를 제어할 수 있는 모든 기법이 캡슐화의 일종이다.
변경을 캡슐화할 수 있는 다양한 방법이 존재하지만 협력을 일관성 있게 만들기 위해 가장 일반적으로 사용하는 방법은 서브타입 캡슐화와 객체 캡슐화를 조합하는 것이다. 그림 14.13에서 알 수 있는 것처럼 서브타입 캡슐화는 인터페이스 상속을 사용하고, 객체 캡슐화는 합성을 사용한다.
P508
기본 정책을 추가하기 위해 규칙을 지키는 것보다 어기는 것이 더 어렵다는 점에 주목하라. 일관성 있는 협력은 개발자에게 확장 포인트를 강제하기 때문에 정해진 구조를 우회하기 어렵게 만든다.
P509
유사한 기능에 대해 유사한 협력 패턴을 적용하는 것은 객체지향 시스템에서 개념적 무결성(Conceptual Integrity)[Brooks95]을 유지할 수 있는 가장 효과적인 방법이다.
시스템이 일관성 있는 몇 개의 협력 패턴으로 구성된다면 시스템을 이해하고, 수정하고, 확장하는 데 필요한 노력과 시간을 아낄 수 있다. 따라서 협력을 설계하고 있다면 항상 기존의 협력 패턴을 따를 수는 없는지 고민하라. 그것이 시스템의 개념적 무결성을 지키는 최선의 방법일 것이다.
P510
개념적으로는 불필요한 FixedFeeCondition 클래스를 추가하고 findTimeIntervals 메서드의 반환 타입이 List임에도 항상 단 하나의 DateTimeInterval 인스턴스를 반환한다는 사실이 마음에 조금 걸리지만 개념적 무결성을 무너뜨리는 것보단 약간의 부조화를 수용하는 편이 더 낫다.
P511
처음에는 일관성을 유지하는 것처럼 보이던 협력 패턴이 시간이 흐르면서 새로운 요구사항이 추가되는 과정에서 일관성의 벽에 조금씩 금이 가는 경우를 자주 보게 된다. 협력을 설계하는 초기 단계에서 모든 요구사항을 미리 예상할 수 없기 때문에 이것은 잘못이 아니며 꽤나 자연스러운 현상이다. 오히려 새로운 요구사항을 수용할 수 있는 협력 패턴을 향해 설계를 진화시킬 수 있는 좋은 신호로 받아들여야 한다.
협력은 고정된 것이 아니다. 만약 현재의 협력 패턴이 변경의 무게를 지탱하기 어렵다면 변경을 수용할 수 있는 협력 패턴을 향해 과감하게 리팩터링하라. 요구사항의 변경에 따라 협력 역시 지속적으로 개선해야 한다. 중요한 것은 현재의 설계에 맹목적으로 일관성을 맞추는 것이 아니라 달라지는 변경의 방향에 맞춰 지속적으로 코드르 개선하려는 의지다.
P512
따라서 훌륭한 설계자가 되는 첫걸음은 변경의 방향을 파악할 수 있는 날카로운 감각을 기르는 것이다. 그리고 이 변경에 탄력적으로 대응할 수 있는 다양한 캡슐화 방법과 설계 방법을 익히는 것 역시 중요하다.