Nick Dev

[객사오] 1장. 협력하는 객체들의 공동체 본문

객체지향의 사실과 오해

[객사오] 1장. 협력하는 객체들의 공동체

Nick99 2024. 12. 27. 16:27
반응형

객체지향의 목표는 실세계의 모방이 아니다

  • 객제지향 프로그래밍이란 현실 속 존재하는 사물을 최대한 유사하게 모방해 소프트웨어 내부로 옮겨오는 작업이라고 하지만 실제로 그렇지 않다.
    • 소프트웨어 객체실세계 사물 사이에 존재하는 연관성은 희미
  • 그럼에도 실세계에 대한 비유가 객제지향의 다양한 측면을 이해하고 학습하는데 효과적

객체지향의 가장 중요한 개념 3가지

  1. 역할
  2. 책임
  3. 협력

협력

  • 특정한 책임을 수행하는 역할들 간의 연쇄적인 요청과 응답을 통해 목표를 달성
  • 스스로 해결하지 못하는 문제를 마주치면 도움을 요청함
    • 도움은 또 다른 도움 요청을 발생시킴
    • 이렇게 연쇄적으로 요청이 발생함
  • 요청을 받은 사람은 주어진 책임을 다하면서 요청에 대해 응답함
  • 협력의 성공은 특정 역할을 맡은 개인이 얼마나 요청을 성실히 이행하는가에 달려 있음
  •  

역할

  • 어떤 협력에 참여하는 특정 사람이 협력 안에서 차지하는 책임이나 의무를 의미
  • 역할은 의미적으로 책임이라는 개념을 내포
    • 캐시어라면 손님으로부터 주문을 받을 책임이 있음
    • 즉, 특정 역할은 특정 책임을 암시
  • 특징
    • 여러 객체가 동일한 역할 수행 가능
    • 역할은 대체 가능성을 의미
      • 두 객체가 동일 역할을 수행하면 어떤 객체가 역할을 수행하더라도 무관하다
    • 각 객체는 책임을 수행하는 방법을 자율적으로 선택 가능
      • 동일한 요청에 대해 서로 다른 방식으로 응답할 수 있는 능력 ⇒ 다형성
    • 하나의 객체가 동시에 여러 역할을 수행할 수 있음

객체

  • 협력에 참여하는 주체는 객체
  • 객체는 상태(state)와 행동(behavior)을 함께 지닌 실체다
  • 객체가 협력에 참여하기 위해 어떤 행동을 해야 된다면, 그 행동을 하는데 필요한 상태도 함께 지니고 있어야 한다
  • 객체의 사적인 부분 → 객체 스스로 관리하고 외부에서 일체 간섭할 수 없도록 차단
  • 객체 외부에서는 접근이 허락된 수단을 통해서만 객체와 의사소통해야 함
  • 즉, 객체는 다른 객체가 무엇을 수행하는지는 알 수 있지만, 그걸 어떻게 수행하는지는 모른다.
    • 예를 들어, 캐시어 라는 객체는 바리스타가 커피를 만들어준다는 건 알지만, 커피를 어떻게 만들어서 줄 지는 모른다
    • 핸드 드랍으로 줄지, 머신으로 내려줄지 모른다.
    • 그냥 바리스타라는 객체가 어떻게 만들지는 캐시어 객체 입장에서는 알빠노 임

메시지

  • 객체지향 세계에 존재하는 단 1가지 의사소통 방식
  • 객체는 협력을 위해 다른 객체에게 메시지를 전송하고 다른 객체로부터 메시지를 수신함

메서드

  • 객체가 수신된 메서드를 처리하는 방법을 메서드라고 한다
  • 어떤 객체에게 메시지를 전송 → 메시지에 대응되는 특정 메서드가 실행된다
    • 메시지를 수신한 객체가 런타임 시점에 메서드를 선택할 수 있다는 것
    • 이게 다른 프로그래밍 언어와 객체지향 프로그래밍 언어를 구분하는 핵심 특징
  • 예시
    • 메시지 : 캐시어 → 바리스타 로 전달된 커피 제조 요청
    • 메서드 : 바리스타가 커피를 제조하는 구체적인 방법
  • 외부 요청이 무엇인지 표현하는 메시지와 요청을 처리하는 구체적 방법인 메서드를 분리하는 것은 객체의 자율성을 높여주는 핵심 메커니즘이다.

메시지와 메서드 예시

메시지 : 메서드 호출을 의미..? → 무엇을 할지
메서드 : 메서드 내용을 의미..? → 어떻게 할지

// 바리스타 인터페이스
interface Barista {
    Coffee makeCoffee(String menuName);  // 메시지의 형태를 정의
}

