01. 객체, 설계
태그
비어 있음
프로그래밍 패러다임이란 특정 시대의 어느 성숙한 개발자 공동체에 의해 수용된 프로그래밍 방법과 문제 해결 방법, 프로그래밍 스타일이다.
우리가 어떤 프로그래밍 패러다임을 사용하느냐에 따라 우리가 해결할 문제를 바라보는 방식과 프로그램을 작성하는 방법이 달라진다.
프로그래밍 패러다임은 개발자 공동체가 동일한 프로그래밍 스타일과 모델을 공유함으로써 불필요한 의견 충돌을 방지하고, 동일한 규칙과 방법을 공유하는 개발자로 성장할 수 있도록 준비시킬 수 있다.
이 책의 목적은 객체지향 패러다임이 제시하는 프로그래밍 패러다임을 설명하는 것
객체지향 프로그래밍을 하는 개발자들이 동일한 규칙과 표준에 따라 프로그램을 작성할 수 있게 함.
✅ 티켓 판매 어플리케이션 구현하기
소극장의 티켓판매 어플리케이션을 구현해보자
구현한 어플리케이션의 클래스 다이어그램
Theater 클래스
소극장은 관람객의 가방 안에 초대장이 들어 있는지 확인한다.
만약 초대장이 들어 있다면 판매원에게 받은 티켓을 관람객의 가방 안에 넣어준다.
초대장이 들어 있지 않다면 소극장은 관람객의 가방에서 티켓 금액 만큼을 차감한 후 매표소에 금액을 증가시킨다.
소극장은 관람객의 가방 안에 티켓을 넣어줌으로써 관람객의 입장 절차를 끝낸다.
C++
복사
class Theater {
private ticketSeller: TicketSeller;
constructor(ticketSeller: TicketSeller) {
this.ticketSeller = ticketSeller;
}
public enter(audience: Audience): void {
if (audience.getBag().hasInvitation()) {
const ticket: Ticket = this.ticketSeller.getTicketOffice().getTicket();
audience.getBag().setTicket(ticket);
} else {
const ticket: Ticket = this.ticketSeller.getTicketOffice().getTicket();
audience.getBag().minusAmount(ticket.getFee());
this.ticketSeller.getTicketOffice().plusAmount(ticket.getFee());
audience.getBag().setTicket(ticket);
}
}
}
✅ 무엇이 문제인가
소프트웨어 모듈이 가져야 하는 세가지 기능 - 로버트 마틴
실행 중에 제대로 동작하는 것
목적은 변경을 위해 존재하는 것
코드를 읽는 사람과 의사소통 하는 것
⇒ 작성된 프로그램은 2,3을 만족시키지 못한다. 그 이유를 살펴보자.
예상을 빗나가는 코드
소극장 클래스의 enter함수를 보면
소극장이 관람객의 가방을 마음대로 연다.
소극장이 판매원의 허락도 없이 매표소의 티켓과 현금에 마음대로 접근한다.
받은 돈을 매표소에 적립하는 일을 소극장이 수행한다.
이해 가능한 코드란 그 동작이 우리의 예상에서 크게 벗어나지 않는 코드다.
현재 코드는 우리의 상식과 다르게 동작하기 때문에 독자와 제대로 소통하지 못한다.
또한 하나의 클래스(Theater)나 메서드(enter)가 너무 많은 세부사항을 다루기 때문에, 코드를 이해하기 위해서는 모든 사실을 기억하고 있어야 한다.
변경에 취약한 코드
가장 심각한 문제는 변경에 취약한 것이다.
<다이어그램>
아까 본 다이어그램을 보면 Theater이 너무 많은 클래스에 의존한다.
Theater는 관람객이 가방을 들고 있고 판매원이 매표소에서만 티켓을 판매한다는 지나치게 세부적인 사실에 의존해서 동작한다.
→ 불필요한 의존성이 있음.
객체 사이의 의존성을 완전히 없애는 것이 정답은 아니다.
불필요한 의존성을 제거해보자
설계의 목표 : 불필요한 의존성을 제거해 결합도를 낮추어 변경에 용이하게 만들자
✅ 설계 개선하기
자율성을 높이자
설계를 변경하기 어려운 이유는 Theater가 Audience, TicketSeller, Bag, TicketOffice까지 마음대로 접근할 수 있기 때문이다.
각 객체가 자기 자신의 문제를 스스로 해결하도록 코드를 수정해보자.
TicketSeller 캡슐화 : Theater 코드를 TicketSeller로 이동
Theater의 enter 메서드의 TicketOffice에 접근하는 모든 코드를 TicketSeller 의 sellTo메서드로 옮긴다.
⇒ Theater는 ticketOffice가 TicketSeller 내부에 존재한다는 사실을 알지 못한다.
⇒ TicketSeller의 내부구현이 성공적으로 캡슐화 되었다.
Java
복사
class TicketSeller {
private ticketOffice: TicketOffice;
constructor(ticketOffice: TicketOffice) {
this.ticketOffice = ticketOffice;
}
public sellTo(audience: Audience): void {
if (audience.getBag().hasInvitation()) {
const ticket: Ticket = this.ticketOffice.getTicket();
audience.getBag().setTicket(ticket);
} else {
const ticket: Ticket = this.ticketOffice.getTicket();
audience.getBag().minusAmount(ticket.getFee());
this.ticketOffice.plusAmount(ticket.getFee());
audience.getBag().setTicket(ticket);
}
}
}
class Theater {
private ticketSeller: TicketSeller;
constructor(ticketSeller: TicketSeller) {
this.ticketSeller = ticketSeller;
}
public enter(audience: Audience): void {
this.ticketSeller.sellTo(audience);
}
}
Audience 캡슐화 : TicketSeller의 코드를 Audience로 이동
Bag에 접근하는 모든 로직을 Audience내부로 감추기 위해 sellTo 메서드에서 buy메서드로 옮긴다.
⇒ 외부에서는 더이상 Audience가 Bag을 소유하고 있다는 사실을 알 필요가 없다.
⇒ Bag의 존재를 내부로 캡슐화 함.
Java
복사
class Audience {
private bag: Bag;
constructor(bag: Bag) {
this.bag = bag;
}
public buy(ticket: Ticket): number {
if (this.bag.hasInvitation()) {
this.bag.setTicket(ticket);
return 0;
} else {
this.bag.setTicket(ticket);
this.bag.minusAmount(ticket.getFee());
return ticket.getFee();
}
}
}
class TicketSeller {
private ticketOffice: TicketOffice;
constructor(ticketOffice: TicketOffice) {
this.ticketOffice = ticketOffice;
}
public sellTo(audience: Audience): void {
this.ticketOffice.plusAmount(audience.buy(this.ticketOffice.getTicket()));
}
}
개선된 코드의 다이어그램
Audience와 TicketSeller가 내부 구현을 외부에 노출하지 않고 자신의 문제를 스스로 책임지고 해결하는 자율적인 존재가 되었다.
무엇이 개선됐는가?
의사소통 관점에서 개선
변경 용이성 측면에서 개선
어떻게 한 것인가?
자기 자신의 문제를 스스로 해결하도록 코드를 변경한 것
객체의 자율성을 높이는 방향으로 설계를 개선함으로써 이해하기 쉽고 유연한 설계를 얻었다.
캡슐화와 응집도
밀접하게 연관된 작업만을 수행하고 연관성 없는 작업은 다른 객체에게 위임하는 객체를 가리켜 응집도가 높다고 할 수 있다.
응집도를 높이기 위해서는 객체 스스로 자신의 데이터를 책임지는 자율적인 존재여야한다.
절차지향과 객체지향
절차지향 : 프로세스와 데이터를 별도의 모듈에 위치시키는 방식
객체지향 : 데이터와 프로세스가 동일한 모듈 내부에 위치하도록 프로그래밍 하는 방식
책임의 이동
Theater로 집중되어있는 책임을 각 객체에 적절하게 분산함.
⇒ 각 객체가 맡은 일을 스스로 처리함.
⇒ 책임의 이동
객체 지향 설계의 핵심은 적절한 객체에 적절한 책임을 할당하는 것
캡슐화는 객체 간 결합도를 낮추고, 객체의 자율성을 높이고, 응집도 높은 객체들의 공동체를 창조할 수 있게 한다.
최소한의 의존성만을 남기는 것이 훌륭한 객체지향 설계다.
더 개선할 수 있다.
Bag을 자율적인 존재로 바꾸어 관련된 로직을 Bag안에 캡슐화 하여 결합도를 낮출 수 있다.
하지만 TicketOffice와 Audience 사이에 의존성이 추가되어 결합도를 높였다.
트레이드오프의 시점
TicketOffice의 자율성 vs 결합도가 높은 변경에 어려운 설계
이 예제를 통해 알 수 있는 사실
어떤 기능을 설계하는 방법은 한 가지 이상일 수 있다.
그러므로 결국 설계는 트레이드오프의 산물이다.
의인화 : 무생물을 자율적이고 능동적인 존재로 소프트웨어 객체를 설계하는 원칙
ex) Bag을 자율적인 존재로 설계
✅ 객체지향 설계
저자가 가장 좋아하는 설계의 정의
설계란 코드를 배치하는 것이다.
설계와 구현을 떨어트려서 이야기하는 것은 불가능하다. 코드 작성의 매순간 코드를 어떻게 배치할 것인지를 결정하는 과정에서 설계는 나온다.
객체지향 설계
데이터와 프로세스를 하나의 덩어리로 모으는 것은 훌륭한 객체지향 설계로 가는 첫걸음일 뿐이다.
훌륭한 객체지향 설계란 협력하는 객체 사이의 의존성을 적절하게 관리하는 설계다.
표
'책 > 오브젝트' 카테고리의 다른 글
06. 메시지와 인터페이스 (0) | 2025.03.17 |
---|---|
05. 책임 할당하기 (0) | 2025.03.17 |
04. 설계 품질과 트레이드 오프 (0) | 2025.03.17 |
03. 역할, 책임, 협력 (0) | 2025.03.17 |
02. 객체지향 프로그래밍 (0) | 2025.03.17 |