객체 지향 설계 속의 상호 연관된 관점 3가지
개념관점
도메인 내의 개념, 개념 사이의 관계 표현
명세관점
소프트웨어의 관점→객체의 인터페이스 관점
객체가 협력을 위해 무엇을 할 수 있는가?
ex:인터페이스와 구현 분리
구현 관점
객체들이 책임을 수행하기 위해 필요한 동작하는 코드 작성
객체의 책임을 어떻게 수행할 것인가?
클래스는 세 가지 관점을 모두 수용하며, 이 관점을 식별할 수 있도록 깔끔하게 분리해야 한다
역할, 책임, 협력을 이용한 인터페이스 식별
- 협력 참여를 위해 객체가 수신해야 하는 메시지
- 메시지가 모여 인터페이스 구성
→인터페이스 관점(명세 관점)
커피 전문점 도메인
커피 전문점에서 커피 주문하기를 객체 협력 관계로 해석해보자
커피 전문점이라는 세상
커피 전문점을 구성하는 요소에는 무엇이 있는가?
- 메뉴판
- 아메리카노
- 카푸치노
- 카라멜 마끼아또
- 에스프레소
메뉴판은 객체, 메뉴 항목 또한 객체다
- 손님
- 메뉴판 객체 속의 메뉴 항목을 선택
- 바리스타 객체에게 전달
- 바리스타
- 메뉴항목을 전달받아
- 커피를 제조
객체들 간의 관계는 어떤가?::도메인 모델
- 손님↔메뉴판
- 손님↔바리스타
- 바리스타↔커피
타입은 분류를 위해 사용된다
동일한 상태, 동일한 행동을 가질 수 있는 객체는 같은 타입의 인스턴스다
손님→손님타입
바리스타→바리스타 타입
아메리카노, 에스프레소…→커피 타입
메뉴판→메뉴판 타입
메뉴항목→메뉴항목 타입
*메뉴항목은 메뉴판에 하나의 단위로 움직인다 = 메뉴판 객체에 포함되어 있다(합성 관계)
*손님 타입은 메뉴판 타입을 알아야 하지만, 손님이 메뉴판을 포함하지 X (연관 관계)
*바리스타 타입은 커피 타입을 알아야 하지만, 바리스타가 커피 타입을 포함하지 X (연관 관계)
설계하고 구현하기::소프트웨어 관점
|| 커피를 주문하기 위한 협력 찾기 ||
객체지향 설계의 첫 번째 목표는 훌륭한 협력을 설계하는 것이다
→ 메시지가 객체를 선택하게 해야 한다
먼저 메시지가 있고, 수신하는 객체는 메시지를 처리할 책임을 맡음
메시지는 객체가 외부에 제공하는 공용 인터페이스에 포함됨
메세지를 찾아보자
커피
를주문하라
- 어떤 정보(인자)를 전달해야 할까? : 커피(메뉴 항목)
- 처리할 수 있는 객체는 누굴까? :손님
- 무엇을 반환해야 할까? :없음
- 메시지를 수행하기 위해 어떤 단계가 있을까?
- 메뉴항목을 받기
- 제조된 커피를 받기
- 손님이 커피를 주문하는 도중에 스스로 할 수 없는 일은?(다른 객체에게 요청해야 하는 일)
1️⃣메뉴 항목을 알 수 없다
→ 손님의 일부가 아니므로!!
→ 손님 객체에서 외부로 메시지 전송
메뉴
이름을찾아라
메시지 발견!!
2️⃣커피를 제조할 수 없다 →커피는 손님의 일부가 아니므로
→손님 객체에서 외부로 메시지 전송
커피
를제조하라
메시지 발견!!
메뉴 항목
을찾아라
- 어떤 정보(인자)를 전달해야 할까? :메뉴 이름
- 처리할 수 있는 객체는 누굴까? :메뉴판
- 무엇을 반환해야 할까? :메뉴 항목
커피
를제조하라
- 어떤 정보를 전달해야 할까? : 메뉴 항목
- 처리할 수 있는 객체는 누굴까? :바리스타
- 무엇을 반환해야 할까? :커피
- 바리스타가 커피를 제조하는 도중에 스스로 할 수 없는 일은?
:커피를
생성
하기→ 생성자로 주입??- 바리스타는 커피를 만들기 위한 지식(상태)과 기술(행동)을 가진다
- ex: Coffee americano = new Coffee(shot, water);
- ex:
public class Barista{ makeCoffee(Coffee coffee) return coffee; }
|| 인터페이스 정리하기 ||
수신 가능한 메세지만 추리면 객체의 인터페이스로 만들 수 있다
🚀 객체를 포괄하는 타입 정의
객체의 행위를 기준으로 분류했을 때, 해당 객체는 어떤 타입일까? →일반적으로 class로 구현
🚀 해당 타입의 인터페이스에 식별된 오퍼레이션을 추가
정리 예시
class Customer{
public void order(String menuName)
}
...
인터페이스 정리 코드 더보기
//손님: 주문하라(제품) 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 타입 인터페이스인
Customer.class
//손님: 주문하라(제품)
class Customer{
public void order(String menuName)
}
order()를 실행하려면 사실 Menu와 Barista에게 메시지를 보내는 과정이 필요했다
🚀어떻게 접근해야 할까?→참조 얻어오기
order{} 메서드의 parameter로 받아와보자
//손님: 주문하라(제품)
class Customer{
//바리스타랑 메뉴판이 있어야 주문을 하죠...
public void order(String menuName, Menu menu, Barista, barista){}
}
🚀구현을 채워보자
//손님: 주문하라(제품)
class Customer{
//바리스타랑 메뉴판이 있어야 주문을 하죠...
public void order(String menuName, Menu menu, Barista, barista){
//메뉴이름을 받아와서
MenuItem menuItem = menu.choose(name)
//바리스타한테 만들라고 했어요
Coffee coffee = barista.makeCoffee(menuItem);
...
}
}
전체 구현 코드
-
Customer.class
//손님: 주문하라(제품) class Customer{ //바리스타랑 메뉴판이 있어야 주문을 하죠... public void order(String menuName, Menu menu, Barista, barista){ //메뉴이름을 받아와서 MenuItem menuItem = menu.choose(name) //바리스타한테 만들라고 했어요 Coffee coffee = barista.makeCoffee(menuItem); ... } }
Menu.class
class MenuItem{} //메뉴 : 찾아라(메뉴이름) class Menu{ public MenuItem choose(String name){} }
Menu에 MenuItem이 포함된다
class Menu{ //MenuItem 타입의 List인 items 필드 private List<MenuItem> items;// items를 캡슐화하여 구현 //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; } }//for문의 끝 return null; } }
Barista.class
//바리스타 : 커피내려라(메뉴이름) class Barista{ public Coffee makeCoffee(MenuItem menuItem){} } // 커피 : 생성하라(메뉴이름) class Coffee{ public Coffee(MenuItem menuItem){} }
menuItem을 알아야 커피를 만들 수 있다.
커피를 만들려면 커피 참조를 받아와야한다.
이번엔
new
키워드로 받아오자//바리스타 : 커피내려라(메뉴이름) class Barista{ public Coffee makeCoffee(MenuItem menuItem){ Coffee coffee =new Coffee(menuItem); return coffee } }
Coffee.class
// 커피 : 생성하라(메뉴이름) class Coffee{ public Coffee(MenuItem menuItem){ } }
커피 타입에 아아, 라떼, 아인슈페너 등이 있다면,
각각의 객체는 커피 이름과 가격을 속성(상태)로 갖게 됨
다만, 이름과 가격은 메뉴항목에 의해 알게 되는 것!!(Domain Driven)
// 커피 : 생성하라(메뉴이름) class Coffee{ private String name; private int price; public Coffee(MenuItem menuItem){ this.name = menuItem.getName(); this.price = menuItem.getPrice(); } }
menuItem에서 가격과 이름을 알게 되어야 하므로, menuItem 클래스를 이제야 정의할 수 있게 됨
MenuItem.class
public class MenuItem{ private String name; private int price; public MenuItem(String name, int price){ this.name = name; this.price = price } //@Getter 역할 public int getPrice() { return price;//this.price 대신 그냥 price를 한다면?? } public String getName() { return this.name; } }
-
코드와 세 가지 관점
|| 앞서 작성한 코드 돌아보기
- 개념 관점
Customer, Menu, MenuItem,Batista, Coffee 클래스
카페 도메인을 구성하는 개념과 관계 반영
→도메인 개념, 특성을 최대한 수용하면
- 변경 관리 eady
- 유지보수성 향상
ex: 커피 제조 과정 변경: Barista를 변경해야겠구나!
- 명세 관점
클래스의 인터페이스에 집중
public 메서드=다른 클래스가 협력할 수 있는 공용 인터페이스
인터페이스: 외부의 객체가 해당 객체에 접근 가능한 유일한 부분→ 수정 시 협력하는 모든 객체에게 영향→수정하기 어려움
변화에 안정적인 인터페이스를 만드려면?
→구현과 관련된 세부사항이 드러나지 않게 한다
메시지만 뽑아낸 것을 의미한다
- 구현 관점
클래스의 내부 구현에 집중
클래스의 메서드, 속성은 공용 인터페이스가 아님
→ (원칙적으로) 외부 객체에 영향을 미쳐서는 안 됨
=캡슐화되어야 함
구현 과정에서 인터페이스가 객체 내부 속성(name, price등)에 아무 힌트도 주지 않았던 걸 떠올려보자
||도메인 개념을 참조하는 이유
도메인 개념 안에서 적절한 객체를 선택= 도메인 지식 기반으로 코드, 구조와 의미를 쉽게 유추할 수 있게 함 →시스템 유지보수성 올라감
변경 발생 시 인터페이스만 수정하여 유지보수 easy
'Computer Science > OOP' 카테고리의 다른 글
JPA 연관관계 매핑, 상속관계 매핑 (0) | 2022.09.04 |
---|---|
캡슐화는 정보 은닉이 아니다(번역) encapsulation, cohesion, coupling (2) | 2022.07.11 |
For문 중괄호 생략 이유, For - while 문 구조 정리 (2) | 2022.06.25 |