[오브젝트] 1장. 객체, 설계
objects-study에서 스터디를 진행하고 있습니다.
도입부
로버트 L.글래스는 이론보다 실무가 먼저
라고 주장했다. 특히 소프트웨어 개발에서는 실무가 이론보다 앞서 있는 대표적인 분야로, 개발자는 구체적인 코드를 만지며 손을 더럽힐 때 가장 많은 것을 얻어간다. 따라서 이론과 개념은 잠시 뒤로 미루고, 간단한 프로그램을 하나 살펴 보려고 한다.
티켓 판매 애플리케이션 구현하기
- 소극장의 홍보를 위해 작은 이벤트를 기획하려고 한다.
- 이벤트 내용: 추첨을 통해 선정된 관람객에게 공연을 무료로 관람할 수 있는 초대장 발송
- 이벤트에 당첨된 관람객과 그렇지 못한 관람객을 분리해야 한다.
- 이벤트 당첨된 관람객: 초대장을 티켓으로 교환
- 이벤트 낙첨된 관람객: 티켓을 돈으로 구매
실습 코드: https://github.com/Java-Crew/objects-study/tree/jayon/src/main/java/chapter01/step01
무엇이 문제인가
로버트 마틴은 소프트웨어 모듈(크기와 상관 없이 클래스나 패키지, 라이브러리와 같은 프로그램을 구성하는 임의의 요소)이 가져야 하는 세 가지 기능에 관해 설명한다.
- 실행 중에 제대로 동작한다.
- 변경을 위해 존재한다.
- 간단한 작업만으로도 변경이 가능해야 한다.
- 코드를 읽는 사람과 의사소통하는 것이다.
- 개발자가 쉽게 읽고 이해할 수 있어야 한다.
앞선 티켓 판매 애플리케이션은 제대로 실행한다는 첫 번째 제약은 만족하지만, 변경 용이성과 의사소통이라는 목적은 만족하지 못하고 있다.
예상을 빗나가는 코드
의사 소통이라는 목적을 만족하지 못하는 이유를 살펴 보자.
- Theater 클래스의
enter()
메소드가 하는 일- 소극장은 관람객의 가방을 열어 그 안에 초대장이 들어 있는지 살펴 본다.
- 가방 안에 초대장이 들어 있으면 판매원에게 매표소에 보관되어 있는 티켓을 관람객의 가방 안으로 옮기라고 시킨다.
- 가방 안에 초대장이 들어 있지 않으면 관람객의 가방 안에서 티켓 금액만큼의 현금을 꺼내 티켓을 구매하고, 티켓을 가방 안에 넣는다.
- 관람객 입장에서는 소극장이라는 제 3자가 마음대로 가방을 뒤적이고 돈을 가져가고, 티켓을 넣는 것을 지켜봐야 한다.
- 판매원 입장에서는 소극장이라는 제 3자가 허락도 없이 매표소에 티켓과 현금의 값을 조작하는 것을 지켜봐야 한다.
- 이해 가능한 코드란 동작이 우리의 예상을 크게 벗어나지 않는 코드를 뜻하는데, 위 소극장은 우리의 예상을 벗어난다.
- 관람객은 스스로 가방 안에서 돈을 꺼내 판매원에게 지불하여 티켓을 받아야 한다.
- 판매원은 스스로 매표소에 있는 티켓을 직접 꺼내 관람객에게 전달하고, 관람객에게서 돈을 받아 매표소에 보관해야 한다.
- 또한,
enter()
메소드를 이해하기 위해서는 여러 가지 세부적인 내용을 모두 기억하고 있어야 한다.- Audience가 Bag을 가지고 있다.
- Bag 안에는 현금과 티켓이 있다.
- TiketSellet가 TicketOffice에서 티켓을 판매하고, TicketOffice 안에 돈과 티켓이 보관되어 있다.
변경에 취약한 코드
enter()
메소드는 2가지 조건을 가정한다.
- 관람객은 현금과 초대장을 보관하기 위해 항상 가방을 들고 다닌다.
- 판매원은 매표소에서만 티켓을 판매한다.
그렇다면 아래 상황은 어떨까?
- 관람객은 가방이 없을 수 있다.
- 관람객은 현금이 아니라 신용 카드를 사용할 수 있다.
- 판매원이 매표소 밖에서 티켓을 판매할 수 있다.
가령 첫 번째 요구 사항을 만족하려면, Audience의 Bag 객체를 없애야 하고, 이와 연관된 Theater 클래스의 enter()
메소드를 수정해야 한다. 이것은 Theater 클래스가 관람객은 가방을 들고 있고, 판매원은 매표소에서만 티켓을 판매한다는 지나치게 세부적인 사실에 의존하고 있기 때문이다. 이러한 세부적인 사실 중 하나라도 바뀌면 해당 클래스와 의존된 클래스(ex. Theater)를 모두 수정해야 한다.
이것은 객체 사이의 의존성과 관련된 문제이며, 의존성은 변경에 대한 영향을 암시한다. 하지만 객체 지향 설계는 서로 의존하면서 협력하는 객체의 공동체를 구축하는 목표이므로, 무작정 의존성을 없애는 것이 아니라 애플리케이션의 기능을 구현하는 데 필요한 최소한의 의존성만 유지하고 불필요한 의존성은 제거해야 한다.
객체 사이의 의존성이 과한 경우를 결합도가 높다고 하는데, 두 객체 사이의 결합도가 높으면 높을수록 함께 변경될 확률도 높아지게 된다. 따라서 설계의 목표는 객체 사이의 결합도를 낮춰 변경이 용이한 설계를 만드는 것이 된다.
설계 개선하기
관람객이 가방을 가지고 있다는 사실과 판매원이 매표소에서 티켓을 판매한다는 사실을 Theater이 알 필요가 없다. 단지 Theater는 관람객이 소극장에 입장하는 것을 원한다. 따라서 관람객이 스스로 가방 안의 현금과 초대장을 처리하고, 판매원이 스스로 매표소의 티켓과 판매 요금을 다루도록 자율적인 존재로 만들어야 한다.
자율성을 높이자
밀접하게 연관된 작업만을 수행하고 연관성 없는 작업은 다른 객체에게 위임하는 객체를 가리켜 응집도가 높다고 말한다. 자신의 데이터를 스스로 처리하는 자율적인 객체를 만들면 결합도를 낮추고 응집도를 높일 수 있게 된다.
절차 지향과 객체 지향
- 절차 지향
- Theater의
enter()
메서드는 프로세스이며, Audience, TicketSeller, Bag, TicketOffice는 데이터다. - 프로세스와 데이터를 별도의 모듈에 위치하는 방식을 절차 지향적 프로그래밍이라고 부른다.
- 우리의 직관에 위배되는 코드가 많다. (ex. 관람객은 스스로 돈과 초대장을 관리한다.)
- 데이터의 변경으로 인한 영향을 좁히기 어렵다.
- 책임을 중앙 집중식으로 관리한다. (Theater가 모두 관리)
- Theater의
- 객체 지향
- 데이터와 프로세스가 동일한 모듈 내부에 위치하는 방식을 객체 지향적 프로그래밍이라고 부른다.
- 우리의 직관에 맞는 코드를 작성할 수 있다.
- 데이터의 변경으로 인한 영향을 캡슐화를 통해 효과적으로 좁힐 수 있다.
- 각 객체가 자신을 스스로 책임진다.
실습 코드: https://github.com/Java-Crew/objects-study/tree/jayon/src/main/java/chapter01/step02
더 개선할 수 있다.
- Audience 클래스의 Bag 클래스는 여전히 Audience 클래스에 의해 끌려다니는 수동적인 존재이므로 Bag 클래스를 자율적인 객체로 만들어야 한다.
- TicketSeller 클래스의 TicketOffice 역시 TicketSeller에 의해 마음대로 관리 당한다. TicketOffice를 자율적인 객체로 만들어야 한다.
- 하지만 변경 이후 TicketOffice는 Audience와 추가 결합도가 발생했다.
- 이처럼 설계는 트레이드 오프를 잘 따져야 한다. 이 경우 Audience와 결합도를 낮추기 위해 TicketOffice를 어느 정도 수동적인 객체를 만들기로 합의를 볼 수도 있다.
실습 코드: https://github.com/Java-Crew/objects-study/tree/jayon/src/main/java/chapter01/step03
그래, 거짓말이다!
- 현실에서는 수동적인 존재라고 하더라도 객체 지향의 세계에 들어오면 모든 것이 능동적이고 자율적인 존재로 바뀐다.
- 의인화를 사용하여 수동적인 객체를 마치 웃고, 떠들고 화내는 대상으로 생각하는 것이 좋다.
객체 지향 설계
설계가 왜 필요한가
- 설계란 코드를 배치하는 것이다.
- 좋은 설계란 오늘 요구하는 기능을 온전히 수행하면서 내일의 변경을 매끄럽게 수용할 수 있는 것이다.
객체 지향 설계
- 변경 가능한 코드란 이해하기 쉬운 코드다.
- 객체 지향 패러다임은 우리가 세상을 바라보는 방식대로 코드를 작성할 수 있게 돕는다.
- 객체는 자신의 데이터를 스스로 책임지는 자율적인 존재다.
- 훌륭한 객체 지향 설계란 협력하는 객체 사이의 의존성을 적절하게 관리하는 설계다.
출처
- 오브젝트
'스터디 > 오브젝트 스터디' 카테고리의 다른 글
[오브젝트] 2장. 객체 지향 프로그래밍 (0) | 2022.04.25 |
---|
댓글