서문
마틴 파울러의 객체지향 설계 안에 존재하는 세 가지 상호 연관된 관점
- 개념 관점 (Conceptual Perspective): 도메인 안에 존재하는 개념과 개념들 사이의 관계를 표현
- 도메인 (domain): 사용자들이 관심을 가지고 있는 특정 분야나 주제
- 명세 관점 (Specification Perspective): 소프트웨어 안에서 살아 숨 쉬는 객체들의 책임에 초점을 맞춤
- 객체의 인터페이스 (interface)를 바라본다.
- 구현 관점 (Implementation Perspective): 객체들이 책임을 수행하는 데 필요한 동작하는 코드를 작성
위 관점들이 순서대로 개발되는 게 아니라, 클래스에서 이 관점들이 모두 관찰될 수 있도록 작성해야 한다.
커피 전문점 도메인
커피 전문점 도메인을 정의해 보면 다음과 같다.
- 커피 전문점 안에는 메뉴판이 있다.
- 메뉴판 안에는 아메리카노, 카푸치노, 캐러멜 마끼아또, 에스프레소가 있다.
- 메뉴판과, 각 메뉴들은 모두 객체다.
- 손님은 메뉴판을 보고 바리스타에게 원하는 커피를 주문한다.
- 손님, 바리스타 모두 객체다.
- 바리스타는 자율적으로 커피를 제조한다.
관계 파악하기
각 객체의 관계를 파악해보자.
- 손님은 메뉴판에서 주문할 커피를 선택한다. 따라서 손님과 메뉴판 사이에 관계가 있다.
- 손님은 바리스타에게 주문을 한다. 따라서 손님과 바리스타 사이에 관계가 있다.
- 바리스타는 커피를 제조한다. 따라서 바리스타와 커피 사이에 관계가 있다.
- 메뉴판은 여러 메뉴를 들고 있다. 이 관계를 포함 (containment) 또는 합성 (composition)이라 한다.
- 손님과 메뉴판이 서로를 알고 있지만 손님이 메뉴판의 일부가 아니기 때문에, 이러한 관계를 연관 (association)이라고 한다. 즉, 위의 합성 관계를 제외한 나머지 관계들은 모두 연관 관계이다.
- 실제 관계를 파악할 때는 합성, 연관 관계임을 구분하는 것보다는 그저 관계가 있다는 것을 이해하는 게 중요하다.
이렇게 소프트웨어가 대상으로 하는 영역인 도메인을 단순화해서 표현한 모델을 도메인 모델이라 한다.
위의 경우, 커피 전문점이라는 도메인을 단순화해서 표현한 모델이다. 도메인이라는 개념에 대해 확실히 정립하도록 하자.
도메인 모델을 설계할 때 각 객체는 타입으로 추상화해야 한다. 복잡성을 낮추기 위함이다.
설계하고 구현하기
훌륭한 객체지향 설계를 하기 위한 첫 번째는 훌륭한 협력을 설계하는 것이다. 훌륭한 객체는 훌륭한 협력을 설계할 때만 얻을 수 있기 때문이다.
훌륭한 협력을 얻기 위해서는 메시지가 객체를 선택하도록 해야 한다. 메시지를 먼저 선택하고, 그 후 메시지를 수신하기에 적절한 객체를 선택해야 한다.
커피 주문 메시지
커피를 주문하는 것을 메시지로 뽑아보면 다음과 같이 표현 가능하다. 메시지 위에 붙은 추가 화살표는 메시지에 넣을 인자를 의미한다.
이제 메시지를 처리할 객체를 선택한다. 우선 도메인 모델 안에 책임을 수행하기에 적절한 타입이 존재하는지 살펴봐야 한다.
도메인 모델을 보면 손님 타입의 인스턴스가 적절하다는 것을 파악할 수 있다.
메뉴판 조회 메시지
손님이 커피를 주문하는데, 해당 메뉴 이름을 메뉴판에서 조회해야 한다. 그런데 메뉴 항목에 대해서는 알지 못한다. 따라서 협력을 위해 요청해야 한다.
메뉴 항목에 대해 알고 있는 객체는 메뉴판 객체이다. 따라서 메뉴판 객체에게 요청한다.
커피 제조 메시지
이제 손님은 메뉴 항목을 커피 제조에 넘겨 제조된 커피를 받는다. 손님이 커피 제조를 할 수는 없기 때문에, 적절한 객체인 바리스타 객체에게 메뉴 항목을 들고 요청한다. 이후 바리스타는 커피를 제조한다.
각 객체들이 수신하는 메시지는 객체의 인터페이스를 결정한다.
실제 개발
손님
설계를 바탕으로 실제 개발을 해 보자.
처음에 설계한 대로라면, 손님은 주문을 할 때 메뉴 이름을 함께 전달한다. 즉 코드가 다음과 같을 것이다.
public class Customer {
public void order(String menuName) {
// 내용
}
}
그런데, 주문을 할 때 다른 객체에게 협력을 요청한다. 즉 메서드의 인자에 다른 객체들이 들어가야 한다. 이는 구현 도중에 객체의 인터페이스가 달라질 수 있음을 의미한다.
따라서 다음과 같이 수정해야 한다.
public class Customer {
public void order(String menuName, Menu menu, Barista barista) {
MenuItem menuItem = menu.choose(menuName);
Coffee coffee = barista.makeCoffee(menuItem);
}
}
머릿속으로만 구상한 설계는 코드로 구현하는 단계에서 대부분 변경되기에, 설계에 너무 오래 머무르지 말고 최대한 빨리 코드로 구현하여 설계를 점검하고 변경해 나가도록 한다.
메뉴판
메뉴판은 전달받은 menuName에 해당되는 MenuItem을 찾는다.
public 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;
}
}
이때 주의 깊게 볼 점은, 객체의 인터페이스 (외부)에서 최대한 내부 속성인 List <MenuItem>을 모르게 했다는 점이다. 객체의 책임을 결정한 뒤, 그 책임을 수행하는 데 필요한 속성을 결정하도록 하자.
바리스타
바리스타는 `menuItem`을 기반으로 커피를 만든다.
public class Barista {
public Coffee makeCoffee(MenuItem menuItem) {
Coffee coffee = new Coffee(menuItem);
return coffee;
}
}
커피
MenuItem의 값을 토대로 커피를 생성한다.
public class Coffee {
private String name;
private int price;
public Coffee(MenuItem menuItem) {
this.name = menuItem.getName();
this.price = menuItem.cost();
}
}
메뉴
각 메뉴는 이름과 가격을 가진다.
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;
}
}
코드와 세 가지 관점
지금까지 작성된 코드를 위의 세 가지 관점으로 바라보자.
개념 관점
Customer, Menu, MenuItem, Barista, Coffee 클래스들로 구성되어 있다.
따라서 커피 전문점 도메인을 구성하는 중요한 개념과 관계가 반영되었다.
소프트웨어 클래스가 도메인 개념의 특성을 최대한 수용하면 변경을 관리하기 쉽고 유지보수성을 향상할 수 있다.
명세 관점
명세 관점은 객체의 인터페이스를 바라본다고 하였다.
인터페이스를 수정하면 해당 인터페이스를 요청하는 객체들에게 전부 영향이 가게 된다.
그렇기에 최대한 변화에 안정적인 인터페이스를 만들려면, 구현과 관련된 부분을 숨겨야 한다.
구현 관점
공용 인터페이스 외적으로 객체가 가지고 있는 메서드와 속성은 이들의 변경이 일어나더라도 원칙적으로 외부 객체에게 영향을 주어서는 안 된다.
명세 관점은 클래스의 안정적인 측면, 구현 관점은 클래스의 불안정한 측면을 드러내도록 하라.
요약
- 개념 관점은 도메인 안에 존재하는 개념과 개념 사이의 관계를 표현한다.
- 명세 관점은 객체의 인터페이스를 바라본다. 따라서 변화에 안정적이어야 한다.
- 구현 관점은 공용 인터페이스 외적인 부분 (이외의 메서드, 속성)을 바라본다. 따라서 변화에 불안정하다. 변화가 일어나더라도 외부 객체에게 영향을 주어서는 안 된다. (원칙적으로)
- 설계를 빠르게 하고 코드로 검증해야 한다.
- 구현 도중, 객체의 인터페이스가 달라질 수 있다.
- 책임 설계 후 속성을 결정한다.
'도서 📚 > 📗 객체지향의 사실과 오해' 카테고리의 다른 글
6장: 객체 지도 (0) | 2023.11.08 |
---|---|
5장: 책임과 메시지 (0) | 2023.11.07 |
4장: 역할, 책임, 협력 (0) | 2023.11.05 |
3장: 타입과 추상화 (1) | 2023.11.02 |
2장: 이상한 나라의 객체 (1) | 2023.11.01 |