협력
객체지향 시스템은 자율적인 객체들의 공동체
메시지 전송은 객체 사이의 협력을 위해 사용할 수 있는 유일한 수단
메시지를 수신한 객체는 메서드를 실행해 요청에 응답
외부 객체는 오직 메시지만 전송할 뿐, 메시지가 어떻게 처리될지는 수신한 객체가 직접 결정
>> 객체는 자율적인 존재
객체들이 어플리케이션 기능 구현을 위해 수행하는 상호작용을 협력 이라고 함
객체를 자율적으로 만드는 가장 기본적인 방법 >> 내부 구현을 캡슐화 하는 것
캡슐화를 통해 변경에 대한 파급효과를 제한
상태는 객체가 행동하는데 필요한 정보에 의해 결정
행동은 협력 안에서 객체가 처리할 메시지로 결정
협력은 객체를 설계하는데 필요한 일종의 문맥(Context)을 제공
쉽게 말해
하나의 객체가 혼자 기능을 수행하지 않고 다른 객체에 메시지를 보내 책임을 분산하는 방법
public class Order {
private String orderId;
private double amount;
public Order(String orderId, double amount) {
this.orderId = orderId;
this.amount = amount;
}
public boolean process(PaymentProcessor processor) {
return processor.pay(orderId, amount);
}
}
public class PaymentProcessor {
public boolean pay(String orderId, double amount) {
System.out.println("결제 요청: 주문번호=" + orderId + ", 금액=" + amount);
return true;
}
}
Order 는 스스로 결제를 진행하지 않고, PaymentProcessor 에게 메시지를 보내 결제 기능 위임
자신의 책임 일부를 다른 객체에게 위임하는 것이 협력
책임
협력에 참여하기 위해 객체가 수행하는 행동
하는 것 (Doing)
객체를 생성하거나 계산을 수행하는 등의 스스로 하는 것
public class Calculator {
// 계산을 스스로 수행하는 책임
public int add(int a, int b) {
return a + b;
}
}
다른 객체의 행동을 시작시키는 것
public class Order {
private PaymentProcessor paymentProcessor;
public Order(PaymentProcessor paymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
public void checkout(double amount) {
// 다른 객체의 행동을 유도 (협력)
paymentProcessor.processPayment(amount);
}
}
public class PaymentProcessor {
public void processPayment(double amount) {
System.out.println("결제 처리 중: " + amount);
}
}
다른 객체의 활동을 제어하고 조절하는 것
public class TaskManager {
private Worker worker;
public TaskManager(Worker worker) {
this.worker = worker;
}
public void execute() {
if (!worker.isBusy()) {
worker.doWork();
} else {
System.out.println("작업자가 바쁩니다. 대기 중...");
}
}
}
public class Worker {
private boolean busy;
public boolean isBusy() {
return busy;
}
public void doWork() {
busy = true;
System.out.println("작업 시작");
// 작업 수행
busy = false;
}
}
아는 것 (Knowing)
사적인 정보에 관해 아는 것
public class User {
private String name;
private String email;
// 사적인 정보(name, email)를 알고 있음
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
관련된 객체에 관해 아는 것
public class Order {
private User customer; // 관련된 객체(User)에 대해 알고 있음
public Order(User customer) {
this.customer = customer;
}
public String getCustomerEmail() {
return customer.getEmail(); // 관련 객체의 상태를 조회
}
}
자신이 유도하거나 계산할 수 있는 것에 관해 아는 것
public class Rectangle {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
// 자신의 상태를 기반으로 유도 가능한 정보를 제공
public int getArea() {
return width * height;
}
}
** 책임은 객체지향 설계의 핵심
협력이 중요한 이유도 객체에게 할당할 책임을 결정할 수 있는 문맥을 제공하기 때문
Order → PaymentProcessor → PaymentGateway
Order가 결제를 직접 처리하는 게 아니라 PaymentProcessor에 협력 요청
결제 처리는 Order가 아니라 PaymentProcessor의 책임이구나”라고 판단 가능
즉, 협력 구조를 살펴보면 책임 분배 기준을 정할 수 있는 문맥이 되는 것
!!! 객체지향 설계에서 가장 중요한 것이 책임 !!!
책임 할당
자율적인 객체를 만드는 가장 기본적인 방법 >> 책임을 수행하는데 필요한 정보를 가장 잘 알고있는 전문가에게 책임을 할당하는것
이를 책임 할당을 위한 Information Expert(정보 전문가) 패턴 이라고 함
객체에게 책임 할당을 위해서는 협력이라는 문맥을 정의해야 함
public class Screening {
private Movie movie;
private LocalDateTime whenScreened;
private int sequence;
// 협력 문맥: 예약 요청 → Movie에게 책임 위임
public Reservation reserve(Customer customer, int audienceCount) {
Money fee = movie.calculateFee(this, audienceCount); // 협력
return new Reservation(customer, this, fee, audienceCount);
}
// 상태 조회용
public LocalDateTime getStartTime() {
return whenScreened;
}
public int getSequence() {
return sequence;
}
}
책임
- 예매 요청을 처리하는 책임 (reserve)을 가짐
- 하지만 요금 계산 책임은 자신이 아님을 인지하고 Movie에게 위임
협력
- Movie.calculateFee(this, audienceCount) 호출을 통해 책임을 정보 전문가에게 토스
- 자신은 Movie, Customer, audienceCount 정보만 알고, 계산 X
public class Movie {
private String title;
private Duration runningTime;
private Money fee;
private DiscountPolicy discountPolicy;
// 정보 전문가: 요금 계산 책임 보유
public Money calculateFee(Screening screening, int audienceCount) {
Money discount = discountPolicy.calculateDiscountAmount(screening); // 협력
return fee.minus(discount).times(audienceCount);
}
}
책임
- 요금 계산 책임 (calculateFee)은 Movie가 가진 고유 정보에 근거하므로 여기서 수행
- 기본 요금(fee)
- 할인 정책(discountPolicy)
- 이는 Information Expert 원칙의 전형적인 사례
협력
- 할인 계산은 DiscountPolicy 객체에 위임
- 즉, 할인 세부 전략은 또 다른 전문가(discountPolicy)가 책임지고 수행
public class Reservation {
private Customer customer;
private Screening screening;
private Money fee;
private int audienceCount;
public Reservation(Customer customer, Screening screening, Money fee, int audienceCount) {
this.customer = customer;
this.screening = screening;
this.fee = fee;
this.audienceCount = audienceCount;
}
}
책임
- 예매 결과를 보관하는 단순한 책임만 가짐
- 특별한 행위(Doing)는 없고, 상태 정보만 저장 → 대표적인 정보 보관 객체
협력 없음
- 생성된 후 외부에서 참조되는 용도로 사용
객체 | 책임 | 이유 | 협력 |
Movie | 요금 계산 (calculateFee) | 요금, 할인 정책, 금액 계산 정보를 알고 있음 | 할인 정책 객체에 위임 |
Screening | 예매 요청 처리 (reserve) | 예매 요청을 받고, 어떤 영화인지 알고 있음 | Movie에게 요금 계산 요청 |
Reservation | 예매 결과 보관 | 생성된 예매 정보를 보관 | 없음 (단순 DTO 성격) |
이 설계가 좋은 이유
- Doing 책임과 Knowing 책임이 명확히 분리됨
- 정보를 가장 잘 아는 객체가 그 책임을 가짐 (Information Expert)
- 협력(메시지 위임) 을 통해 각 객체는 단일 책임 원칙(SRP) 을 유지함
- 변경에 유연 (ex: 할인 정책만 바꿔도 시스템 전체 수정 없이 확장 가능)
책임 주도 설계
책임을 찾고, 책임을 수행할 적절한 객체를 찾아 책임을 할당하는 방식으로 협력을 설계하는 방법
예매 시스템에서
- 시스템이 사용자에게 제공해야 하는 기능 파악
- 시스템 책임을 더 작은 책임으로 분할
- 분할된 책임을 수행할 수 있는 적절한 객체나 역할을 찾아 책임 할당
- 객체가 책임 수행 도중, 다른 객체 도움이 필요한 경우 이를 책임질 적절한 객체, 역할을 탐색
- 해당 객체, 역할에게 책임을 할당함으로써 두 객체가 협력
메시지가 객체를 결정
메시지가 객체를 선택하게 해야 하는 이유
1. 객체가 최소한의 인터페이스를 가질 수 있게 됨
필요한 메시지 식별 이전까지 객체 퍼블릭 인터페이스에 어떤 것도 추가하지 않기 때문에
객체는 어플리케이션에 필요한 크기의 퍼블릭 인터페이스를 가질 수 있음
// 메시지를 먼저 식별: "총 주문 금액을 알고 싶다"
order.getTotalAmount(); // 메시지
// 이에 따라 Order 클래스는 이 메서드만 퍼블릭 인터페이스로 갖게 됨
public class Order {
public Money getTotalAmount() {
...
}
}
- getTotalAmount()만 퍼블릭 API로 노출되므로 불필요한 내부 정보 노출 방지
- 이렇게 하면 객체의 인터페이스가 작고 깔끔해짐 (정보 은닉 + 유지보수 용이)
2. 객체는 충분히 추상적인 인터페이스를 가질 수 있게 됨
객체 인터페이스는 무엇을 하는지는 표현, but 어떻게 하는지는 노출 X
메시지는 외부 객체가 요청하는 무언가를 의미하기 때문에
메시지를 먼저 식별하면 무엇을 수행할 지에 초점 맞추기 가능
나쁜 예
// 너무 구체적인 메서드가 퍼블릭으로 노출됨
order.calculateDiscountRate();
order.calculateSubtotal();
order.applyCoupon();
- 내부 구현이 퍼블릭 API에 노출되어 결합도 높고, 캡슐화 깨짐
좋은 예
// "총 결제 금액이 궁금하다"는 메시지
order.getFinalPrice();
- 내부 로직(할인, 쿠폰, 세금 등)은 숨기고,
- 단 하나의 의미 있는 메시지로 외부와 소통 → 추상화 유지
행동이 상태를 결정
객체가 존재하는 이유 >> 협력 참여 위해
객체를 객체답게 만드는 것은 객체의 상태가 아닌 다른 객체에게 제공하는 행동
초보자들은 먼저 객체에 필요한 상태가 무엇인지를 결정하고, 그 후에 상태에 필요한 행동을 결정
>> 이런 방식은 객체의 내부 구현이 객체의 퍼블릭 인터페이스에 노출되도록 만들기 때문에 캡슐화를 저해
객체의 내부 구현에 초점을 맞춘 설계 방벙을 데이터-주도 설계(Data-Driven Design)
라고 함>> 좋지 않음
예시 | 설명 |
DTO 객체에 비즈니스 로직 추가 | 원래 단순 데이터 전송용 객체에 점점 처리 로직을 넣게 됨 |
User 클래스에 setName(), setEmail()만 존재 | 정보 보관만 하고 책임 없음 → 아무나 상태 변경 가능 |
Order 객체가 getItemList().add() 식으로 직접 수정됨 | 외부에서 내부 리스트 조작 → 캡슐화 위반 |
DB 테이블 필드 중심으로 POJO 먼저 만든 후 메서드 붙이기 | "DB 구조 = 객체 구조"가 되며, 도메인 모델이 아님 |
@Entity
public class Order {
@Id
private Long id;
@OneToMany
private List<OrderItem> items;
public Money getTotalPrice() {
return items.stream()
.map(OrderItem::getSubtotal)
.reduce(Money.ZERO, Money::plus);
}
// JPA용 기본 생성자
protected Order() {}
public void addItem(OrderItem item) {
this.items.add(item);
}
}
이렇게 Entity, domain 을 하나로 섞어쓰지 말고
// Entity (Persistence 전용)
@Entity
public class OrderEntity {
@Id
private Long id;
@OneToMany
private List<OrderItemEntity> items;
public List<OrderItemEntity> getItems() {
return items;
}
}
// Domain (Business Logic 전용)
public class Order {
private List<OrderItem> items;
public Money calculateTotalPrice() {
return items.stream()
.map(OrderItem::getSubtotal)
.reduce(Money.ZERO, Money::plus);
}
public boolean isFreeShipping() {
return calculateTotalPrice().isGreaterThan(Money.of(100000));
}
}
DB테이블과 매핑되는 Entity 역할, 비즈니스 로직을 위한 Domain 역할 객체를 나눠 사용
쉽게 말해서동작을 먼저 생각하고 상태 (필드 등)을 결정해라
역할
객체지향 설계에서 객체가 특정 문맥(상황)에서 수행해야 할 책임의 집합을 의미
즉, 객체가 어떤 행위자(actor)로서 시스템 안에서 어떤 기능을 담당하는가를 나타냄
유연하고 재사용 가능한 협력
역할과 책임을 중심으로 객체 간 협력을 설계하여,
구체 클래스가 아닌 추상화(역할)를 기준으로 상호작용하게 하고
그로 인해 객체 간 결합도를 줄이고 교체와 확장이 가능한 구조를 만드는 것
// 역할 (추상화된 협력 대상)
public interface DiscountPolicy {
Money calculateDiscountAmount(Screening screening);
}
// 협력 객체 1: 정액 할인
public class AmountDiscountPolicy implements DiscountPolicy {
public Money calculateDiscountAmount(Screening screening) {
return new Money(1000);
}
}
// 협력 객체 2: 비율 할인
public class PercentDiscountPolicy implements DiscountPolicy {
public Money calculateDiscountAmount(Screening screening) {
return screening.getFee().times(0.1);
}
}
// 협력의 클라이언트: Movie
public class Movie {
private DiscountPolicy discountPolicy; // 역할에 의존
public Money calculateFee(Screening screening) {
Money discount = discountPolicy.calculateDiscountAmount(screening); // 메시지 전송
return screening.getFee().minus(discount);
}
}
항목 | 설명 |
정책 교체 유연 | DiscountPolicy 인터페이스만 구현하면 새 정책을 Movie에 손대지 않고 추가 가능 |
테스트 용이 | FakeDiscountPolicy 등을 만들어 단위 테스트 가능 |
협력 구조 재사용 가능 | calculateDiscountAmount() 메시지를 통해 다양한 할인 방식과 유사 구조의 협력 재사용 가능 |
확장성 확보 | TimeDiscountPolicy, HolidayDiscountPolicy 등 무한 확장 가능 |
역할 모델링
- 역할 → 책임 → 객체 순으로 설계 흐름을 유지
- 행위 중심 설계를 유도 (데이터 중심이 아님)
- 추상적인 역할 단위의 협력 구조를 정의하고,
후에 구체적인 객체로 매핑하여 유연한 구조를 만들 수 있도록 함
구성요소 | 설명 |
역할(Role) | 객체가 수행해야 할 기능적 위치/행위자 |
책임(Responsibility) | 역할이 수행해야 할 작업과 정보 |
협력(Collaboration) | 역할들이 어떻게 메시지를 주고받으며 목표를 달성하는지 |
예시 시나리오 : 결제 시스템
고객이 결제를 요청하면, 시스템은 결제 수단을 선택해서 결제를 수행
결제 수단은 카드나 포인트 등으로 다양하게 바뀔 수 있음
1. 역할의 정의
역할 | 책임 |
결제 요청자 | 결제 요청 시작 (requestPayment) |
결제 처리자 | 결제 수행 (process()) |
2. 코드로 역할 모델링
public interface PaymentMethod {
void process(int amount);
}
3. 실제 협력 객체 정의
public class CardPayment implements PaymentMethod {
public void process(int amount) {
System.out.println("카드로 " + amount + "원 결제 완료");
}
}
public class PointPayment implements PaymentMethod {
public void process(int amount) {
System.out.println("포인트로 " + amount + "원 결제 완료");
}
}
4. 역할을 이용하는 클라이언트 객체
public class Customer {
private PaymentMethod paymentMethod; // 역할에 의존
public Customer(PaymentMethod paymentMethod) {
this.paymentMethod = paymentMethod;
}
public void requestPayment(int amount) {
paymentMethod.process(amount); // 협력 메시지
}
}
5. 실행
public class Main {
public static void main(String[] args) {
PaymentMethod method = new CardPayment(); // 역할에 대한 구체 객체 주입
Customer customer = new Customer(method);
customer.requestPayment(10000); // 카드로 10000원 결제 완료
// 역할 교체: 포인트로 결제
customer = new Customer(new PointPayment());
customer.requestPayment(5000); // 포인트로 5000원 결제 완료
}
}
요소 | 설명 |
역할 | PaymentMethod 인터페이스 |
책임 | process(int amount) 수행 |
협력 | Customer → PaymentMethod 메시지 전달 |
유연성 | 카드, 포인트 등 구현 객체 교체 가능 |
역할과 추상화
역할은 곧 추상화의 이름이며,
추상화는 역할을 코드로 표현하는 수단
// 역할: PaymentMethod
// 추상화로 표현된 인터페이스
public interface PaymentMethod {
void process(int amount);
}
- PaymentMethod는 "결제 처리자"라는 역할을 추상화
- 역할을 수행할 수 있는 객체는 무엇이든 이 인터페이스를 구현하면 됨
실제 객체는 역할을 수행하는 배우(actor)일 뿐
public class CardPayment implements PaymentMethod {
public void process(int amount) {
System.out.println("카드로 " + amount + "원 결제");
}
}
public class PointPayment implements PaymentMethod {
public void process(int amount) {
System.out.println("포인트로 " + amount + "원 결제");
}
}
- 이 두 클래스는 같은 역할(PaymentMethod)을 수행하지만, 방식이 다름
- 즉, 역할은 하나, 구현은 여러 개 → 역할은 추상화고, 객체는 구체화
<<interface>> PaymentMethod ← 역할 (추상화)
▲
|
+------------------------+
| CardPayment | ← 역할을 수행하는 구체 클래스
+------------------------+
| PointPayment | ← 또 다른 구체 클래스
- “역할”은 객체가 시스템 내에서 수행하는 기능적 위치
- “추상화”는 그 역할을 코드상에서 표현하는 수단 (ex: 인터페이스, 추상 클래스)
- 객체지향 설계에서는 역할을 기준으로 추상화를 먼저 정의하고 그 역할을 수행하는 여러 객체를 구현하여 협력을 설계하는 것이 핵심
'복습 > Object' 카테고리의 다른 글
[Object] 객체지향 프로그래밍 (0) | 2025.07.20 |
---|