1장
객체지향의 핵심은 '클래스'나 '상속'이 아닌 '자율적인 객체들 간의 협력'이다.
객체지향의 목표는 실세계를 소프트웨어로 끌고와서 모방하는 게 아니라, 고객이 원하는 요구사항을 새로운 세계로 만드는 것이다. 따라서 실세계의 모방이라는 개념은 설계/구현할 때는 부적합하다. 그래도 객체지향이라는 세계를 이해하고 사상을 학습하는 데는 효과적이므로, 이 관점으로 1장을 설명해보겠다.

손님, 캐시어, 바리스타 = 역할
주문, 주문 접수, 제조 = 책임
특정 역할은 특정한 책임을 암시한다.
역할 > 책임 (포함관계)
각 역할을 가진 객체들은 서로 협력해서 문제를 해결한다.
협력 : 연쇄적인 요청과 연쇄적인 응답이 발생하고, 각 역할을 맡은 객체들이 요청을 성실히 이행하는 것
- 요청한 역할만 정확히 수행해준다면, 어떤 객체가 책임을 수행했든 상관 없음 -> 역할은 대체 가능하며, 여러 명일 수 있다.
- 요청 받은 객체는 다형성을 통해 동일 요청에 대해 서로 다른 방식으로 응답할 수 있다.
- 한 객체가 여러 역할을 맡기도 한다.
시스템(애플리케이션) 기능은 여러 책임들로 이뤄지므로, 좋은 객체지향설계를 하려면
1) 적절한 객체에게
2) 적절한 책임을 할당해야 한다.
-> 유연하고 재사용가능하도록! (다형성과 관련있음)
'역할'의 특징들
- 여러 객체가 동일한 역할을 수행할 수 있다.
- 역할은 대체 가능성을 의미한다.
- 각 객체는 책임을 수행하는 방법을 자율적으로 선택할 수 있다.
- 하나의 객체가 동시에 여러 역할을 수행할 수 있다.
애플리케이션 기능 구현을 위해서는 객체 간의 협력이 필수 ('객체'지향인 이유가 여기에 있음)
결국 객체 품질이 협력 품질을 결정한다.
'객체'가 갖춰야 하는 두 가지 덕목
1) 충분히 협력적
다른 객체의 명령에 무조건 복종하는 수동적 존재 X -> 내부적인 복잡도가 너무 심해짐
어떻게 응답할지 스스로 판단하고 결정해야 함. 그저 요청에 '응답'해야 함
2) 충분히 자율적
객체 본인의 행동을 스스로 결정하고 책임짐
객체란 상태(data)와 행동(function)을 함께 가진 존재 -> 어떤 일을 하기 위해서는 상태를 알고 있어야 함
객체는 자율적인 존재 -> 객체는 자기 일을 스스로 판단하고 처리해야 함. 다른 객체가 '어떻게 하라'고 내부 로직에 간섭하면 안 됨. 다른 객체가 what을 하는지는 알 수 있지만 how는 몰라야 함
객체를 '상태+행동'으로 묶어서 '자율적인 존재'로 만들어야 유지보수가 용이하고 재사용성이 올라가며, 확장이 쉬워진다.
객체지향 세계에서 객체는 협력을 위해 메시지를 전송/수신한다.
메시지 : 객체 간의 의사소통 수단
객체는 수신된 메시지를 처리하기 위해 메서드를 사용한다.
메서드 : 함수 또는 프로시저를 통해 구현됨
절차지향 언어 -> 프로시저 호출 시 실행할 코드를 컴파일 시간에 결정
객체지향 언어 -> 프로시저 호출 시 실행 시간에 메서드를 선택할 수 있음
2장
객체지향 패러다임의 목적은 현실 세계를 모방하는 것이 아니라 현실 세계를 기반으로 새로운 세계를 창조하는 것이다.
상태는 행동의 결과이다.
행동의 결과로 나타난 상태는 이전 상태에 의존적이다.
객체가 될 수 있는 것?
하나의 개별적인 실체로 식별 가능한 물리적인 또는 개념적인 사물들이라면 모두 객체가 될 수 있다.
'상태'라는 개념이 왜 필요한가?
현재시점의 객체와 행동의 결과는 과거에 어떤 행동들이 객체에게 일어났는가에 따라 달라지는데, 과거 행동들을 다 기억하려면 복잡성이 늘어나기 때문에 상태라는 개념이 발생. -> 상태를 쓰면 과거 이력에 얽매이지 않고 현재를 깁반으로 객체의 행동결과를 예측 가능
'상태'가 될 수 있는 것?
1) 단순한 값 -> 속성(attribute)이라고 함
2) 객체 그 자체 -> 링크(link)로 연결되어있다.
링크 : 객체가 다른 객체를 참조할 수 있다는 의미. 일반적으로 한 객체가 다른 객체의 식별자를 알고있는 상태를 의미함. 객체 사이에 링크가 있어야 서로 메시지를 주고 받으며 협력할 수 있다.
즉 상태 = 값(attribute) + 객체(link)
그리고 이를 객체의 '프로퍼티'라고 함.
프로퍼티는 변하지 않음 (정적) 그러나 프로퍼티의 값 자체는 변함 (동적)
객체는 자율적인 존재이다! 즉 스스로의 행동에 의해서만 상태가 변경되어야 한다.
그래서 [상태+상태를 조작하는 행동]을 하나로 묶는 것이 객체지향의 기본 사상이다.
다른 객체에 의해 간접적으로 특정 객체의 상태를 변경/조회하려면 '행동'을 사용하면 된다.
행동에 의해 상태가 변경되는데, 이를 행동의 부수효과(side effect)라고 함
그리고 그 변경된 결과는 이전 상태에 의존적이다.
객체는 협력과정에서 자신 뿐만아니라 다른 객체의 상태까지 변화시킬 수 있다.
객체 간에 협력하는 유일한 방법은 다른 객체에게 요청을 보내는 것이기 때문이다.
다른 객체에 의해 상태가 변경되더라도, 요청을 전달할 뿐이지 객체 상태를 변화시키는 것은 본인이 직접 해야 함
캡슐화 : 객체는 상태를 감춰두고 외부에 노출하지 않아야 함. 행동을 노출하여 외부객체가 접근할 수 있게 해야 함
캡슐화의 장점 -> 자율성이 높아지고, 협력이 유연하고 간결해짐. 스스로 판단하고 결정하기 때문에
객체는 '식별자'가 다르면 다르다고 판단되는 성질이 있음 (동등성, equality) -> 시간에 따라 상태가 변함/ 행동이 상태를 변화시킴
값은 '상태'가 다르면 다르다고 판단됨
// 한 파일로 돌릴 수 있는 간단한 예시들 (javac CafeDemo.java && java CafeDemo)
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/* ===========================
* 1) 역할(Role)과 책임(Responsibility), 협력(Collaboration)
* =========================== */
// 역할은 인터페이스로, 책임은 메서드 시그니처로 표현
interface CashierRole {
Receipt takeOrder(Customer customer, Order order); // 주문 접수 책임
}
interface BaristaRole {
Drink makeDrink(Order order); // 제조 책임 (다형성의 대상)
}
interface CustomerRole {
void placeOrder(CashierRole cashier, Order order); // 주문 책임
}
/* ===========================
* 2) 자율적 객체: 상태 + 행동 + 캡슐화
* =========================== */
final class Order {
enum Status { NEW, ACCEPTED, MAKING, DONE }
private final String menu;
private Status status = Status.NEW; // 상태(값)
private BaristaRole assignedBarista; // 링크(다른 객체 참조)
Order(String menu) { this.menu = menu; }
// 상태 변경은 자신의 행동으로만 허용(캡슐화)
void acceptBy(CashierRole cashier) {
ensure(Status.NEW);
this.status = Status.ACCEPTED;
}
void assign(BaristaRole barista) {
ensure(Status.ACCEPTED);
this.assignedBarista = barista;
}
Drink startMaking() {
ensure(Status.ACCEPTED);
this.status = Status.MAKING;
ensureAssigned();
Drink d = assignedBarista.makeDrink(this); // 협력: 메시지 전송
this.status = Status.DONE;
return d;
}
String menu() { return menu; }
Status status() { return status; }
private void ensure(Status expected) {
if (this.status != expected) throw new IllegalStateException("잘못된 상태 전이: " + status + " -> 기대: " + expected);
}
private void ensureAssigned() {
if (assignedBarista == null) throw new IllegalStateException("바리스타 미배정");
}
}
final class Receipt {
private static final AtomicInteger SEQ = new AtomicInteger(1);
private final int no = SEQ.getAndIncrement();
private final String menu;
Receipt(String menu) { this.menu = menu; }
public String toString() { return "영수증#" + no + " (" + menu + ")"; }
}
final class Drink {
private final String name;
private final String note; // 다형적 제조 방식의 결과
Drink(String name, String note) { this.name = name; this.note = note; }
public String toString() { return name + " [" + note + "]"; }
}
/* ===========================
* 3) 역할 대체 가능성 & 다형성
* =========================== */
// 서로 다른 방식으로 동일 책임을 수행하는 두 바리스타
final class FastBarista implements BaristaRole {
public Drink makeDrink(Order order) {
// 행동의 부수효과(사이드 이펙트): 인벤토리 차감 등은 내부에서 결정
return new Drink(order.menu(), "초고속 추출");
}
}
final class CarefulBarista implements BaristaRole {
public Drink makeDrink(Order order) {
return new Drink(order.menu(), "정밀 계량·온도 제어");
}
}
/* ===========================
* 4) 한 객체가 여러 역할 수행 (동시 다역)
* =========================== */
final class Manager implements CashierRole, BaristaRole {
private final Inventory inventory = new Inventory();
// Cashier 역할
public Receipt takeOrder(Customer customer, Order order) {
order.acceptBy(this);
// 상황에 따라 자신(매니저)이 바리스타 역할도 수행하거나, 다른 바리스타에 위임 가능
order.assign(this); // 여기서는 자신을 배정
System.out.println("[Manager/Cashier] 주문 접수: " + order.menu());
return new Receipt(order.menu());
}
// Barista 역할
public Drink makeDrink(Order order) {
inventory.consume(order.menu()); // 내부 상태 변화(캡슐화)
return new Drink(order.menu(), "매니저 스페셜 블렌드");
}
}
/* ===========================
* 5) 구체 Cashier: 바리스타 교체가 쉬움(유연성)
* =========================== */
final class Cashier implements CashierRole {
private BaristaRole barista; // 링크: 협력 대상 (역할에만 의존)
Cashier(BaristaRole barista) { this.barista = barista; }
void changeBarista(BaristaRole newOne) { this.barista = newOne; } // 역할 대체
public Receipt takeOrder(Customer customer, Order order) {
order.acceptBy(this);
order.assign(barista);
System.out.println("[Cashier] 주문 접수: " + order.menu());
return new Receipt(order.menu());
}
}
/* ===========================
* 6) 고객: 자율적 판단으로 메시지 전송(what만 요청)
* =========================== */
final class Customer implements CustomerRole {
private final String id; // 식별자(엔티티의 동일성)
Customer(String id) { this.id = id; }
public void placeOrder(CashierRole cashier, Order order) {
Receipt receipt = cashier.takeOrder(this, order);
System.out.println("[Customer " + id + "] " + receipt + " 수령");
Drink drink = order.startMaking();
System.out.println("[Customer " + id + "] 음료 픽업: " + drink);
}
}
/* ===========================
* 7) 값(Value)과 엔티티(Entity) 예시
* =========================== */
final class Money {
private final long amount; // 원화 가정
Money(long amount) { this.amount = amount; }
public long amount() { return amount; }
// 값 객체: 상태가 같으면 같음
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money)) return false;
return amount == ((Money) o).amount;
}
@Override public int hashCode() { return Objects.hash(amount); }
public String toString() { return amount + "원"; }
}
final class Ticket { // 엔티티: 식별자가 같으면 같음
private final UUID id = UUID.randomUUID();
private Money price;
Ticket(Money price) { this.price = price; }
public UUID id() { return id; }
public Money price() { return price; }
public void changePrice(Money newPrice) { this.price = newPrice; } // 상태 변경(부수효과)
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Ticket)) return false;
return id.equals(((Ticket) o).id);
}
@Override public int hashCode() { return id.hashCode(); }
}
/* ===========================
* 8) 내부 상태(Inventory) 캡슐화 예시
* =========================== */
final class Inventory {
private final Map<String, Integer> stock = new HashMap<>();
Inventory() {
stock.put("아메리카노", 10);
stock.put("라떼", 8);
}
void consume(String menu) {
int remain = stock.getOrDefault(menu, 0);
if (remain <= 0) throw new IllegalStateException("재고 없음: " + menu);
stock.put(menu, remain - 1);
}
@Override public String toString() { return stock.toString(); }
}
/* ===========================
* 9) 데모
* =========================== */
public class CafeDemo {
public static void main(String[] args) {
// 바리스타 두 명(다형성)
BaristaRole fast = new FastBarista();
BaristaRole careful = new CarefulBarista();
// 캐시어는 역할에만 의존 → 바리스타 교체 쉬움
Cashier cashier = new Cashier(fast);
// 손님은 캐시어에게 "무엇을"만 요청 (어떻게 만드는지는 몰라도 됨)
Customer alice = new Customer("Alice");
alice.placeOrder(cashier, new Order("아메리카노"));
// 역할 교체(대체 가능성)
cashier.changeBarista(careful);
Customer bob = new Customer("Bob");
bob.placeOrder(cashier, new Order("라떼"));
// 한 객체가 여러 역할 수행(Manager)
Manager manager = new Manager();
Customer chris = new Customer("Chris");
chris.placeOrder(manager, new Order("라떼"));
// 값 vs 엔티티
Money m1 = new Money(3000);
Money m2 = new Money(3000);
System.out.println("[값 동등성] m1==m2 ? " + m1.equals(m2)); // true
Ticket t1 = new Ticket(new Money(4500));
Ticket t2 = new Ticket(new Money(4500));
System.out.println("[엔티티 동일성] t1==t2 ? " + t1.equals(t2)); // false
// 상태 변화(부수효과): 티켓 가격 변경
t1.changePrice(new Money(5000));
System.out.println("t1 price: " + t1.price() + ", t2 price: " + t2.price());
}
}
'스터디' 카테고리의 다른 글
| [객체지향의 사실과 오해] 7장 정리 (0) | 2025.08.31 |
|---|---|
| [객체지향의 사실과 오해] 5장, 6장 정리 (2) | 2025.08.23 |
| [객체지향의 사실과 오해] 3장, 4장 정리 (3) | 2025.08.16 |