✏️, 💡,❓ 해당 이모지는 저의 생각임을 나타냅니다.
이 책에서는 객체지향 패러다임을 설명하기 위해 추상적인 개념이나 이론을 앞세우지 않을 것이다. '코드'를 이용해 객체지향의 다양한 측면을 설명하려고 노력할 것이다.
01 티켓 판매 애플리케이션 구현하기
티켓 판매 애플리케이션을 만들어보자. 주의해야할 점은 이벤트에 당첨된 관람객과 그렇지 못한 관람객은 다른 방식으로 입장시켜야 한다는 것이다.
최종 코드는 아래와같이 된다. (코드 확인)
public class Theater {
private TicketSeller ticketSeller;
public Theater(TicketSeller ticketSeller) {
this.ticketSeller = ticketSeller;
}
public void enter(Audience audience) {
if(audience.getBag().hasInvitation()) {
Ticket ticket = ticketSeller.getTicketOffice().getTicket();
audience.getBag().setTicket(ticket);
} else {
Ticket ticket = ticketSeller.getTicketOffice().getTicket();
audience.getBag().minusAmount(ticket.getFee());
ticketSeller.getTicketOffice().plusAmount(ticket.getFee());
audience.getBag().setTicket(ticket);
}
}
}
❓ 의문점 1: ticketSeller가 ticketOffice를 가지고 있는 형태가 처음에 부자연스럽게 느껴졌다. office가 아니라 ticket storage box같은 것이었으면 더 좋았을 것 같다.
❓ 의문점 2: Theater의 enter에서 티켓을 검사하고, 티켓을 저장시키는 행위가 일어나고 있는데 어색함이 느껴졌다. 행위가 일어나는 class가 바뀌어야 할 것 같았다.
02 무엇이 문제인가
로버트 마틴(클린 소프트웨어)에서 소프트웨어 모듈이 가져야 하는 세 가지 기능에 관해 설명한다. 모듈이란 크기와 상관 없이 클래스나 패키지, 라이브러리와 같이 프로그램을 구성하는 임의의 요소를 의미한다.
1) 실행 중에 제대로 동작하는 것.
2) 변경을 위해 존재하는 것. 간단한 작업만으로도 변경이 가능해야 한다.
3) 코드를 읽는 사람과 의사소통하는 것. 모듈은 특별한 훈련 없이도 개발자가 쉽고 이해할 수 있어야 한다.
하지만 위에서 작성한 프로그램은 변경 용이성과 읽는 사람과의 의사소통이라는 목적은 만족시키지 못한다.
예상을 빗나가는 코드
Theather 클래스의 enter메서드를 말로 풀어보면 아래와 같다.
소극장은 관람객의 가방을 열어 초대장이 있는지 살펴본다. 가방안에 초대장이 들어 있으면 판매원은 매표소에 보관돼 있는 티켓을 관람객의 가방 안으로 옮긴다.... ...
첫번째 문제점) 관람객과 판매원이 소극장의 통제를 받는 수동적인 존재이다.
두번째 문제점) 코드를 이해하기 위해서는 세부적인 내용을 한번에 기억하고 있어야 한다. Theater의 enter메서드를 이해하려면 Audience가 Bag를 가지고 있고 Bag안에는 현금과 티켓이 있으며 TicketSeller가 TicketOffice에서 티켓을 판매한다는 등의 사실을 동시에 기억해야 한다.
세번째 문제점) 가장 심각한 문제는, Audience와 TicketSeller를 변경할 경우 Theater도 함께 변경해야 한다는 사실이다.
변경에 취약한 코드
가장 큰 문제는 변경에 취약하다는 것이다. 해당 코드는 관람객이 현금과 초대장을 위해 항상 가방을 들고 다닌다고 가정한다. 또한 판매원이 매표소에서만 티켓을 판매한다고 가정한다. 이런 가정이 깨지는 순간 모든 코드는 일시에 흔들리게 된다.
관람객이 가방을 들고 있다는 가정이 바뀌면, Audience 클래스에서 Bag를 제거해야 할 뿐만 아니라 Theater의 enter 메서드 역시 수정해야 한다. 이처럼 다른 클래스가 Audience의 내부에 대해 알면 알수록 Audience를 변경하기 어려워진다.
이것은 객체 사이의 의존성(dependench)와 관련된 문제다. 의존성은 변경에 대한 영향을 암시한다. => 어떤 객체가 변경될 때 그 객체에 의존하는 다른 객체도 함께 변경될 수 있다는 사실이 내포되어 있다.
그렇다고 객체 사이 의존성을 완전 없애는 게 정답은 아니다. 객체지향 설계는 서로 의존하면서 협력하는 객체들의 공동체를 구축하는 것이다. => 최소한의 의존성만 유지, 불필요한 의존성 제거.
객체 사이 의존성이 과한 경우를 결합도(coupling)가 높다고 한다. 두 객체 사이 결합도가 높을 수록 함께 변경될 확률도 높아지기 때문에 변경하기 어려워진다. 설계의 목표는 객체 사이 결합도를 낮춰 변경이 용이한 설계를 만드는 것이어야 한다.
03 설계 개선하기
코드의 해결 방법은 간단하다. Theater가 Audience와 TicketSeller에 관해 너무 세세한 부분까지 알지 못하도록 정보를 차단하면 된다. 사실 관람객이 가방이 있다는 사실과 판매원이 매표소에서 티켓을 판매한다는 사실을 Theater가 알아야 할 필요가 없다.
자율성을 높이자
어떻게 하면 될까? 간단하다. 판매자가 티켓을 판매하기 위해 TicketOffice를 사용하는 모든 부분을 TicketSeller 내부로 옮기고, 관람객이 티켓을 구매하기 위해 Bag를 사용하는 모든 부분을 Audience 내부로 옮기면 된다. (코드 확인)
이렇게 할 경우 아래와 같이 변한다. ->
1) 외부에서 Ticket Office 접근 불가 (TicketSeller만 접근 가능) - 캡슐화
2) Theater은 TicketSeller의 인터페이스에만 의존
3) Audience 스스로 Bag 관리
위 그림을 보았을 때 Theater에서 TicketOffice로의 의존성이 제거됐다는 사실을 알 수 있다.
캡슐화와 응집도
핵심은 객체 내부 상태를 캡슐화하고 객체 간에 오직 메시지를 통해 상호작용 하도록 하는 것이다.
밀접한 작업만 수행하고 연관성 없는 작업은 다른 객체에게 위임하는 객체를 응집도(cohesion)가 높다고 말한다. 자신의 데이터를 스스로 처리하는 자율적인 객체를 만들면 결합도를 낮추고 응집도를 높일 수 있다.
절차지향과 객체지향
처음 작성했던 코드는 절차적 프로그래밍 방식으로 작성된 코드의 의존성을 보여준다. 모든 처리가 하나의 클래스 안에 위치하고 나머지 클래스는 단지 데이터의 역할만 수행한다.
절차적 프로그래밍 세계에서는 관람객과 판매원이 수동적인 존재이다. 그리고 절차적 프로그래밍은 프로세스가 필요한 모든 데이터에 의존해야한다는 근본적 문제때문에 변경에 취약하다.
데이터와 프로세스가 동일한 모듈 내부에 위치하도록 프로그래밍 하는 것을 객체지향 프로그래밍이라고 부른다.
절차적 프로그래밍에서는 책임이 Theater에게만 있었는데, 객체지향 설계에서는 각 객체에 책임이 분배되게끔 몰려있던 책임이 개별 객체로 이동했다. 이것이 책임의 이동이 의미하는 것이다.
객체는 책임을 수행해야 하기 때문에 객체가 어떤 데이터를 가지느냐 보다는 객체에 어떤 책임을 할당할 것이냐에 초점을 맞춰야 한다.
설계를 어렵게 만드는 것은 의존성이다. 해결 방법은 불필요한 의존성을 제거해 객체 사이 결합도를 낮추는 것이다. 그러기 위해선 몰라도 되는 세부사항을 캡슐화해야 한다. 캡슐화를 하게 되면 자율성을 높이고 응집도 높은 객체들의 공동체를 창조할 수 있다.
더 개선할 수 있다
아직도 코드에 개선의 여지가 있다. Bag와 TicketOffice는 현재 Adudience와 TicketSeller에서 조작하고 있다. 각자의 데이터를 각자가 관리하도록 수정해준다. (코드 확인)
하지만 이렇게 된다면 TicketOffice와 Audience 사이에 의존성이 추가되게 된다.
public class TicketSeller {
private TicketOffice ticketOffice;
public TicketSeller(TicketOffice ticketOffice) {
this.ticketOffice = ticketOffice;
}
public void sellTo(Audience audience) {
ticketOffice.sellTicketTo(audience);
}
}
트레이드오프 시점이 왔다. 토론 끝에 개발 팀은 TicketOffice의 자율성보다는 Audience에 대한 결합도를 낮추는 게 중요하다는 결론에 도달했다. (코드 확인)
1) 어떤 기능을 설계하는 방법은 한 가지 이상일 수 있다.
2) 설계는 트레이드오프의 산물이다. 어떤 경우에도 모든 사람을 만족시킬 수 있는 설계를 만들 수는 없다.
그래, 거짓말이다!
앞에서는 실세계의 생물처럼 스스로 생각하고 행동하도록 객체를 설계하는 것이 이해하기 쉬운 코드를 작성하는 것이라고 설명했다. 하지만 이제 말을 바꿔야겠다. 훌륭한 객체지향 설계란, 소프트웨어를 구성하는 모든 객체들이 자율적으로 행동하는 설계를 가리킨다.
이해하기 쉽고 변경하기 쉬운 코드를 작성하고 싶다면 한 편의 애니메이션을 만든다고 생각하라. 그렇게 하면 코드 안에서 웃고, 떠들고, 화내는 가방 객체를 만나도 당황하지 않을 것이다.
04 객체지향 설계
설계가 왜 필요한가
설계란 코드를 배치하는 것이다[Metz12].
좋은 설계란 무엇인가?
오늘 완성해야하는 기능을 구현함과 동시에 내일 쉽게 변경할 수 있는 코드를 짜야 한다. 좋은 설계란 오늘 요구하는 기능을 온전히 수행하면서 내일의 변경을 매끄럽게 수용할 수 있는 설계다. => 변경을 수용할 수 있는 설계가 중요한 이유는 요구사항이 항상 변경되기 때문이다. 그리고, 요구사항 변경은 코드 수정을 초래하고 코드 수정은 버그가 발생할 가능성을 높인다.
객체지향 설계
따라서 우리가 진정으로 원하는 것은 변경에 유연하게 대응할 수 있는 코드다. 객체지향 프로그래밍은 의존성을 효율적으로 통제할 수 있는 방법을 제공함으로써 요구사항 변경에 좀 더 수월하게 대응할 수 있는 가능성을 높여준다. 앞의 예제를 통해 코드 변경 측면에서는 객체지향이 안정감을 준다는 사실에 공감했으면 좋겠다.
변경 가능한 코드란 이해하기 쉬운 코드다. 객체지향은 여러분이 세상을 바라보는 방식대로 코드를 작성할 수 있게 돕는다.
훌륭한 객체지향 설계란 협력하는 객체 사이의 의존성을 적절하게 관리하는 설계다. 데이터와 프로세스를 하나의 덩어리로 모으는 것은 훌륭한 객체지향 설계로 가는 첫걸음일 뿐이다. 협력하는 객체들 사이의 의존성을 적절히 조절함으로써 변경에 용이한 설계를 만드는 것이 진정한 객체지향 설계로 나아가는 길이다.
마무리하며 - 챕터 1 마무리
챕터 1에서는 절차지향 -> 객체지향으로 변경하면서 객체지향의 장점을 확실히 알아볼 수 있었다.
책에 있는 코드를 따라치는 것에 대한 필요성을 크게 못 느꼈었는데, 따라치면서 코드를 확인하니 이해도 더 잘 되고 긍정적인 부분이 있는 것 같다.
마지막에 '객체지향이 안정감을 준다'는 부분에 무척 공감이 되었고, 앞으로의 내용들이 더욱 기대가 된다.
전체 코드 확인👇
Prography-10th-object-study/src/jongeuni/chapther01 at jongeuni · prography/Prography-10th-object-study
10기 <오브젝트>를 학습하는 스터디입니다. Contribute to prography/Prography-10th-object-study development by creating an account on GitHub.
github.com
'Book' 카테고리의 다른 글
[오브젝트] 챕터 03: 역할, 책임, 협력 (0) | 2025.05.02 |
---|---|
[오브젝트] 챕터 02: 객체지향 프로그래밍 (0) | 2025.04.22 |
오브젝트, 코드로 이해하는 객체지향 설계: 들어가며 (0) | 2025.04.09 |
[객체지향의 사실과 오해: 마무리] 07 함께 모으기 / 부록 A (1) | 2024.11.20 |
[객체지향의 사실과 오해] 06 객체 지도 (3) | 2024.10.30 |