https://product.kyobobook.co.kr/detail/S000001766367
오브젝트 | 조영호 - 교보문고
오브젝트 | 역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음
product.kyobobook.co.kr
책 목차:
1장 객체, 설계 : https://inhyeok-blog.tistory.com/32
2장 객체지향 프로그래밍 : https://inhyeok-blog.tistory.com/33
3장 역할, 책임, 협력 : https://inhyeok-blog.tistory.com/34
4장 설계 품질과 트레이드오프 : https://inhyeok-blog.tistory.com/36
5장 책임 할당하기 : https://inhyeok-blog.tistory.com/37
6장 메시지와 인터페이스 :
7장 객체 분해 :
8장 의존성 관리하기 :
9장 유연한 설계 :
10장 상속과 코드 재사용 :
11장 합성과 유연한 설계 :
12장 다형성 :
13장 서브클래싱과 서브타이핑 :
14장 일관성 있는 협력 :
15장 디자인 패턴과 프레임워크 :
객체지향 설계란 올바른 객체에게 올바른 책임을 할당하면서 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 활동이다. 이는 객체지향 설계의 핵심이 책임이라는 것과 책임을 할당하는 작업이 응집도와 결합도 같은 설계 품질과 연관이 깊다는 것을 의미한다. 이번 장에서는 데이터 중심의 설계를 살펴보고 객체지향적으로 설계한 구조와 어떤 차이점이 있는지 살펴보자.
01. 데이터 중심의 영화 예매 시스템
앞으로 객체의 상태는 데이터와 동일한 용어로 사용하겠다.
객체지향 설계는 크게 2가지 방법으로 시스템을 객체로 분할할 수 있다.
- 상태를 분할의 중심축으로 삼는 방법
상태는 구현에 속한다. 구현은 변경되기 쉽고, 상태를 중심으로 삼으면 캡슐화의 원칙이 무너진다.
-> 변경에 취약해질 수 밖에 없다 - 책임을 분할의 중심축으로 삼는 방법
상태는 인터페이스에 속한다. 변경에 안정적이고, 캡슐화를 성공적으로 수행한다.
이번에는 데이터 중심의 분할 방법을 먼저 살펴보자
데이터를 준비하자
앞선 영화 예메 프로그램을 데이터 중심으로 설계해봤다. 각 객체가 가져야하는 데이터를 먼저 정의하고, Getter와 Setter만 정의했다.
영화를 예매하자
영화를 예매하는 로직은 ReservationAgency라는 객체가 담당하도록 했고, 여기에 reserve라는 메소드가 앞서 구현한 데이터를 가져와서 영화를 예매하는 로직을 수행하도록 했다.
02. 설계 트레이드오프
여기서는 데이터 중심 설계와 책임 중심 설계의 장단점을 비교하기 위해 캡슐화, 응집도, 결합도를 사용한다. 각각에 대해서 알아보자.
캡슐화
상태와 행동을 하나의 객체 안에 모으는 이유는 객체의 내부 구현을 외부로 부터 감추기 위해서다. 이를 통해서 변경에 의한 파급효과를 적절하게 조절할 수 있다. 여기서 변경가능성이 높은 부분을 구현이라 부르고, 상대적으로 안정적인 부분을 인터페이스라 부른다.
'캡슐화란 변경 가능성이 높은 부분을 객체 내부로 숨기는 추상화 기법이다.'
응집도와 결합도
응집도는 모듈에 포함된 내부 요소들이 연관돼 있는 정도를 나타낸다.
변경이 발생할 때 모듈 내부에서 발생하는 변경의 정도를 통해 알 수 있다. 하나의 변경에 의한 내부 변경이 같은 모듈에 모여있으면 응집도가 높은 것이고, 하나의 변경에 의해 다양한 모듈 내부에 변화가 있다면 응집도가 낮은 것이다.
결합도는 의존성의 정도를 나타내며, 다른 모듈에 대해서 얼마나 많은 지식을 갖고 있는지를 나타내는 척도이다.
결합도는 한 모듈이 변경되기 위해서 다른 모듈의 변경을 요구하는 정도로 측정된다. 예컨데 내부 구현을 변경했을 때 이것이 다른 모듈에 영향을 미치는 경우에는 두 모듈 사이의 결합도가 높다고 표현할 수 있고, 퍼블릭 인터페이스를 수정했을 때만 다른 모듈에 영향을 미치는 경우에는 결합도가 낮다고 표현할 수 있다.
03. 데이터 중심의 영화 예매 시스템의 문제점
앞서 데이터 중심의 설계로 만든 영화 예매 시스템은 객체 내부 구현을 인터페이스의 일부로 만든다. 캡슐화의 정도가 객체의 응집도와 결합도를 결정한다는 사실을 기억하라.
앞선 설계는 크게 3가지의 문제를 가진다.
- 캡슐화 위반
- 높은 결합도
- 낮은 응집도
캡슐화 위반
언듯 보기에 코드가 캡슐화를 지킨 것 같지만, Getter와 Setter를 사용하므로서 내부 구현을 인터페이스로 드러냈다.(이는 인스턴스 변수의 가시성을 public으로 만드는 것과 같은 것이다.) 이처럼 설계할 때 협력에 관해 고민하지 않으면 캡슐화를 위반하는 과도한 접근자와 수정자를 가지게 되는 경향이 있다.
앨린 홀립은 접근자와 수정자에 과도하게 의존하는 설계 방식을 추측에 의한 설계 전략이라고 부른다. 이는 결과적으로 변경에 취약한 설계를 만든다.
높은 결합도
앞서 접근자와 수정자를 때문에 캡슐화를 위반한 결과는 높은 결합도를 가져왔는데, 클라이언트 객체가 서버 객체의 구현(데이터)를 알고 있어야 하는 것이 문제인 것이다. 이는 서버 객체의 내부 구현을 변경했음에도 이 인터페이스에 의존하는 모든 클라이언트들도 함께 변경해야 하는 결과를 만들었다.
이뿐만이 아니다. 앞선 설계에서는 예약 로직이 특정 객체 안에 집중되기 때문에 하나의 제어 객체가 다수의 데이터 객체에 강하게 결합된다. 따라서 시스템 안의 어떤 변경도 ReservationAgency의 변경을 유발한다.
낮은 응집도
서로 다른 이유로 변경되는 코드가 하나의 모듈 안에 공존할 때 모듈의 응집도가 낮다고 한다. 이는 코드를 수정하는 이유가 여러가지인지 보면 되는데, 앞선 설계의 ReservationAgency코드를 수정하는 이유가 아주 많다.(제어로직이 모여있기 때문)
- 할인 정책이 추가될 경우
- 할인 정책별로 할인 요금을 계산하는 방법이 변경될 경우
- 할인 조건이 추가되는 경우
- 할인 조건별로 할인 여부를 판단하는 방법이 변경될 경우
- 예매 요금을 계산하는 방법이 변경될 경우
응집도가 낮을 때 두가지 측면의 문제가 있다.
- 변경과 아무 상관이 없는 코드들이 영향을 받게 된다.
- 하나의 요규사항을 반영하기 위해 동시에 여러 모듈을 수정해야한다.
단일 책임 원칙은 응집도가 변경과 연관이 있다는 사실을 강조한다.
클래스는 단 한가지의 변경 이유만 가져야 한다.
단일 책임의 원칙에서 "책임"은 "변경의 이유"라는 의미로 사용되고 있다. 지금까지 만한 역할, 책임, 협력과는 다른 것이다.
04. 자율적인 객체를 향해
캡슐화를 지켜라
캡슐화는 설계의 제1원리다. 데이터 중심의 설계가 낮은 응집도와 높은 결합도라는 문제를 가지게 된 것도 다 캡슐화의 원칙을 위반했기 때문이다. 앞서 언급한바와 같이 접근자나 수정자를 통해 속성을 외부로 제공하는 것 역시 캡슐화 위반이다. 그럼 접근자와 수정자를 남발하면 어떤 문제가 생길까?
- 코드중복
특정 데이터를 가지고 작업을 수행하는 로직이 여러곳에 중복 될 것이다. 예컨데, 사각형의 너비를 구하는 로직이 getter를 통해서 높이와 넓이를 곱하는 방식이라면 이곳저곳에서 이 로직을 중복 구현하게 되는 것 이다. - 변경에 취약함
데이터의 변수명이 바뀌거나, 타입이 바뀌면 의존하는 모든 객체가 변경된다.
해결 방법은 캡슐화를 강화 시키는 것이다. 이는 자신의 데이터를 스스로 변경하도록, 스스로 계산하도록 '책임을 이동'시킨 것이다. 이것이 바로 객체가 스스로를 책임진다는 말의 의미다.
스스로 자신의 데이터를 책임지는 객체
우리가 상태와 행동을 객체라는 하나의 단위로 묶는 이유는 객체 스스로 자신의 상태를 처리할 수 있게 하기 위해서다. 객체는 데이터보다 협력에 참여하면서 수행할 책임을 정의하는 오퍼레이션이 더 중요하다.
영화 예매 시스템의 자율성을 보장한 결과는 아래의 그림과 같다. 이는 객체 스스로 구현하고 있다.
05. 하지만 여전히 부족하다
앞선 개선은 분명 처음 설계보다 나아졌지만, 여전히 데이터 중심의 설계이며 같은 문제가 발생한다.
캡슐화 위반
기간 조건을 판단하는 isDiscountable(DayOfWeek dayOfWeek, LocalTime time)메서드의 시그니처는 구현을 알아야만 하며, 오버로딩된 isDiscountable(int sequence)역시 구현을 알아야 한다는 사실을 내포하고있다. 이는 여전히 구현이 수정되면 인터페이스가 변하는 문제점을 가지고 있으며, 캡슐화가 부족하다는 증거인 파급효과에서 자유로울 수 없다.
또한 Movie역시 내부 구현을 인터페이스에 노출시키고 있다. calculateAmountDiscountFee, calculatePercentDiscountFee, calculateNoneDiscountFee와 같은 메서드는 내부 구현을 만천하에 드러낸다.
캡슐화의 진정한 의미
캡슐화는 변경될 수 있는 어떤 것이라도 감추는 것을 의미한다. 설계에서 변하는 것이 무엇인지 고려하고 변하는 개념을 캡슐화해야 한다.
높은 결합도/낮은 응집도
캡슐화를 실패한 대가로 높은 결합도와 낮은 응집도를 가지게 되었다.
06. 데이터 중심 설계의 문제점
캡슐화를 위반하므로서 결과적으로 변경에 유연하지 못하게 만들었다. 왜일까?
- 너무 이른 시기에 데이터에 관해 결정하도록 강요한다.
- 협력이라는 문맥을 고려하지 않고 객체를 고립시킨 채 오퍼레이션을 결정한다.
데이터 중심 설계는 객체의 행동보다는 상태에 초점을 맞춘다
데이터 중심의 설계는 너무 이른 시기에 데이터에 대해 고민하기 때문에 캡슐화에 실패하게 된다. 객체의 내부 구현이 객체의 인터페이스를 어지럽히고 객체의 응집도와 결합도에 나쁜 영향을 미치기 때문에 변경에 취약한 코드를 낳게 된다.
데이터 중심 설계는 객체를 고립시킨 채 오퍼레이션을 정의하도록 만든다
객체지향 설계는 협력하는 객체들의 공동체를 구축한다는 것을 의미한다. 따라서 협력이라는 문맥 안에서 필요한 책임을 결정하고 이를 수행할 적절한 객체를 결정하는 것이 가장 중요하다. 올바른 객체지향 설계는 객체의 외부에 맞춰져 있어야 한다. 객체가 어떤 상태와 구현을 가지는지는 중요하지 않다. 중요한 것은 객체가 다른 객체와 협력하는 방법이다.(즉, 메시지가 우선되어야 하는 것이다.)
'책 리뷰' 카테고리의 다른 글
[가상 면접 사례로 배우는 대규모 시스템 설계 기초]1. 사용자 수에 따른 규모 확장성 (0) | 2023.05.15 |
---|---|
오브젝트[05 책임 할당하기] (1) | 2022.12.27 |
오브젝트[03 역할, 책임, 협력] (0) | 2022.12.18 |
오브젝트[02 객체지향 프로그래밍] (1) | 2022.12.13 |
오브젝트[01 객체, 설계] (2) | 2022.12.10 |