[Panda] 객체지향의 사실과 오해 7장
마틴 파울러가 제시한 객체지향 설계는 세 가지 상호 연관된 관점인 개념 관점, 명세 관점, 구현 관점을 통해 소프트웨어 개발을 바라보는 방식을 제공한다. 각 관점은 동일한 객체를 다른 시각에서 이해하는 방법으로, 설계의 핵심 요소가 된다. 이 글에서는 이 세 가지 관점을 커피 전문점의 예시를 통해 설명하고, 코드와 함께 이를 구체화한다.
1. 개념 관점 (Conceptual Perspective)
개념 관점은 도메인에 존재하는 개념과 그 개념들 사이의 관계를 반영하는 데 중점을 둔다. 도메인이란 사용자가 관심을 갖는 특정 주제나 문제 영역을 의미하며, 소프트웨어는 이를 해결하기 위해 개발된다. 이 관점에서는 소프트웨어 객체들이 현실 세계의 개념을 얼마나 잘 반영하느냐가 중요하다.
커피 전문점을 예시로 들면, 커피를 주문하는 과정은 다음과 같은 객체들로 표현된다:
- 손님: 메뉴를 선택하고 바리스타에게 커피를 주문한다.
- 메뉴판: 커피 메뉴 항목들이 포함된 객체다.
- 바리스타: 커피를 제조하는 객체다.
- 커피: 주문된 커피다.
![]() |
![]() |
이들 객체는 도메인에서 각자의 역할을 수행하며 서로 연관 관계를 맺는다.
2. 명세 관점 (Specification Perspective)
명세 관점에서는 도메인에서 벗어나 소프트웨어가 객체 간의 협력을 어떻게 설계하는지에 초점을 맞춘다. 객체가 어떤 책임을 가지고 있는지, 협력을 통해 무엇을 할 수 있는지를 정의하는 것이 명세 관점이다. 여기서 중요한 것은 객체의 인터페이스다. 인터페이스는 객체가 외부와 상호작용하는 방법을 정의하며, 객체가 수신하는 메시지들이 이에 해당한다.
커피 주문 프로세스:
- 손님은 메뉴를 선택하고 바리스타에게 커피를 주문한다.
- 메뉴판은 손님에게 선택된 메뉴 항목을 제공한다.
- 바리스타는 해당 메뉴 항목에 맞는 커피를 제조한다.
![]() |
![]() |
이 과정에서 객체 간의 메시지가 중요하다. 예를 들어, 손님은 "커피를 주문해"라는 메시지를 메뉴판과 바리스타에게 전달하며, 각각의 객체가 이 메시지를 수신하고 처리하는 방식이 인터페이스로 정의된다.
3. 구현 관점 (Implementation Perspective)
구현 관점은 객체가 실제로 어떻게 책임을 수행할지를 다룬다. 이 관점에서의 설계는 코드 구현에 초점을 맞추며, 객체들이 책임을 수행하는 구체적인 동작을 작성하는 과정이다. 클래스의 메서드와 속성은 모두 구현 관점에서 다루는 요소이며, 외부로 노출되지 않는 객체의 내부 동작이 여기에 포함된다.
커피 주문 구현 예시:
- 손님 객체는 메뉴판 객체에서 커피 항목을 선택하고, 그 정보를 바리스타에게 전달하여 커피를 주문한다.
- 바리스타는 메뉴 항목에 따라 적절한 커피를 제조하고 반환한다.
// 손님이 커피를 주문하는 과정
public class Customer {
public void order(String menuName, Menu menu, Barista barista) {
MenuItem menuItem = menu.choose(menuName); // 메뉴에서 항목 선택
Coffee coffee = barista.makeCoffee(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; // 해당 메뉴가 없을 경우
}
}
// 바리스타가 커피 제조
public class Barista {
public Coffee makeCoffee(MenuItem menuItem) {
return new Coffee(menuItem);
}
}
// 커피 클래스
public class Coffee {
private String name;
private int price;
public Coffee(MenuItem menuItem) {
this.name = menuItem.getName(); // 메뉴 항목에서 이름 가져오기
this.price = menuItem.cost(); // 메뉴 항목에서 가격 가져오기
}
}
// MenuItem 클래스: 메뉴 항목의 이름과 가격을 정의
public class MenuItem {
private String name;
private int price;
public MenuItem(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public int cost() {
return price;
}
}
이렇게 구현된 코드에서는 개념 관점에서 정의된 객체들이 실제 소프트웨어 내에서 어떻게 책임을 수행하는지를 명확하게 드러낸다. 손님 객체는 메뉴를 보고 주문을 하며, 바리스타는 커피를 제조한다. 모든 객체는 각자의 책임을 갖고 메시지를 통해 협력한다.
코드의 세 가지 관점
소프트웨어 설계와 구현은 세 가지 관점을 모두 포함해야 한다.
- 개념 관점에서,
Customer
,Menu
,MenuItem
,Barista
,Coffee
는 커피 전문점 도메인을 구성하는 중요한 개념을 반영한다. - 명세 관점에서는 각 클래스의 인터페이스가 중요한 역할을 한다. 예를 들어,
Customer
클래스의order
메서드는 손님이 메뉴에서 항목을 선택하고 바리스타에게 커피를 주문하는 인터페이스를 제공한다. - 구현 관점에서는 클래스의 내부 동작이 드러난다. 각 클래스는 자신의 책임을 수행하기 위한 구체적인 메서드를 구현하며, 그 내부 동작은 외부에 노출되지 않는다.
인터페이스와 구현의 분리
명세 관점에서는 안정적인 인터페이스를 설계하는 것이 중요하다. 인터페이스는 외부 객체와 상호작용하는 유일한 부분이므로 쉽게 변경되어서는 안 된다. 반면, 구현 관점은 클래스 내부에서 발생하는 세부 사항으로, 이는 필요에 따라 변경될 수 있다. 따라서 인터페이스와 구현을 분리함으로써 설계의 안정성을 높일 수 있다.
객체지향 설계에서 개념 관점, 명세 관점, 구현 관점은 모두 중요한 역할을 하며, 세 가지 관점을 균형 있게 고려해야 소프트웨어의 유지보수성과 확장성을 높일 수 있다. 이를 통해 객체 간의 협력 관계가 명확해지고, 설계의 안정성을 확보할 수 있다.