////
Search

4장 - 설계 품질과 트레이드 오프

Date
2024/02/18 03:49
Tags
객체지향 설계의 핵심은 역할, 책임, 협력이다.
협력 = 애플리케이션 기능 구현을 위한 메시지의 상호작용
책임 = 객체가 협력을 위해 수행하는 행동
역할 = 대체 가능한 책임의 집합
설계는 변경을 위해 존재하고 변경에는 어떤 식으로든 비용이 발생한다.
훌륭한 설계란 합리적 비용 안에서 변경을 수용할 수 있는 구조를 만드는 것이다.
객체의 상태가 아닌 행동에 초점을 맞춘다면 응집도를 합리적인 수준으로 유지할 수 있다.

1. 데이터 중심의 영화 예매 시스템

객체지향 설계에는 두 가지 방법으로 시스템 객체로 분할할 수 있다.
상태를 분할의 중심축으로 두는 방법
객체 자신이 포함하고 있는 데이터를 조작하는 데 필요한 오퍼레이션을 정의한다.
객체의 상태에 초점을 맞춘다.
책임을 분할의 중심축으로 삼는 방법
객체는 다른 객체가 요청할 수 있는 오퍼레이션을 위해 필요한 상태를 보관한다.\
객체의 행동에 초점을 맞춘다.
훌륭한 객체지향 설계는 책임에 초점을 맞춰야 하는데 그 이유는 변경에 유연하기 때문이다.
상태를 중심으로 삼음 → 구현의 세부사항이 인터페이스에 스며들어 캡슐화가 무너짐 → 의존하는 다른 객체들에게 영향이 퍼짐
(가정이 무너지고… 사회가 무너지고…. 가정이 황폐화되고…)
데이터 중심의 설계에서는 객체가 포함해야 하는 데이터에 집중한다.
객체의 책임을 결정하기 전 포함 데이터의 질문을 반복한다면 데이터 중심 설계에 매몰돼 있을 확률이 높다.

2. 설계 트레이드 오프

2.1. 캡슐화

상태와 행동을 하나의 객체 안에 모으는 이유는 객체 내부의 구현을 외부로 감추기 위함이다.
외부에서 다른 객체의 구현을 알게될 경우 오염이 발생해 변경의 영향이 미치게된다.
변경 가능성이 높은 부분은 내부에 숨기고 외부에는 상대적으로 안정적인 부분만 공개하여 변경의 여파를 통제한다.
구현 = 변경 가능성이 높은 부분
인터페이스 = 상대적으로 안정적인 부분
객체지향에서 가장 중요한 원리는 바로 캡슐화다.
설계가 필요한 이유는 요구사항이 변경되기 때문이다.
캡슐화가 중요한 이유는 불안정한 부분과 안정적인 부분을 분리해 변경의 영향을 통제할 수 있기 때문이다.
즉, 변경될 수 있는 어떤 것이라도 캡슐화 해야한다.

2.2. 응집도와 결합도

응집도 = 모듈에 포함된 내부 요소들이 연관돼 있는 정도를 나타낸다.
객체지향 관점에서 응집도는 객체에 얼마나 관련 높은 책임들을 할당했는지를 나타낸다.
결합도 = 의존성의 정도를 나타내며 다른 모듈에 대해 얼마나 많은 지식을 갖고 있는지를 나타내는 척도다.
객체지향 관점에서 결합도는 객체 또는 클래스가 협력에 필요한 적절한 수준의 관계만을 유지하고 있는지를 나타낸다.
좋은 설계란 오늘의 기능을 수행하면서 내일의 변경을 수용할 수 있는 설계다.
높은 응집도와 낮은 결합도를 가진 설계를 추구한다면 설계를 쉽게 변경할 수 있게 만든다.
변경의 관점에서 응집도란 변경이 발생할 때 모듈 내부에서 발생하는 변경의 정도로 측정할 수 있다.
위 그림은 변경과 응집도 사이의 관계를 나타낸 것으로 왼쪽은 응집도가 높은 설계를 나타내며 오른쪽은 응집도가 낮은 설계를 나타낸다.
음영으로 표시된 부분은 변경이 일어났을 때 수정해야 하는 부분을 의미한다.
응집도가 높을수록 변경의 대상과 범위가 명확해져 코드를 변경하기 쉬워진다.
결합도 역시 한 모듈이 변경되기 위해 다른 모듈의 변경을 요구하는 정도로 측정이 가능하다.
결합도가 낮을 때에는 변경의 영향이 오직 모듈 하나에만 한정되는 것을 알 수 있다.
영향을 받는 모듈 수 이외의 변경의 원인을 이용해 결합도의 개념이 설명 가능한데 내부 구현을 변경했을 때 이것이 다른 모듈에 영향을 미치는 경우엔 두 모듈 사이의 결합도가 높다 표현한다.
결합도가 높아도 상관없는 경우도 있는데 이는 일반적으로 변경될 확률이 적은 안정적인 모듈에 의존하는 경우 아무런 문제가 되지 않는다.
예를 들자면 자바의 String이나 ArrayList가 있을 것이다.
만약 코드가 변경에 강하게 저항하고 있다면 구성 요소들의 응집도가 낮고 요소들이 서로 강하게 결합돼 있을 확률이 높다.
캡슐화를 지키면 모듈의 응집도는 높아지고 결합도는 낮아진다.
응집도와 결합도를 고려하기 이전에 캡슐화부터 잘 지키기 위해서 노력하자.