// 초보 바리스타
class JuniorBarista implements Barista {
    @Override
    public Coffee makeCoffee(String menuName) {  // 메서드 구현
        return switch (menuName) {
            case "아메리카노" -> {
                extractEspresso();
                addHotWater();
                yield new Coffee("아메리카노");
            }
            case "카페라떼" -> {
                extractEspresso();
                steamMilk();
                yield new Coffee("카페라떼");
            }
            default -> throw new IllegalArgumentException("없는 메뉴입니다.");
        };
    }

    private void extractEspresso() {
        System.out.println("약한 압력으로 에스프레소를 추출합니다.");
    }

    private void addHotWater() {
        System.out.println("뜨거운 물을 넣습니다.");
    }

    private void steamMilk() {
        System.out.println("우유를 데우고 거품을 냅니다.");
    }
}

// 숙련된 바리스타
class SeniorBarista implements Barista {
    @Override
    public Coffee makeCoffee(String menuName) {  // 다른 방식으로 구현된 메서드
        return switch (menuName) {
            case "아메리카노" -> {
                adjustGrinder();
                extractEspresso();
                addHotWater();
                yield new Coffee("아메리카노");
            }
            case "카페라떼" -> {
                adjustGrinder();
                extractEspresso();
                steamMilkWithLatte();
                yield new Coffee("카페라떼");
            }
            default -> throw new IllegalArgumentException("없는 메뉴입니다.");
        };
    }

    private void adjustGrinder() {
        System.out.println("원두 분쇄도를 조절합니다.");
    }

    private void extractEspresso() {
        System.out.println("최적의 압력으로 에스프레소를 추출합니다.");
    }

    private void addHotWater() {
        System.out.println("최적의 온도로 물을 넣습니다.");
    }

    private void steamMilkWithLatte() {
        System.out.println("우유를 완벽한 온도로 데우고 라떼아트를 만듭니다.");
    }
}

// 캐시어 클래스
class Cashier {
    private final Barista barista;

    public Cashier(Barista barista) {
        this.barista = barista;
    }

    public Coffee orderCoffee(String menuName) {
        // 바리스타에게 메시지를 보냄 (makeCoffee)
        return barista.makeCoffee(menuName);
    }
}

// 실행 예시
public class CafeExample {
    public static void main(String[] args) {
        // 초보 바리스타로 시작
        Cashier cashier = new Cashier(new JuniorBarista());
        cashier.orderCoffee("아메리카노");  // 메시지 전송

        System.out.println("\n바리스타 교체\n");

        // 숙련된 바리스타로 교체
        cashier = new Cashier(new SeniorBarista());
        cashier.orderCoffee("아메리카노");  // 동일한 메시지, 다른 처리 방식
    }
}
  • 앞서 설명한 메시지를 수신한 객체가 실행 시간에 메서드를 선택할 수 있다
  • 메시지
    • makeCoffee(String menuName) → 커피 만들어 달라는 요청
    • 캐시어는 바리스타에게 단순히 메뉴 만들어달라는 메시지만 전달
    • 캐시어는 어떻게 메뉴가 만들어질지 모름
  • 메서드
    • JuniorBaristaSeniorBaristamakeCoffee 구현 부분
    • 각 바리스타가 커피를 만드는 구체적인 방법
    • 같은 메시지에 대해 다른 방식으로 처리

객체지향의 본질

  • 객체지향이란 자율적인 객체들을 이용해 시스템을 분할하는 방법이다
  • 자율적인 객체란 상태와 행위를 함께 지니고 스스로 자기 자신을 책임지는 객체
  • 객체는 다른 객체와 협력하기 위해 메시지를 전송하고, 수신한 객체는 메시지 처리하는데 적합한 메서드를 자율적으로 선택
  • 객체지향 → 클래스 라고 생각하지 말고 우선적으로 객체를 떠올리자
    • 애플리케이션을 협력하는 객체들의 공동체가 아닌 클래스로 구성된 설계도로 보는 관점은 유연하고 확장 가능한 애플리케이션의 구축을 방해함

코드를 담는 클래스의 관점에서 메시지를 주고 받는 객체의 관점으로 사고의 중심을 전환하자

  • 객체지향의 핵심은 클래스가 아니다 (물론 클래스도 중요함)
    • 클래스는 객체를 만드는데 필요한 구현 메커니즘일 뿐..
  • 메시지를 주고받는 객체들의 동적인 관계가 중요하다
  • 클래스의 구조, 메서드에 집중하지 말고 객체의 역할, 책임, 협력에 집중하자
반응형