
07장: 함께 모으기
코드와 모델을 밀접하게 연관시키는 것은 코드에 의미를 부여하고 모델을 적절하게 한다.
-Eric Evans
객체지향 설계 안에는 세 가지 관점이 존재한다.
개념 관점(Conceptual Perspective)에서 설계는 도메인 안에 존재하는 개념과 개념들 사이의 관계를 표현한다. 실제 도메인의 규칙과 제약을 최대한 유사하게 반영하는 것이 핵심이다.
명세 관점(Specification Perspective)에 이르면 사용자 영역인 도메인을 벗어나 개발자 영역인 소프트웨어로 초점이 옮겨진다. 즉, 객체 인터페이스를 바라보며 객체들의 책임에 초점을 맞추게 된다.
구현 관점(Implementation Perspective)은 객체들이 책임을 수행하는 데 필요한 코드를 작성하는 것에 초점을 맞춘다. 개발자는 객체의 책임을 '어떻게' 수행할 것인가를 생각하며 인터페이스 구현에 필요한 속성과 메서드를 클래스에 추가한다.
이 세 관점은 소프트웨어 개발 순서가 아니다. 개념/명세/구현 관점은 동일한 클래스를 세 가지 다른 방향에서 바라보는 것을 의미한다.
클래스는 세 가지 관점을 모두 수용할 수 있도록 개념, 인터페이스, 구현을 함께 드러내야 한다.
협력 안에서 메시지를 선택하고 메시지를 수신할 객체를 선택하는 것은 객체의 인터페이스, 즉 명세 관점에서 객체를 바라보는 것이다.



객체지향 세계에서는 모든 객체가 능동적이고 자율적인 존재다. 메뉴판은 마치 생명을 가진 존재처럼 자기 스스로 메뉴 항목을 찾는다. 따라서 설계자는 무생물을 생물처럼 '의인화' 해야 한다.
소프트웨어 객체는 현실 속의 객체를 모방하거나 추상화한 것이 아니다. 단지 의미를 쉽게 유추할 수 있도록 '은유'할 뿐이다.
메시지가 객체를 선택하고, 선택된 객체는 메시지를 자신의 인터페이스로 받아들인다.

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) {}
}

class Customer {
public void order(String menuName, Menu menu, Barista barista) {
MenuItem menuItem = menu.choose(menuName);
Coffee coffee = barista.makeCoffee(menuItem);
...
}
}
class Menu {
private List<MenuItem> items;
public Menu(List<MenuItem> items) {
this.items = items;
}
public MenuItem choose(String name) {
for (MenuItem item : items) {
if (item.getName().equals(name)) {
return item;
}
}
return null;
}
}
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;
}
}
class Barista {
public Coffee makeCoffee(MenuItem menuItem) {
Coffee coffee = new Coffee(menuItem);
return coffee;
}
}
class Coffee {
private String name;
private int price;
public Coffee(MenuItem menuItem) {
this.name = menuItem.getName();
this.price = menuItem.cost();
}
}
개념 관점에서 코드를 바라보면 Customer, Menu, MenuItem, Barista, Coffee 클래스가 보인다. 커피 제조 과정 변경 시 Barista 클래스를 변경하면 된다고 쉽게 유추할 수 있듯이, 소프트웨어 클래스가 도메인 개념의 특성을 최대한 수용하면 변경을 관리하고 쉽고 유지보수성을 향상시킬 수 있다.
명세 관점은 클래스의 인터페이스를 바라본다. 클래스의 public 메서드는 다른 클래스가 협력할 수 있는 공용 인터페이스를 드러낸다. 객체의 인터페이스는 수정하기 어렵다. 최대한 변화에 안정적인 인터페이스를 만들기 위해서는 인터페이스를 통해 구현과 관련된 세부 사항이 드러나지 않게 해야 한다.
구현 관점은 클래스의 내부 구현을 바라본다. 클래스의 메서드와 속성은 구현에 속하며 공용 인터페이스의 일부가 아니다. 즉, 메서드와 속성이 철저하게 클래스 내부로 캡슐화되어야 한다.
도메인 개념 안에서 적절한 객체를 선택하는 것은 도메인에 대한 지식을 기반으로 코드의 구조와 의미를 쉽게 유추할 수 있게 한다.
명세 관점 : 클래스의 안정적인 측면
구현 관점 : 클래스의 불안정한 측면
명세 관점과 구현 관점을 분리하는 것은 매우 중요하다.
프로그래머 입장에서 코드를 가장 많이 접하게 되어 구현 관점을 가장 빈번하게 사용하겠지만, 실제로 설계 품질을 향상시키려면 명세 관점으로 설계해야 한다.