오브젝트 1장 객체, 설계
무엇이 문제인가
모든 소프트웨어 모듈에는 세 가지 목적이 있다. 첫 번째 목적은 실행 중에 제대로 동작하는 것이다. 이것은 모듈의 존재 이유라고 할 수 있다. 두 번째 목적은 변경을 위해 존재하는 것이대 다부분의 모듈은 생명주기 동안 변경되기 때문에 간단한 작업만으로도 변경이 가능해야 한다. 변경하기 어려운 모듈은 제대로 동작하더라도 개선해야 한다. 모듈의 세 번째 목적은 코드를 읽는 사람과 의사소통하는 것이다. 모듈은 특별한 훈련 없이도 개발자가 쉽게 이해할 수 있어야 한다. 읽는 사람과 의사소통할 수 없는 모듈은 개선해야 한다.
- 14p (로버트 마틴의 클린 소프트웨어 중에서 재인용)
앞에서 작성한 프로그램은 관람객들을 입장시키는 데 필요한 기능을 오류 없이 정확하게 수행하고 있다. 따라서 제대로 동작해야 한다는 제약은 만족시킨다. 하지만 불행하게도 변경 용이성과 읽는 사람과의 의사소통이라는 목적은 만족시키지 못한다.
- 14p
문제는 관람객과 판매원이 소극장의 통제를 받는 수동적인 존재라는 점이다.
현실에서는 관람객이 직접 자신의 가방에서 초대장을 꺼내 판매원에게 건넨다. 티켓을 구매하는 관람객은 가방 안에서 돈을 직접 꺼내 판매원에게 지불한다. 판매원은 매표소에 있는 티켓을 직접 꺼내 관람객에게 건네고 관람객에서 직접 돈을 받아 매표소에 보관한다. 하지만 코드 안의 관람객, 판매원은 그렇게 하지 않는다. 현재의 코드는 우리의 상식과는 너무나도 다르게 동작하기 때문에 코드를 읽는 사람과 제대로 의사소통하지 못한다.
- 15p
여러가지 세부적인 내용들을 한꺼번에 기억하고 있어야 한다는 점이다. ... 가장 심각한 문제는 Audience와 TicketSeller를 변경할 경우 Theater도 함께 변경해야 한다는 사실이다.
더 큰 문제는 변경에 취약하다는 것이다.
- 16p
이것은 객체 사이의 의존성과 관련된 문제다. 문제는 의존성이 변경과 관련돼 있다는 점이다. 의존성은 변경에 대한 영향을 암시한다. 의존성이라는 말 속에는 어떤 객체가 변경될 때 그 객체에게 의존하는 다른 객체도 함께 변경될 수 있다는 사실이 내포돼 있다.
- 16p
관람객과 판매원을 자율적인 존재로 만들기
package ch1;
public class Theater {
private TicketSeller ticketSeller;
public Theater(TicketSeller ticketSeller){
this.ticketSeller = ticketSeller;
}
public void enter(Audience audience){
ticketSeller.sellTo(audience);
}
}
수정된 Theater 클래스 어디서도 ticketOffice에 접근하지 않는다는 사실에 주목하라. Theater는 ticketOffice가 TicketSeller 내부에 존재한다는 사실을 알지 못한다. Theater는 단지 ticketSeller가 sellTo 메시지를 이해하고 응답할 수 있다는 사실만 알고 있을 뿐이다.
- 20p
package ch1;
public class Audience {
private Bag bag;
public Audience(Bag bag){
this.bag = bag;
}
public Bag getBag(){
return bag;
}
public Long buy(Ticket ticket){
if (bag.hasInvitation()){
bag.setTicket(ticket);
return 0L;
} else {
bag.setTicket(ticket);
bag.minusAmount(ticket.getFee());
return ticket.getFee();
}
}
}
변경된 코드에서 Audience는 자신의 가방 안에 초대장이 들어 있는지를 스스로 확인한다. 외부의 제3자가 자신의 가방을 열어보도록 허용하지 않는다. Audience가 Bag을 직접 처리하기 때문에 외부에서는 더 이상 Audience가 Bag을 소유하고 있다는 사실을 알 필요가 없다. 이제 Audience 클래스에서 getBag 메서드를 제거할 수 있고 결과적으로 Bag의 존재를 내부로 캡슐화할 수 있게 됐다.
- 23p
ticketSeller가 Audience의 인터페이스에만 의존하도록 수정
package ch1;
public class TicketSeller {
private TicketOffice ticketOffice;
public TicketSeller(TicketOffice ticketOffice){
this.ticketOffice = ticketOffice;
}
public void sellTo(Audience audience) {
ticketOffice.plusAmount(audience.buy(ticketOffice.getTicket()));
}
}
- 23p
캡슐화를 개선한 후에 가장 크게 달라진 점은 Audience와 TicketSeller가 내부 구현을 외부에 노출하지 않고 자신의 문제를 스스로 책임지고 해결한다는 것이다.
- 24p
핵심은 객체 내부의 상태를 캡슐화하고 객체 간에 오직 메시지를 통해서만 상호작용하도록 만드는 것이다. Theater는 TicketSeller의 내부에 대해서는 전혀 알지 못한다. 단지 TicketSeller가 sellTo 메시지를 이해하고 응답할 수 있다는 사실만 알고 있을 뿐이다. TicketSeller 역시 Audience의 내부에 대해서는 전혀 알지 못한다. 단지 Audience가 buy 메시지에 응답할 수 있고 자신이 원하는 결과를 반환할 것이라는 사실만 알고 있을 뿐이다.
밀접하게 연관된 작업만을 수행하고 연관성 없는 작업은 다른 객체에게 위임하는 객체를 가리켜 응집도가 높다고 말한다. 자신의 데이터를 스스로 처리하는 자율적인 객체를 만들면 결합도를 낮출 수 있을뿐더러 응집도를 높일 수 있다.
객체의 응집도를 높이기 위해서는 개체 스스로 자신의 데이터를 책임져야 한다. 자신이 소유하고 있지 않은 데이터를 이용해 작업을 처리하는 객체에게 어떻게 연관성 높은 작업들을 할당할 수 있겠는가? 객체는 자신의 데이터를 스스로 처리하는 자율적인 존재여야 한다. 그것이 객체의 응집도를 높이는 첫걸음이다. 외부의 간섭을 최대한 배제하고 메시지를 통해서만 협력하는 자율적인 객체들의 공동체를 만드는 거이 훌륭한 객체지향 설계를 얻을 수 이는 지름길인 것이다.
- 25 ~ 26p
수정하기 전의 코드에서는 Theater의 enter 메서드 안에서 audience와 TicketSeller로부터 Bag과 TicketOffice를 가져와 관람객을 입장시키는 절차를 구현했다. Audience, TicketSeller, Bag, TicketOffice는 관람객을 입장시키는 데 필요한 정보를 제공하고 모든 처리는 Theater의 enter 메서드 안에 존재했었다는 점에 주목하라.
이 관점에서 Theter의 enter 메서드는 프로세스이며 Audience, TicketSeller, Bag, TicketOffice는 데이터다. 이처러 프로세스와 데이터를 별도의 모듈에 위치시키는 방식을 절차적 프로그래밍이라고 부른다.
...
해결 방법은 자신의 데이터를 스스로 처리하도록 프로세스의 적절한 단계를 Audience와 TicketSeller로 이동시키는 것이다. 수정한 후의 코드에서는 데이터를 사용하는 프로세스가 데이터를 소유하고 있는 Audience와 TicketSeller 내부로 옮겨졌다. 이처럼 데이터와 프로세스가 동일한 모듈 내부에 위치하도록 프로그래밍하는 방식을 객체지향 프로그래밍이라고 부른다.
두 방식의 차이점을 가장 쉽게 이해할 수 있는 방법은 기능을 처리하는 방법을 살펴보는 것이다. .. 작업 흐름이 주로 Theater에 의해 제어된다는 사실을 알 수 있다. 객체지향 세계의 용어를 사용해서 표현하면 책임이 Theater에 집중돼 있는 것이다.
- 27p
변경 후의 객체지향 설계에서는 각 객체가 자신이 맡은 일을 스스로 처리했다. 다시 말해 Theater에 몰려 있던 책임이 개별 객체로 이동한 것이다. 이것이 바로 책임의 이동이 의미하는 것이다.
... 이런 관점에서 객체지향 프로그래밍을 흔히 데이터와 프로세스를 하나의 단위로 통합해 놓는 방식으로 표현하기도 한다. 비록 이 관점이 객체지향을 구현 관점에서만 바라본 지극히 편협한 시각인 것은 맞지만 객체지향에 갓 입문한 사람들에게 어느 정도 도움이 되는 실용적인 조언인 것 또한 사실이다.
- 28p
TicketSeller의 책임은 무엇인가? 티켓을 판매하는 것이다. Audience의 책임은 무엇인가? 티켓을 사는 것이다. Theater의 책임은 무엇인가? 관람객을 입장시키는 것이다. 적절한 객체에 적절한 책임을 할당하면 이해하기 쉬운 구조와 읽기 쉬운 코드를 얻게 된다.
- 29p
적절한 트레이드오프
TicketOffice의 자율성보다는 Audience에 대한 결합도를 낮추는 것이 더 중요하다는 결론에 도달했다.
- 33p
우리는 오늘 완성해야 하는 기능을 구현하는 코드를 짜야 하는 동시에 내일 쉽게 변경할 수 있는 코드를 짜야한다[Metz12]
- 35p
'Book' 카테고리의 다른 글
[독서 기록] 오브젝트 3장 역할, 책임, 협력 (0) | 2023.01.15 |
---|---|
[독서 기록] 오브젝트 2장 객체, 설계 (0) | 2023.01.15 |
[독서 기록] 모던 자바 인 액션 18장-21장, 함수형 프로그래밍과 자바 진화의 미래 (0) | 2023.01.13 |
[독서 기록] 모던 자바 인 액션 13장, 디폴트 메서드 (0) | 2023.01.11 |
[독서 기록] 모던 자바 인 액션 12장, 새로운 날짜와 시간 api (0) | 2023.01.11 |