스터디

[객체지향의 사실과 오해] 5장, 6장 정리

nkdev 2025. 8. 23. 23:29

5장. 책임과 메시지

5장을 한 문장으로 정리하면 ‘객체의 자율성이 협력의 품질을 결정한다’

객체가 자율적으로 책임을 수행해야 협력이 단순해지고, 응집도가 올라가고, 캡슐화가 잘 된다. 결론적으로 설계의 확장성과 유연성, 재사용성이 향상된다.

 

‘객체가 자율적이다’

 

어떤 요청을 받았을 때, 그 책임을 어떻게 수행할 건지에 대해서는 객체 본인이 판단한다는 의미.

객체의 책임이 너무 구체적이면 객체의 자율성이 침해되고, 객체의 책임이 너무 추상적이면 객체가 해야 할 행동이 명확하지 않아진다.

1. 객체가 너무 구체적인 책임을 지게 되어 자율성이 침해되는 경우

Notification 객체에게 부여된 책임

  • type을 판단하라
  • 판단된 type에 따라서 알림을 전송하라

문제점?

  • 호출부의 문자열 타입에 오타가 있는 경우 런타임 에러 위험 → 타입 안정성
  • 전송 로직이 수정되거나 전송 타입이 추가될 경우 Notification 객체 내부 로직 수정 필요 → 확장성, 응집도
class Notification {
    public void send(String type, String message) {
        if ("EMAIL".equals(type)) {
            System.out.println("[이메일 발송] " + message);
        } else if ("SMS".equals(type)) {
            System.out.println("[문자 발송] " + message);
        }
    }
}

public class TooSpecificExample {
    public static void main(String[] args) {
        Notification notification = new Notification();
        // 호출자가 방식까지 지정해야 함 → 자율성 부족
        notification.send("EMAIL", "회원가입을 환영합니다!");
        notification.send("SMS", "비밀번호를 재설정하세요.");
    }
}

2. 객체가 자율성을 가지고 스스로 판단하는 경우

Notification 객체는 ‘알림을 보내라’는 책임 한 가지만 가짐
실제 발송 책임은 Sender라는 객체에게 맡김
Notification은 Sender에게 요청해야 하므로 내부에 Sender 인터페이스를 필드로 가짐

  • 필드로 인터페이스를 갖는 이유
    • 다형성을 활용하기 위해서
    • Notification이 Sender의 세부 구현을 알게 되면 새로운 전송 방법이 생겼을 때 Notification 클래스의 코드도 함께 변경됨
interface Sender {
    void send(String message);
}

class EmailSender implements Sender {
    public void send(String message) {
        System.out.println("[이메일 발송] " + message);
    }
}

class SmsSender implements Sender {
    public void send(String message) {
        System.out.println("[문자 발송] " + message);
    }
}

class Notification {
    private Sender sender;

    // 어떤 Sender를 쓸지 생성자에서 결정 → 자율성 확보
    public Notification(Sender sender) {
        this.sender = sender;
    }

    public void notify(String message) {
        sender.send(message); // 내부에서 어떻게 보낼지 스스로 결정
    }
}

public class AutonomousExample {
    public static void main(String[] args) {
        Notification emailNoti = new Notification(new EmailSender());
        emailNoti.notify("회원가입을 환영합니다!");

        Notification smsNoti = new Notification(new SmsSender());
        smsNoti.notify("비밀번호를 재설정하세요.");
    }
}

 

객체가 자율적이려면?

 

묻지 말고 시켜라 (Tell, Don’t Ask)
데이터를 꺼내서 외부에서 처리하지 말고, 그 객체에게 직접 시켜서 책임을 맡기라는 뜻이다.
객체가 본인이 가지고 있는 데이터를 외부에 노출하지 않고 요청 받으면 스스로 처리하는 것 즉, 객체가 스스로 책임을 수행할 수 있는 것도 객체의 자율성이다.

 

메시지를 믿어라
메시지를 먼저 결정하고 그 메시지를 요청할 객체를 선택하는 방식을 따르면 객체의 자율성은 자연히 따라온다.
발신자는 수신자를 몰라도 되며, 자신이 전송한 메시지를 잘 처리할 것이라고 믿고 메시지를 전송할 수밖에 없다.

 

좋지 않은 예시

  • 객체의 내부 데이터와 계산 로직이 외부에 노출됨
  • 발신자가 수신자의 데이터와 로직을 모두 알게 됨 → 객체의 자율성 x
class Order {
    private List<Integer> itemPrices = new ArrayList<>();

    public void addItem(int price) {
        itemPrices.add(price);
    }

    // 단순히 데이터 접근 메서드
    public List<Integer> getItemPrices() {
        return itemPrices;
    }
}

public class AskExample {
    public static void main(String[] args) {
        Order order = new Order();
        order.addItem(3000);
        order.addItem(5000);

        // 외부에서 데이터를 꺼내서 직접 합계 계산
        int total = 0;
        for (int price : order.getItemPrices()) {
            total += price;
        }

        System.out.println("총 가격: " + total);
    }
}

 

좋은 예시

  • 메시지를 믿고 전송하는 경우
  • 발신자는 수신자를 알 필요 없이 메시지를 잘 수행할 것을 믿고 메시지만 보냄
  • 수신자는 메시지를 받으면 데이터와 로직을 사용해 자율적으로 처리함
class Order {
    private List<Integer> itemPrices = new ArrayList<>();

    public void addItem(int price) {
        itemPrices.add(price);
    }

    // 이제 외부는 총합 계산을 직접 하지 않고 Order에게 시킴
    public int calculateTotalPrice() {
        return itemPrices.stream().mapToInt(Integer::intValue).sum();
    }
}

public class TellExample {
    public static void main(String[] args) {
        Order order = new Order();
        order.addItem(3000);
        order.addItem(5000);

        // 외부는 단순히 "총 가격 계산해줘"라고만 시킴
        System.out.println("총 가격: " + order.calculateTotalPrice());
    }
}