패러다임의 시대
패러다임은 모델, 패턴, 전형적인 예를 의미하는 그리스어인 파라데이그마에서 유래
현재는 사회 전체가 공유하는 이론, 방법, 문제의식 등의 체계를 의미
각 패러다임과 패러다임을 채용하는 언어는 특정한 종류의 문제를 해결하는 데 필요한 일련의 개념들을 지원하기 때문에 프로그래밍 언어와 프로그래밍 패러다임은 분리해서 설명할 수 없음
C++은 절차형 패러다임과 객체지향 패러다임을 접목시켜 서로의 장단점을 보완
프로그래밍 패러다임은 혁명적이 아니라 발전적이므로 객체지향 패러다임을 주로 사용한다고 하더라도 다른 패러다임을 배우는 것이 도움이 될 수 있음
객체, 설계
티켓 판매 어플리케이션 다이어그램
초대장이 있을 경우 티켓으로 교환하고, 없을 경우 돈으로 티켓을 구매해야 함
void Theater::enter(Audience* audience)
{
Ticket ticket = ticketSeller.getTicketOffice().getTicket();
if (!audience->getBag().hasInvitation())
{
const int ticketFee = ticket.getFee();
audience->getBag().minusAmount(ticketFee);
ticketSeller.getTicketOffice().plusAmount(ticketFee);
}
audience->getBag().setTicket(ticket);
}
소프트웨어 모듈이 가져야 하는 세 가지 기능
모듈 : 크기와 상관 없이 클래스, 패키지, 라이브러리와 같이 프로그램을 구성하는 임의의 요소
모든 모듈은 제대로 실행돼야 하고, 변경이 용이해야 하며, 이해하기 쉬워야 함
변경에 취약한 코드
Audience와 TicketSeller를 변경할 경우 Theater도 함께 변경해야 함
만약 관람객이 가방을 들고 있지 않거나 현금이 아닌 신용카드를 제시하거나, 판매원에 매표소 밖에서 티켓을 판매하는 상황이 발생한다면 해당 클래스 뿐만 아니라 Theater::enter 메서드까지 수정해야 함
이 문제는 객체 사이의 결합도(의존성)과 관련됐고, 결합도는 변경과 관련됨
결합도에는 어떤 객체가 변경될 때 그 객체에 의존하는 다른 객체도 함께 변경될 수 있다는 사실이 내포됨
따라서 기능을 구현하는 데 최소한의 결합도만 유지하고 불필요한 결합을 제거해야 함
설계 개선하기
관람객과 판매원을 자율적인 존재로 만들어 최소한의 결합도만 유지해야 함
Theater에서 TicketOffice로의 의존성을 제거
Theater::enter에서 TicketOffice에 접근하는 코드를 TicketSeller 내부에서 구현하도록 변경
따라서 외부에서 ticketOffice에 접근하지 못하도록 관련된 public 메서드를 제거
이와 같이 객체 내부의 세부적인 사항을 감추는 것을 캡슐화라고 부름
캡슐화를 통해 객체 내부의 접근을 제한하여 객체 사이의 결합도를 낮추기 때문에 변경하기 쉬운 객체를 만듦
Theater는 TicketSeller의 인터페이스에만 의존함
TicketSeller가 내부에 TicketOffice 인스턴스를 포함하고 있는 사실은 구현 영역에 속함
객체를 인터페이스와 구현으로 나누고 인터페이스만 공개하는 것은 객체 사이의 결합도를 낮추는 가장 기본적인 설계 원칙
void Theater::enter(Audience* audience)
{
ticketSeller.sellTo(audience);
}
void TicketSeller::sellTo(Audience* audience)
{
Ticket ticket = ticketSeller.getTicketOffice().getTicket();
if (!audience->getBag().hasInvitation())
{
const int ticketFee = ticket.getFee();
audience->getBag().minusAmount(ticketFee);
ticketSeller.getTicketOffice().plusAmount(ticketFee);
}
audience->getBag().setTicket(ticket);
}
TicketSeller에서 Bag의 의존성을 제거
Bag에 접근하는 모든 로직을 Audience 내부로 캡슐화
TicketSeller의 입장에서는 Audience에 대한 의존성이 추가됐지만 적절한 트레이드 오프
void TicketSeller::sellTo(Audience* audience)
{
ticketOffice.plusAmount(audience->buy(ticketOffice.getTicket()));
}
float Audience::buy(Ticket ticket)
{
float price = 0.0f;
if (!bag.hasInvitation())
{
price = ticket.getFee();
bag.minusAmount(price);
}
bag.setTicket(ticket);
return price;
}
Bag의 자율성을 높임
Bag에 접근하는 모든 로직을 Bag 내부로 캡슐화
float Audience::buy(Ticket ticket)
{
return bag.exchangeTicket(ticket);
}
float Bag::exchangeTicket(Ticket ticket)
{
float price = 0.0f;
if (!hasInvitation())
{
price = ticket.getFee();
minusAmount(price);
}
setTicket(ticket);
return price;
}
** 저의 생각은 TicketOffice가 객체인지 의문이 듭니다.
TicketOffice는 수동적으로 데이터가 변경되고 데이터(티켓, 금액)만 저장되기 때문에 객체보다는 struct(데이터 집합)이 맞지 않나 생각합니다
절차지향과 객체지향
수정하기 전의 코드에서는 Audience, TicketSeller, Bag, TicketOffice는 관람객을 입장시키는 데 필요한 정보를 제공하고 모든 처리는 Theater::enter 메서드에서 처리
이러한 관점에서 Theater::enter 메서드는 프로세스이며, Audience, TicketSeller, Bag, TicketOffice는 데이터
이처럼 프로세스와 데이터를 별도의 모듈에 위치시키는 방식을 절차지향 프로그래밍이라고 부름
일반적으로 절차지향 프로그래밍은 우리의 직관에 위배되어 이해하기 어렵고, 캡슐화가 되지 않았기 때문에 결합도가 높아 변경하기 어려운 코드를 만들어냄
수정된 코드에서는 자신의 데이터를 스스로 처리하도록 프로세스의 적절한 단계를 Audience와 TicketSeller로 이동시킴
이처럼 데이터와 프로세스가 동일한 모듈 내부에 위치하도록 프로그래밍하는 방식을 객체지향 프로그래밍이라고 부름
객체지향 설계의 핵심은 캡슐화를 통해 객체 사이의 결합도를 낮춰 변경에 유연하다는 것
책임의 이동
변경 전 절차지향 설계에서는 Theater가 전체적인 책임을 맡았지만 변경 후 객체지향 설계에서는 각 객체가 각자의 책임을 스스로 처리
객체지향은 각 객체에 책임이 적절하게 분배되어 스스로 책임을 수행함으로써 완성됨
데이터와 프로세스가 별도의 객체에 위치한다면 절차지향일 확률이 높고 데이터와 프로세스가 동일한 객체 안에 위치한다면 객체지향일 확률이 높음
물론 데이터와 프로세스를 하나의 객체에 모으는 것도 중요하지만 객체지향 설계의 핵심은 객체에 적절한 책임을 할당하는 것
따라서 객체에 어떤 책임을 할당할 것이냐에 초점을 맞춰야 함
의인화
능동적이고 자율적인 존재로 소프트웨어 객체를 설계하는 원칙
훌륭한 객체지향 설계는 책임을 할당받아 자율적으로 책임을 수행하는 설계
트레이드 오프
TicketOffice를 자율적인 객체로 변경할 경우 Audience에 대한 의존성이 추가됨
위와 같이 결합도와 자율성 모두를 만족시킬 방법이 없을 때 어떤 것을 우선해야 할지 결정해야 함
어떤 기능을 설계하는 방법은 한 가지 이상일 수 있음
동일한 기능을 한 가지 이상의 방법으로 설계할 수 있기 때문에 설계는 트레이드 오프의 산물
훌륭한 설계는 적절한 트레이드 오프의 결과물이라는 사실을 명심하라
객체지향 설계
설계란 코드를 배치하는 것
설계는 코드를 작성하는 매 순간 코드를 어떻게 배치할 것인지를 결정하는 과정에서 나오기 때문에 설계는 코드 작성의 일부이며 코드를 통해 검증할 수 있음
좋은 설계는 오늘 완성해야 하는 기능을 구현하는 코드를 짜는 동시에 내일 쉽게 변경할 수 있는 코드를 짜는 것
변경을 수용할 수 있는 설계가 중요한 이유는 요구사항이 자주 변경되기 때문이고, 변경할 때 버그가 추가될 가능성이 높기 때문
훌륭한 객체지향 설계란 협력하는 객체 사이의 의존성을 적절하게 관리하는 설계
객체 간의 의존성은 수정하기 어렵게 만드는 원인
'객체 지향 > 오브젝트' 카테고리의 다른 글
[오브젝트] 6장 메시지와 인터페이스 (1) | 2025.03.18 |
---|---|
[오브젝트] 5장 책임 할당하기 (0) | 2025.03.08 |
[오브젝트] 4장 설계 품질과 트레이드 오프 (0) | 2025.02.19 |
[오브젝트] 3장 역할, 책임, 협력 (0) | 2025.02.13 |
[오브젝트] 2장 객체지향 프로그래밍 (0) | 2025.02.06 |