IT 관련 책 정리 및 후기/객체지향의 사실과 오해

[7장] 함께 모으기

JUN0126 2022. 8. 8. 21:33

객체지향 설계 안에 존재하는 세가지 상호 연관 관점

1. 개념관점

 - 설계는 도메인 안에 존재하는 개념과 개념들 사이의 관계를 표현한다.

 - 실제 도메인의 규칙과 제약을 최대한 코드에 유사하게 반영해야 한다.

 

2. 명세관점

 - 사용자의 영역인 도메인을 벗어나 개발자의 영역인 소프트웨어로 초점이 옮겨진다.

 - 객체가 협력을 위해 무엇을 할 수 있는가에 초점을 맞춘다.

 

3. 구현관점

 - 객체의 책임을 어떻게 수행할 것인가에 초점을 맞추어 인터페이스를 구현하는 데 필요한 속성과 메서들을 클래스에 추가한다.

 

이 세가지 관점대로 순서대로 개발하는 것이 아닌 클래스를 세 가지 다른 방향에서 바라보는 관점이 중요하다.

클래스는 세 가지 관점을 모두 수용할 수 있도록 개념, 인터페이스, 구현을 함께 드러내야 하며, 동시에 코드 안에서 세 가지 관점을 쉽게 식별할 수 있도록 분리해야 한다.

 

커피 전문점 도메인

커피 전문점이라는 예를 들어 해결해야하는 문제 (도메인) 모델에서 시작해서 최종 코드 까지 구현 과정 및 구현 클래스를 개념, 명세, 구현 관점에서 바라보는 것에 대한 의미를 설명 한다.

 

커피 전문점(도메인) 에서의 객체

1. 메뉴판 객체

 - 커피 전문점에서 판매하는 4가지 커피  메뉴 항목 객체를 포함할 수 있다

 

2. 커피 객체

  - 아메리카노, 에소프레소, 카라멜 마키아또 등 커피 종류는 모두 커피 타입의 인스턴스 이다

 

3. 손님 객체

 -1)  메뉴판에서 주문할 커피를 선택 한다

   - 메뉴판 객체를 알아야 하며, 메뉴판 객체와 손님 객체 사이에 관계가 존재

  -2) 바리스타에게 주문을 한다

   - 바리스타 객체를 알아야하며, 바리스타 객체와 손님 객체 사이에 관계가 존재

 

4. 바리스타 객체

 -1) 커피를 제조한다

   - 자신이 만든 커피와 관계를 맺는다

 

메뉴 항목이 메뉴판에 포함 된다는 사실을 표현한 포함관계 (합성관계)

포함관계 (합성관계)

한 타입의 인스턴스가 다른 타입의 인스턴스를 포함하지는 않지만 서로 알고 있어야 할 경우를 이를 연관 관계라고 한다.

커피 전문점 도메인 관계의 도식화

여기까지 단계에서 도메인을 단순화 하여 이해하고, 이제 명세 관점으로 초점을 소프트웨어로 옮겨야 한다

초점은 어떤 타입이 도메인을 구성하느냐와 타입들 사이에 어떤 관계가 존재하는지를 파악함으로써 도메인을 이해하는 것이다.

 

설계하고 구현하기

- 객체지향 설계의 첫 번째 목표는 훌륭한 객체를 설계하는 것이 아닌 훌륭한 협력을 설계하는 것

- 메세지를 먼저 선택하고 그 후에 메시지를 수신하기에 적절한 객체를 선택해야 한다

 

커피 전문점 도메인에서 객체 간 협력 찾기

메시지를 먼저 선택하고 그 후에 메시지를 수신하기 적절한 객체를 선택하는 방식으로 객체간 협력을 구성해야 한다.

 

커피를 주문하라

 - 손님 객체는 커피를 주문할 책임을 할당 받았다.

 - 따라서 메시지를 처리할 객체는 손님 타입의 인스턴스다.

 

메뉴 항목을 찾아라

- 손님은 메뉴 항목에 대해서는 알지 못하기에 메뉴 항목을 누군가가 제공해 줄 것을 요청한다. (메뉴 이름 이라는 인자를 포함해 전송)

 

커피를 제조하라

- 손님은 메뉴 항목에 맞는 커피를 제조해달라고 요청할 수 있다.

- 바리스타는 커피를 제조하는 지식을 가지고 있으며 스스로 판단과 지식에 따라 자율적으로 행동하여 커피를 제조할 수 있다.

 

객체 간 협력을 찾고, 객체간 주고받는 메세지에 대해서 대략적인 윤곽을 잡고 이제 이 메세지를 정제함으로써 각 객체의 인터페이스를 구현할 정도로 상세하게 정제 하여야 한다.

 

인터페이스 정리하기

각 객체를 수신 가능한 메시지만 추려내면 객체의 인터페이스가 된다.

 

• 손님 객체 

 - 커피를 주문하라

class 손님 {
  public void order(String 메뉴이름) {}
}

• 메뉴판 객체 

 - 메뉴 항목을 찾아라

class 메뉴항목 {}

class 메뉴 {
  public 메뉴항목 choose(String name) {}
}

• 바리스타 객체

 - 커피를 제조하라

class Barista {
  public 커피 makeCoffee(메뉴항목 menuItem) {}
}

