Chapter 07 함께 모으기
코드와 모델을 밀접하게 연결시키는 것은 코드에 의미를 부여하고 모델을 적절하게 한다. -Eric Evans-
개념 관점 conceptual perspective에서 설계는 도메인 안에 존재하는 개념과 개념들 사이의 관계를 표현한다.
명세 관점 specification perspective은 도메인의 개념이 아니라 실제 sw안에서 살아 숨쉬는 객체들의 책임에 초점을 맞추게 된다. 객체의 인터페이스를 바라보는 것이다. 프로그래머는 객체가 협력을 위해 '무엇을' 할 수 있는가에 초점을 맞춘다.
구현 관점 implementation perspective은 프로그래머인 우리에게 가장 익숙한 관점으로, 실제 작업을 수행하는 코드와 연관돼 있다. 객체들이 책임을 수행하는 데 필요한 동작하는 코드를 작성하는 것이다.
커피 전문점 도메인
커피 주문 - 예제
이번에 다룰 도메인은 동네 어디에서나 볼 수 있는 아담한 커피 전문점이다. 커피 전문점에서는 아메리카노, 카푸치노, 카라멜 마키아또, 에스프레소의 4가지 커피를 판매한다.
<메뉴판>
아메리카노 1500, 카푸치노 2000, 카라멜 마키아또 2500, 에스프레소 2500
손님이 테이블에 앉아 메뉴판을 잠시 훑어본 후 커피를 주문한다. 주문받은 커피를 제조하는 것은 바리스타의 몫이다.
커피 전문점이라는 세상
객체지향의 관점에서 메뉴판은 하나의 객체다. 메뉴 항목들 역시 객체로 볼 수 있다. 따라서 메뉴판은 4개의 메뉴 항목 객체들을 포함하는 객체라고 볼 수 있다.
손님은 메뉴판을 보고 바리스타에게 원하는 커피를 주문한다. 객체의 관점에서 보면 손님 역시 하나의 객체다. 손님 객체는 메뉴판 객체 안에 적힌 메뉴 항목들 중에서 자신이 원하는 메뉴 항목 객체 하나를 선택해 바리스타 객체에게 전달할 것이다.
바리스타는 주문을 받은 메뉴에 따라 적절한 커피를 제조한다. 바리스타가 제조할 수 있는 커피의 종류는 4가지이다. 바리스타는 자율적으로 커피를 제조하는 객체로 볼 수 있으며, 바리스타가 제조하는 커피 역시 객체로 볼 수 있다.
✔ 먼저 어떤 객체가 존재하는지 살펴봐야 한다.
✔ 그 다음으로는 객체들 간의 관계를 살펴봐야 한다. (여기서 "어떤"관계인지는 사실 중요하지 않음!!!)
우리가 할 수 있는 일은 동적인 객체를 정적인 타입으로 추상화해서 복잡성을 낮추는 것이다. 상태와 무관하게 동일하게 행동할 수 있는 객체들은 동일한 타입의 인스턴스로 분류할 수 있다.
손님 객체는 '손님 타입'의 인스턴스로 볼 수 있다. 바리스타 객체는 '바리스타 타입'의 인스턴스로 볼 수 있다. 아메리카노, 카푸치노, 카라멜 마키아또, 에스프레소는 모두 '커피 타입'의 인스턴스로 볼 수 있다. 메뉴판 객체는 '메뉴판 타입'의 인스턴스다. 메뉴판 객체는 아메리카노, 카푸치노, 카라멜 마키아또, 에스프레소라는 4개의 메뉴 항목 객체를 포함할 수 있다. 4개의 메뉴 항목 객체 역시 모두 동일한 '메뉴 항목 타입'의 인스턴스로 모델링할 수 있다.
커피 전문점을 구성하는 범주로서 손님 타입, 바리스타 타입, 메뉴판 타입, 커피 타입, 메뉴 항목 타입이 갖춰졌다.
✔ 이제 타입 간에 어떤 관계가 존재하는 지 알아보자.
하나의 메뉴판 객체는 다수의 메뉴 항목 객체로 구성돼 있다. 메뉴판과 메뉴 항목 객체는 따로 떨어져 존재하지 않으며 하나의 단위로 움직인다.
메뉴판과 메뉴 항목은 포함containment관계 또는 합성composition관계를 나타낸다.
손님 타입은 메뉴판 타입을 알고 있어야 원하는 커피를 선택할 수 있다. 이처럼 한 타입의 인스턴스가 다른 타입의 인스턴그를 포함하지는 않지만 서로 알고 있어야 할 경우 이를 연관association관계라고 한다. (그냥 단순한 선으로 연결)
바리스타 타입은 커피를 제조해야 하므로 커피 타입을 알고 있어야 한다.
✔ 도메인을 단순화해서 이해하는 과정
✔ 커피 전문점이라는 도메인을 단순화해서 이해했으므로 이제 초점을 sw로 옮겨보자.
설계하고 구현하기
✔ 커피를 주문하기 위한 협력 찾기
🌟훌룡한 객체를 설계하는 것이 아니라 훌룡한 협력을 설계하는 게 제일 중요하다는 걸 잊지 말자!🌟
객체가 메시지를 선택하는 것이 아니라, 메시지가 객체를 선택하게 해아 한다. 메시지를 먼저 선택하고 그 후에 메시지를 수신하기에 적절한 객체를 선택해야 한다는 것을 의미한다. 메시지를 수신할 객체는 메시지를 처리할 책임을 맡게 되고 객체가 수신하는 메시지는 객체가 외부에 제공하는 공용 인터페이스에 포함된다.
첫 번째 메시지는 '커피를 주문하라'일 것이다.
메시지 위에 붙은 화살표는 메시지에 담아 전달될 부가적인 정보인 인자를 의미한다. 이 경우 '커피를 주문하라(아메리카노)'와 같이 인자를 포함하는 형식으로 구현된다.
메시지를 처리할 객체를 찾고 있다면 먼저 도메인 모델 안에 책임을 수행하기에 적절한 타입이 존재하는지 살펴보라. 적절한 타입은 찾으면 책임을 수행할 객체를 그 타입의 인스턴스로 만든다.
어떤 객체가 커피를 주문할 책임을 져야 하는가? ➡ 손님객체이기 때문에 메시지를 처리할 객체는 손님 타입의 인스턴스이다. 이제 손님 객체는 커피를 주문할 책임을 할당 받았다.
손님이 할당된 책임을 수행하는 도중에 스스로 할 수 없는 일이 있다면 다른 객체에게 이를 요청해야 한다. 이 요청이 바로 손님 객체에서 외부로 전송되는 메시지를 정의한다.
고객은 누군가가 메뉴를 제공해 줄 것을 요청한다. 따라서 '메뉴항목을 찾아라'라는 메시지가 필요한다.
메뉴 항목을 찾을 책임을 누구에게 할당하는 것이 좋을까? ➡ 메뉴 항목 객체들을 포함하고 있는 메뉴판 객체에 할당한다.
손님은 이제 메뉴 항목에 맞는 커피를 제조해달라고 요청할 수 있다. 손님은 커피를 제조하는 메시지의 인자로 메뉴 항목을 전달하고 반환값으로 제조된 커피를 받아야 한다.
누가 커피를 제조해야 하는가? ➡ 바리스타 객체를 선택한다.
바리스타는 커피를 제조하는 데 필요한 정보를 모두 가지고 있다.
협력에 필요한 객체의 종류, 책임, 주고받아야 하는 메시지에 대한 대략적인 윤곽이다. 이제 남은 일은 메시지를 정의함으로써 각 객체의 인터페이스를 구현 가능할 정도로 상세하게 정제하는 것이다.
인터페이스 정리하기
메시지가 객체를 선택했고, 선택된 객체는 메시지를 자신의 인터페이스로 받아들인다. 각 객체를 협력이라는 문맥에서 떼어내서 수신 가능한 메시지만 추려내면 객체의 인터페이스가 된다. 객체가 어떤 메시지를 수신할 수 있다는 건 그 객체의 인터페이스 안에 메시지에 해당하는 오퍼레이션이 존재한다는 것을 의미한다.
- 손님 객체의 인터페이스 안에는 '커피를 주문하라'라는 오퍼레이션이 포함돼야 한다.
- 메뉴판 객체의 인터페이스는 '메뉴 항목을 찾아라'라는 오퍼레이션을,
- 바리스타 객체의 인터페이스는 '커피를 제조하라'라는 오퍼레이션을,
- 커피 객체는 '커피를 제조하라'라는 오퍼레이션을 제공한다.
💡 객체들을 포괄하는 타입을 정의한 후 식별된 오퍼레이션을 타입의 인터페이스에 추가해야 한다.
객체의 타입을 구현하는 가장 일반적인 방법은 클래스를 이용하는 것이다.
협력을 통해 식별된 오퍼레이션은 공용 인터페이스의 일부이기 때문에 공용public으로 선언돼 있어야 한다.
class Customer {
public void order(String menuName) {}
}
class MenuItem {
}
class Barista {
public Coffee makeCoffee(MenuItem menuItem) {}
}
class Menu {
public MenuItem choose(String name) {}
}
class Coffee {
public Coffee(MenuItem menuItem) {}
}
구현하기
클래스의 인터페이스를 식별했으므로 이제 오퍼레이션을 수행하는 방법을 메서드로 구현하자.
💡 코드 자체가 중요한 게 아니라 어떤 방식으로 코드를 짜야 하는지에 집중!
💡 인터페이스와 구현을 분리하는 것이 가장 중요한 핵심!
Customer 객체는 어떤 방법으로든 자신과 협력하는 Menu객체와 Barista객체에 대한 참조를 알고 있어야 한다. 여기서는 order 메소드의 인자로 Menu와 Barista의 객체를 전달받는 방법을 사용했다.
Menu는 menuName에 해당하는 MenuItem을 찾아야 하는 책임이 있다. 이 책임을 수행하기 위해서는 Menu가 내부적으로 MenuItem을 관리하고 있어야 한다. 간단하게 Menu가 MenuItem의 목록을 포함하게 한다. Menu의 choose()메소드는 MenuItem의 목록을 하나씩 검사해가면서 이름이 동일한 MenuItem을 찾아서 반환한다.
Barista는 MenuItem을 이용해 커피를 제조한다.
Coffee는 자기 자신을 생성하기 위한 생성자를 제공한다. Coffee는 커피 이름, 가격을 속성으로 가지고 생성자 안에서 MenuItem에 요청을 보내 커피 이름, 가격을 얻은 후 Coffee의 속성에 저장한다.
MenuItem은 getName()과 cost() 메시지에 응답할 수 있도록 메서드를 구현해야 한다.
📌느낀점
앞에 챕터들에서 배운 이론적인 내용을 이번 7장에서 모두 활용한 것 같다. 직접 작은 프로그램을 하나 만들면서 꼼꼼히 자세하게 설계를 하는 과정이 의미 있었다. 학교에서 처음 만들어본 자바 프로젝트를 설계할 때는 이것보다 규모가 컸지만, 이 정도로 모든 걸 체계적으로 설계하지 못했다. 아주 간단한 프로그램이지만 이렇게 모든 객체를 하나하나 고민하고, 메서드를 고민하고, 메시지 하나도 어떤 객체에게 책임을 줘야 하는지 고민하니까 완성된 설계 자체만 중요한 게 아니라 설계하는 과정도 정말 중요하다는 걸 느꼈다. 그리고 매번 설계할 때마다 이번 7장에서 배운 설계 마인드를 되새겨야겠다.
솔직히 객체지향설계라는 걸 이렇게 따로 공부를 해야 할까? 라는 생각이 처음에는 들었지만, 소프트웨어적인 사고와 모든 프로젝트의 기반이 되는 객체지향적인 사고를 만드는 데 정말 큰 도움이 되었다. 여태까지 과제를 할 때 정확하고 체계적인 설계를 하지 못하고 어영부영 돌아가기만 하는 프로그램을 만든 것 같아서 내 자신을 돌아보게 되었다. 이번 책을 읽고 앞으로 하는 설계들은 완성도 있게 설계할 수 있을 것 같다.
나중에 두고 설계를 하다가 모르겠을 때, 내가 설계를 너무 복잡하게 한 건 아닌지 생각이 들 때..종종 참고를 할 것 같아서 1장부터 7장까지 열심히 기록했다. 이렇게 좋은 책을 주신 SK DEOCEAN 에게 너무 감사하고🖤 같이 열심히 스터디를 진행해준 경욱한테도 고맙다!✨
이 스터디를 계기로 sw 관련된 책을 읽으면서 리뷰를 하는 컨텐츠를 장기적으로 해봐야겠다,,:)
같이 스터디하는 데보션 영 김경욱님의 블로그 링크 첨부! 책 완주하느라 수고하셨습니다!:)
'Book Review' 카테고리의 다른 글
객체지향의 사실과 오해 Chapter 06 리뷰 (0) | 2022.09.27 |
---|---|
객체지향의 사실과 오해 Chapter 05 리뷰 (0) | 2022.09.20 |
객체지향의 사실과 오해 Chapter 04 리뷰 (0) | 2022.09.14 |
객체지향의 사실과 오해 Chapter 03 리뷰 (0) | 2022.09.07 |
객체지향의 사실과 오해 Chapter 02 리뷰 (0) | 2022.08.31 |