Computer Science/OOP

객체지향 설계의 3가지 관점과 코드예제

TLdkt 2022. 8. 27. 14:49
728x90
반응형
07 함께 모으기

객체 지향 설계 속의 상호 연관된 관점 3가지

개념관점

도메인 내의 개념, 개념 사이의 관계 표현

명세관점

소프트웨어의 관점→객체의 인터페이스 관점

객체가 협력을 위해 무엇을 할 수 있는가?

ex:인터페이스와 구현 분리

구현 관점

객체들이 책임을 수행하기 위해 필요한 동작하는 코드 작성

객체의 책임을 어떻게 수행할 것인가?

클래스는 세 가지 관점을 모두 수용하며, 이 관점을 식별할 수 있도록 깔끔하게 분리해야 한다

역할, 책임, 협력을 이용한 인터페이스 식별

  • 협력 참여를 위해 객체가 수신해야 하는 메시지
  • 메시지가 모여 인터페이스 구성

→인터페이스 관점(명세 관점)

커피 전문점 도메인

커피 전문점에서 커피 주문하기를 객체 협력 관계로 해석해보자

💡
동네의 작은 커피 전문점, 세 가지 메뉴가 적힌 메뉴판을 보고 손님이 커피를 주문하면, 바리스타가 커피를 제조해준다

커피 전문점이라는 세상

커피 전문점을 구성하는 요소에는 무엇이 있는가?
  • 메뉴판
    • 아메리카노
    • 카푸치노
    • 카라멜 마끼아또
    • 에스프레소

메뉴판은 객체, 메뉴 항목 또한 객체다

  • 손님
    • 메뉴판 객체 속의 메뉴 항목을 선택
    • 바리스타 객체에게 전달
  • 바리스타
    • 메뉴항목을 전달받아
    • 커피를 제조
객체들 간의 관계는 어떤가?::도메인 모델
  • 손님↔메뉴판
  • 손님↔바리스타
  • 바리스타↔커피

타입은 분류를 위해 사용된다

동일한 상태, 동일한 행동을 가질 수 있는 객체는 같은 타입의 인스턴스

손님→손님타입

바리스타→바리스타 타입

아메리카노, 에스프레소…→커피 타입

메뉴판→메뉴판 타입

메뉴항목→메뉴항목 타입

*메뉴항목은 메뉴판에 하나의 단위로 움직인다 = 메뉴판 객체에 포함되어 있다(합성 관계)

*손님 타입은 메뉴판 타입을 알아야 하지만, 손님이 메뉴판을 포함하지 X (연관 관계)

*바리스타 타입은 커피 타입을 알아야 하지만, 바리스타가 커피 타입을 포함하지 X (연관 관계)

💡
도메인 모델 작성 TIP! 1. 어떤 타입이 도메인을 구성하는가? 2. 타입들 간에 어떤 관계가 존재하는가? 합성 or 연관관계까지 세분화할 필요 X
설계하고 구현하기::소프트웨어 관점

|| 커피를 주문하기 위한 협력 찾기 ||

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

메시지가 객체를 선택하게 해야 한다

먼저 메시지가 있고, 수신하는 객체는 메시지를 처리할 책임을 맡음

메시지는 객체가 외부에 제공하는 공용 인터페이스에 포함

  • 메세지를 찾아보자
    • 커피주문하라
      • 어떤 정보(인자)를 전달해야 할까? : 커피(메뉴 항목)
      • 처리할 수 있는 객체는 누굴까? :손님
      • 무엇을 반환해야 할까? :없음
      • 메시지를 수행하기 위해 어떤 단계가 있을까?
        • 메뉴항목을 받기
        • 제조된 커피를 받기
      • 손님이 커피를 주문하는 도중에 스스로 할 수 없는 일은?(다른 객체에게 요청해야 하는 일) 1️⃣메뉴 항목을 알 수 없다 → 손님의 일부가 아니므로!! → 손님 객체에서 외부로 메시지 전송
        • 메뉴 이름찾아라 메시지 발견!!

        2️⃣커피를 제조할 수 없다 →커피는 손님의 일부가 아니므로

        →손님 객체에서 외부로 메시지 전송

        • 커피제조하라 메시지 발견!!

    • 메뉴 항목찾아라
      • 어떤 정보(인자)를 전달해야 할까? :메뉴 이름
      • 처리할 수 있는 객체는 누굴까? :메뉴판
      • 무엇을 반환해야 할까? :메뉴 항목
      💡
      참고 객체지향 세계에서는 <이상한 나라의 앨리스> 속 세계처럼 모든 객체가 능동적이고 자율적이다. 따라서 메뉴판이라는 객체는 스스로 메뉴 항목을 찾을 수 있다 즉, 객체는 단순히 현실의 은유일 뿐 현실과 똑같이 설계할 필요가 없다.
    • 커피제조하라
      • 어떤 정보를 전달해야 할까? : 메뉴 항목
      • 처리할 수 있는 객체는 누굴까? :바리스타
      • 무엇을 반환해야 할까? :커피
      • 바리스타가 커피를 제조하는 도중에 스스로 할 수 없는 일은? :커피를 생성하기→ 생성자로 주입??
        • 바리스타는 커피를 만들기 위한 지식(상태)과 기술(행동)을 가진다
        • ex: Coffee americano = new Coffee(shot, water);
        • ex:
        public class Barista{
        	makeCoffee(Coffee coffee)
        		return coffee;
        }
