•
객체지향 설계의 핵심은 역할, 책임, 협력이다.
◦
협력 = 애플리케이션 기능 구현을 위한 메시지의 상호작용
◦
책임 = 객체가 협력을 위해 수행하는 행동
◦
역할 = 대체 가능한 책임의 집합
•
설계는 변경을 위해 존재하고 변경에는 어떤 식으로든 비용이 발생한다.
◦
훌륭한 설계란 합리적 비용 안에서 변경을 수용할 수 있는 구조를 만드는 것이다.
•
객체의 상태가 아닌 행동에 초점을 맞춘다면 응집도를 합리적인 수준으로 유지할 수 있다.
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. 객체를 고립시킨 채 오퍼레이션을 정의하도록 만든다.
•
데이터 중심의 설계는 객체의 외부가 아닌 내부로 향하기에 실행 문맥에 대한 고민없이 관리할 데이터의 세부 정보를 먼저 결정한다.
◦
구현이 결정된 상태에서 다른 객체와 협력 방법을 고민하기 때문에 인터페이스를 억지로 끼워 맞출 수밖에 없다.