3. 데이터 중심의 시스템의 문제점

3.1. 캡슐화 위반

설계 시 협력에 관해 고민하지 않는다면 캡슐화를 위한하는 과도한 접근자와 수정자를 가지게 되는 경향이 있다.
접근자와 수정자를 통해 과도하게 의존하는 설계 방식을 추측에 의한 설계라고 부른다.
결과적으로 내부 구현이 외부에 노출될 수 밖에 없으면 캡슐화 원칙을 위반하게 된다.

3.2. 높은 결합도

데이터 중심의 설계는 접근자와 수정자를 통해 내부 구현을 인터페이스의 일부로 만들기 때문에 캡슐화를 위반한다.
클라이언트가 구현에 의지한다는 것은 강하게 결합된다는 걸 의미하며 의존하는 인터페이스 수정 시 모든 클라이언트도 함께 수정되어야 한다.
객체들을 사용하는 제어 로직이 특정 객체 안에 집중되기 때문에 하나의 제어 객체가 다수의 데이터 객체에 강하게 결합된다는 것이다.
이 결합도로 인해 어떤 데이터 객체를 변경하더라도 제어 객체를 함께 변경할 수밖에 없다.

3.3. 낮은 응집도

서로 다른 이유로 변경되는 코드가 하나의 모듈 안에 공존할 때 모듈의 응집도가 낮다고 말한다.
변경의 이유가 서로 다른 코드들을 하나의 모듈 안에 뭉쳐놓았기 때문에 변경과 아무 상관없는 코드들이 영향을 받게 된다.
하나의 요구사항 변경을 반영하기 위해 동시에 여러 모듈을 수정해야 한다.

4. 자율적인 객체를 향해

4.1. 캡슐화를 지켜라

캡슐화는 설계의 제 1원칙으로 낮은 응집도와 높은 결합도를 만드는 원인은 캡슐화를 위반하기 때문이다.
객체는 스스로 상태를 책임져야 하며 외부에서는 인터페이스에 정의된 메서드를 통해서만 상태에 접근할 수 있어야한다.

4.2. 스스로 자신의 데이터를 책임지는 객체

상태와 행동을 하나의 객체라는 단위로 묶는 이유는 객체 스스로 자신의 상태를 처리할 수 있게 하기 위함이다.
객체는 단순한 데이터 제공자가 아니며 객체 내 데이터보다 협력에 참여하며 책임을 수행할 오퍼레이션이 더 중요하다.
따라서 객체 설계 시 “이 객체가 어떤 데이터를 포함해야 하는가?”라는 질문을 아래 질문으로 분리해야 한다.
이 객체가 어떤 데이터를 포함해야 하는가?
이 객체가 데이터에 대해 수행해야하는 오퍼레이션은 무엇인가?
두 질문을 조합하면 내부 상태를 저장하는 방식과 이 상태에 대해 호출할 수 있는 오퍼레이션의 집합을 얻을 수 있다.

5. 데이터 중심 설계의 문제점

데이터 중심의 설계는 본질적으로 너무 이른 시기에 데이터에 관해 결정하도록 강요한다.
데이터 중심의 설계에서는 협력이라는 문맥을 고려하지 않고 객체를 고립시킨 채 오퍼레이션을 결정한다.

5.1. 객체의 행동보다는 상태에 초점을 맞춘다.

데이터 중심 설계는 일반적으로 데이터와 기능을 분리하는 절차적 프로그래밍 방식을 따른다.
이는 상태와 행동을 하나로 캡슐화하는 객체지향 패러다임에 반하는 것으로 접근자와 수정자로 인해 캡슐화는 완전히 무너진다.
데이터 중심의 설계는 너무 이른 시기에 데이터에 대해 고민하기 때문에 캡슐화에 실패하게 된다.
내부 구현이 객체의 인터페이스를 어지럽히고 응집도와 결합도에 나쁜 영향을 미치기에 변경에 취약한 코드를 낳게 된다.

5.2. 객체를 고립시킨 채 오퍼레이션을 정의하도록 만든다.

데이터 중심의 설계는 객체의 외부가 아닌 내부로 향하기에 실행 문맥에 대한 고민없이 관리할 데이터의 세부 정보를 먼저 결정한다.
구현이 결정된 상태에서 다른 객체와 협력 방법을 고민하기 때문에 인터페이스를 억지로 끼워 맞출 수밖에 없다.