💡
메세지를 찾을 때 고려해야 할 것
  • 어떤 정보를 전달해주어야 할까?→parameter
  • 누가 처리할 수 있을까?→Type
  • 해당 객체는 무엇을 반환해야 할까?→return
  • 그 과정에서 객체 스스로 할 수 없는 행위는 무엇일까?→ new message=new Interface

|| 인터페이스 정리하기 ||

수신 가능한 메세지만 추리면 객체의 인터페이스로 만들 수 있다

💡
객체가 어떤 메세지를 수신할 수 있다

= 해당 객체의 인터페이스 안에 메시지에 해당하는 오퍼레이션이 존재한다

ex:손님 객체의 인터페이스: “제품을 주문하라”

바리스타 객체 인터페이스: ”커피를 제조하라”

커피 객체 인터페이스: ”생성하라”

🚀 객체를 포괄하는 타입 정의

객체의 행위를 기준으로 분류했을 때, 해당 객체는 어떤 타입일까? →일반적으로 class로 구현

🚀 해당 타입의 인터페이스에 식별된 오퍼레이션을 추가

정리 예시

class Customer{
	public void order(String menuName)
}
...

  • 인터페이스 정리 코드 더보기
    💡
    (협력을 통해 구별된) 타입의 오퍼레이션(메세지)는 외부에서 접근 가능공용 인터페이스의 일부 → public으로 선언해야 한다 ex: 손님이 요청하는 “메뉴를 찾아라”→ 메뉴판 수행 외부(손님)에서 접근 가능한(요청 가능한) 공용 인터페이스가 됨
    //손님: 주문하라(제품)
    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);
	...
	}
}
💡
Tip!

이처럼 구현 도중에 객체의 인터페이스는 변경될 수 있다

협력을 구상하는 단계에 너무 오래 시간을 쏟기보다, 최대한 빨리 코드를 구현해 설계가 구현 가능한지를 판단해야 한다

  • 전체 구현 코드
    • 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;
      	}	
      }
      💡
      인터페이스와 구현의 분리

      객체의 구현 세부 사항을 객체의 공용 인터페이스에 노출시키지 않으려면(for 캡슐화)

      인터페이스를 정하는 단계에서는 객체가 어떤 속성을 가지는지, 어떤 자료구조로 구현되었는지 가정하지 말자!

    • 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 클래스

카페 도메인을 구성하는 개념과 관계 반영

→도메인 개념, 특성을 최대한 수용하면

  1. 변경 관리 eady
  1. 유지보수성 향상

ex: 커피 제조 과정 변경: Barista를 변경해야겠구나!

  • 명세 관점

클래스의 인터페이스에 집중

public 메서드=다른 클래스가 협력할 수 있는 공용 인터페이스

인터페이스: 외부의 객체가 해당 객체에 접근 가능한 유일한 부분→ 수정 시 협력하는 모든 객체에게 영향→수정하기 어려움

변화에 안정적인 인터페이스를 만드려면?

→구현과 관련된 세부사항이 드러나지 않게 한다

메시지만 뽑아낸 것을 의미한다

  • 구현 관점

클래스의 내부 구현에 집중

클래스의 메서드, 속성은 공용 인터페이스가 아님

→ (원칙적으로) 외부 객체에 영향을 미쳐서는 안 됨

=캡슐화되어야 함

구현 과정에서 인터페이스가 객체 내부 속성(name, price등)에 아무 힌트도 주지 않았던 걸 떠올려보자

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

도메인 개념 안에서 적절한 객체를 선택= 도메인 지식 기반으로 코드, 구조와 의미를 쉽게 유추할 수 있게 함 →시스템 유지보수성 올라감

변경 발생 시 인터페이스만 수정하여 유지보수 easy

728x90
반응형