• 커피 객체

 - 생성하라

class 커피 {
  public 커피(메뉴항목 menuItem){}
}

책 코드에서는 왜 class라고 할까 interface가 아닌가?

 

구현하기

 식별한 인터페이스를 기준으로 해당 인터페이스를 구현하자

 

• 손님 객체 

 - 손님은 메뉴에게 메뉴이름에 해당하는 메뉴항목을 찾아달라고 요청해야 하며, 메뉴항목을 받아 이를 바리스타에게 전달해서 원하는 커피를 제조하도록 요청해야 한다.

class 손님 {
    // 인자로 메뉴와 바리스타 객체를 전달 받는 방법으로 참조 문제 해결
    public void order(String 메뉴이름, 메뉴 menu, 바리스타 barista) {
        메뉴항목 menuItem = 메뉴.choose(메뉴이름);
        커피 coffee = 바리스타.makeCoffee(menuItem);
        ...
    }
}

• 메뉴 객체 

 - 메뉴이름에 해당하는 메뉴항목을 찾아야 하는 책임을 가지며, 이를 위해 메뉴가 내부적으로 메뉴 항목을 관리해야 한다 (Menu가 MenuItem 목록을 포함)

class 메뉴 {
  private List<메뉴항목> items;
    
  public 메뉴(List<메뉴항목> items) {
      this.items = items;
  }
    
  public 메뉴항목 choose(String name) {
    for (메뉴항목 each : items) {
      if (each.getName().equals(name)) {
        return each;
      }
    }
    return null;
  }
}

• 메뉴항목 객체

 - getName(), cost() 메세지에 응답하기 위해 정보를 반환하는 메서드를 구현해야 한다.

public class 메뉴항목 {
  private String name;
  private int price;
    
  public 메뉴항목(String name, int price) {
    this.name = name;
    this.price = price;
  }
    
  public int cost() {
    return price;
  }
    
  public String getName() {
    return name;
  }
}

 

• 바리스타 객체

 - 바리스타는 메뉴항목을 이용하여 커피를 제조한다.

class 바리스타 {
  public 커피 makeCoffee(메뉴항목 menuItem) {
    커피 coffee = new 커피(menuItem);
    return coffee;
  }
}

• 커피 객체

 - 커피는 자신을 생성하기 위한 생성자를 제공하며, 커피 이름과 가격을 속성으로 가지고 생성자에서 메뉴항목에 요청을 보내 커피 이름과 같은 가격을 얻은 후 커피의 속성에 저장한다

class 커피 {
  private String name;
  private int price;
    
  public 커피(메뉴항목 menuItem) {
    this.name = menuItem.getName();
    this.price = menuItem.cost();
  }
}

 

이와 같이 인터페이스 및 구현을 해보았는데 인터페이스를 통해 실제로 상호작용 해보지 않은 채 인터페이스의 모습을 정확하게 예측하는 것은 매우 힘들다. 그러하여 설계를 간단히 끝내고 최대한 빨리 구현에 들어가며 객체의 협력 구조가 잡힌다면 그대로 코드를 구현하라

테스트 주도 설계 처럼 코드를 작성해가면서 협력의 전체적인 밑그림을 그리며 협력을 설계하라

 

코드와 세 가지 관점

맨 위에서 언급한대로 코드는 개념, 명세, 구현 관점을 모두 제공해야 한다.

1. 개념관점

 - 소프트웨어 클래스와 도메인 클래스 사이의 의미적 간격이 좁으면 좁을수록 기능을 변경하기 위해 찾아야 하는 코드의 양도 줄어든다.

 

2. 명세관점

 - 클래스의 인터페이스를 바라보는 것

 - 인터페이스를 수정하면 모든 객체에게 영향을 미치기 때문에 최대한 변화에 안정적인 인터페이스를 만들기 위해 구현과 관련된 세부사항이 드러나지 않게 해야 한다.

 

3. 구현관점

  - 클래스의 내부 구현을 바라보는 것

  - 메서드의 구현과 속성의 변경은 원칙적으로 외부의 객체에게 영향을 미쳐서는 안된다.

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

- 적절한 객체를 선택하는 것은 도메인에 대한 지식을 기반으로 코드의 구조와 의미를 쉽게 유추할 수 있게 한다. (개념 관점)

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

 - 인터페이스가 구현 세부 사항을 노출하기 시작하면 아주 작은 변동에도 전체 협력이 요동취는 취약한 설계가 된다 (명세 관점)

 

결론

 - 클래스를 명세 관점과 구현 관점으로 명확하게 나누어 볼 수 있어야 하며, 개념, 명세, 구현 관점으로 클래스를 바라 볼 수 있어야 한다.

 

해당 내용은 객체지향의 사실과 오해 를 읽으며 제가 이해한 내용으로 정리한 내용입니다.

자세한 내용과 적확한 내용은 책을 읽어주시길 바랍니다. (책을 읽는것을 추천드립니다)

 

책 : https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=60550259

 

객체지향의 사실과 오해

위키북스 IT Leaders 시리즈 23권. 객체지향이란 무엇인가? 이 책은 이 질문에 대한 답을 찾기 위해 노력하고 있는 모든 개발자를 위한 책이다.

www.aladin.co.kr