07. 함께 모으기

 · 6 mins read

07. 함께 모으기

코드와 모델을 밀접하게 연관시키는 것은 코드에 의미를 부여하고 모델을 적절하게 한다.
  • 객체지향 안에 존재하는 세 가지 상호 연관된 관점
    • 개념 관점 (Conceptual Perspective) 클래스가 은유하는 개념
      • 여기서 설계는 도메인 안에 존재하는 개념과 개념들 사이의 관계를 표현
      • 관점은 사용자가 도메인을 바라보는 관점을 반영
    • 명세 관점 (Specification Perspective) 클래스의 공용 인터페이스
      • 사용자의 영역인 도메인을 벗어나 개발자의 영역인 소프트웨어로 초점이 옮겨짐
      • 명세 관점에서 프로그래머는 객체가 협력을 위해 ‘무엇’을 할 수 있는가에 초점
    • 구현 관점 (Implementation Perspective) 클래스의 속성과 메서드
      • 실제 작업을 수행하는 코드와 연관
      • 객체의 책임을 ‘어떻게’ 수행할 것인가에 초점
      • 인터페이스를 구현하는 데 필요한 속성과 메서드를 클래스에 추가


커피 전문점 도메인

구성 요소(객체)

  • 메뉴판 객체, 메뉴 객체
  • 손님 객체
  • 바리스타 객체
  • 아메리카노 객체, 카푸치노 객체, 카라멜 마키아또 객체 …

동적인 객체를 정적인 타입으로

  • 손님 타입
  • 메뉴판 타입
  • 메뉴 항목 타입
  • 바리스타 타입
  • 커피 타입

타입 간 관계

  • 메뉴 항목 타입은 메뉴판 타입에 포함(containment) or 합성(composition) 관계
  • 손님 타입과 메뉴판 타입은 연관(association) 관계
  • 손님 타입과 바리스타 타입은 연관 관계
  • 바리스타 타입과 커피 타입은 연관 관계
  • 도메인 모델을 작성하는 단계에서 초점은 *"어떤 타입이 도메인을 구성하느냐와 타입들 사이에 어떤 관계가 존재하는지를 파악함으로써 도메인을 이해하는 것"*


설계하고 구현하기

적절한 객체에게 적절한 책임 할당하기 훌륭한 협력을 설계하는 것이 목표 .!

커피를 주문하기 위한 협력 찾기

  • 메시지를 먼저 선택하고 그 후에 메시지를 수신하기에 적절한 객체를 선택하자.

  • 메시지를 수신할 객체는 메시지를 처리할 책임을 맡게 되고 객체가 수신하는 메시지는 객체가 외부에 제공하는 공용 인터페이스에 포함된다.

  1. "커피를 주문하라(메뉴 이름)"라는 메시지를 수신할 객체는 "손님"
    • 손님 타입의 인스턴스가 메시지를 처리할 객체
    • 손님 객체는 커피를 주문할 책임을 할당 받았다.
  2. 손님으로부터 "메뉴 항목을 찾아라(메뉴 이름)"라는 메시지를 수신할 객체는 "메뉴판 객체"
    • 메뉴 이름에 대응되는 "메뉴 항목"을 반환
  3. 손님으로부터 "커피를 제조하라(메뉴 항목)"라는 메시지를 수신할 객체는 "바리스타"
    • 손님에게 "커피"를 반환
  4. 바리스타로부터 "커피를 생성하라"라는 메시지를 수신할 객체는 "커피"

인터페이스 정리하기

  • 객체가 수신한 메시지가 객체의 인터페이스를 결정한다!

  • 객체가 어떤 메시지를 수신할 수 있다는 것은 그 객체의 인터페이스 안에 메시지에 해당하는 오퍼레이션이 존재한다는 것을 의미한다.

class Customer {
    public void order(String menuName)
}

class MenuItem {
}

class Menu {
    public MenuItem choose(String name) {}
}

class Barista {
    public Coffee makeCoffee(MenuItem menuItem) {}
}

class Coffee {
    public Coffee(MenuItem menuItem) {}
}


구현하기

// Customer의 협력 : Menu에게 menuName에 해당하는 MenuItem을 찾아달라고 요청
class Customer {
    public void order(String menuName, Menu menu, Barista barista) {
        MenuItem menuItem = menu.choose(menuName);
        Coffee coffee = barista.makeCoffee(menuItem);
        // ...
    }
}
  • 설계는 코드로 구현하는 단계에서 대부분 변경된다.
    • 협력을 구상하는 단계에 너무 오랜 시간 쏟지 말고 최대한 빨리 코드를 구현하자.


// Menu는 menuName에 해당하는 MenuItem을 찾아야 하는 책임이 있다.
class Menu {
    private List<MenuItem> items;
    
    public Menu(List<MenuItem> items) {
        this.items = items;
    }
    
    public MenuItem choose(String name) {
        for(MenuItem each : items) {
            if(each.getName().equals(name)) {
                return each;
            }
        }
        return null;
    }
}
  • 객체가 어떤 책임을 수행해야 하는지를 결정한 후에야 책임을 수행하는 데 필요한 객체의 속성을 결정하자.
  • 이것이 객체의 구현 세부 사항을 객체의 공용 인터페이스에 노출시키지 않고 인터페이스와 구현을 깔끔하게 분리할 수 있는 기본적인 방법이다.


// Barista는 MenuItem을 이용해서 커피를 제조
class Barista {
    public Coffee makeCoffee(MenuItem menuItem) {
        Coffee coffee = new Coffee(menuItem);
        return coffee;
    }
}
// Coffee는 자기 자신을 생성하기 위한 생성자를 제공
class Coffee {
    private String name;
    private int price;
    
    public Coffee(MenuItem menuItem) {
        this.name = menuItem.getName();
        this.price = menuItem.getPrice();
    }
}
public class MenuItem {
    private String name;
    private int price;
    
    public MenuItem(String name, int price) {
        this.name = name;
        this.price = price;
    }
    
    public int cost() {
        return price;
    }
    
    public String getName() {
        return name;
    }
}
  • 인터페이스는 객체가 다른 객체와 직접적으로 상호작용하는 통로다.
  • 코드를 작성해 가면서 협력을 설계하자.


코드와 세 가지 관점

코드는 세 가지 관점을 모두 제공해야 한다

  • 개념 관점
    • 소프트웨어 클래스와 도메인 클래스 사이의 간격이 좁으면 좁을수록 기능을 변경하기 위해 뒤적거려야 하는 코드의 양도 점점 줄어든다.
  • 명세 관점
    • 최대한 변화에 안정적인 인터페이스를 만들기 위해서는 인터페이스를 통해 구현과 관련된 세부 사항이 드러나지 않게 해야 한다.
  • 구현 관점
    • 메서드의 구현과 속성의 변경은 원칙적으로 외부의 객체에게 영향을 미쳐서는 안 된다.
    • 메서드와 속성이 철저하게 클래스 내부로 캡슐화돼야 한다.

훌륭햔 객체지향 프로그래머는 하나의 클래스 안에 세 가지 관점을 모두 포함하면서도 각 관점에 대응되는 요소를 명확하고 깔끔하게 드러낼 수 있다.

도메인 개념을 참조하는 이유

  • 메시지를 수신할 객체를 선택하는 첫 번째 전략은 도메인 개념 중이서 가장 적절한 것을 선택하는 것

  • 소프트웨어 클래스가 도메인 개념을 따르면 변화에 쉽게 대응할 수 있다.

인터페이스와 구현을 분리하라