기존에 우리가 알던 지식을 기반으로 좀 더 객체지향스럽게 설계 및 개발하는 방법론에 대해서 알아보는 챕터였다.
소프트웨어 모듈이 가져야 할 3가지 기능
- 필요한 기능이 오류 없이 제대로 동작하는 것.
- 변경에 용이한 코드.
- 누구 읽어도 이해하기 쉬운 코드; 동작이 우리의 상식(?) 또는 예상에서 크게 벗어나지 않는 코드
절차지향과 객체지향
데이터와 프로세스 역할을 분리해서 프로그래밍 하는 방식을 절차지향 프로그래밍이라고 부른다.
프로세스는 데이터의 의존하고 있기 때문에 데이터의 변경은 단순히 데이터만 변경하는 것이 아니라 프로세스까지 변경을 해야한다. 즉, 데이터의 변경으로 인한 영향을 지역적으로 고립시키기 어렵다는 것이다. 그렇다면 어떻게 해야 변경하기 쉬운 설계를 할 수 있을까?
" 변경하기 쉬운 설계는 한번에 하나의 클래스만 변경할 수 있는 설계를 하는 것이다. "
그렇게 하려면 어떻게 해야 하는가?
첫번째 한 곳에서 모든 처리가 실행 되는 것을 적절한 단계로 나눈다.
두번째로는 나눠진 프로세스를 해당 프로세스와 연관이 있는 데이터가 있는 클래스로 옮기는 것이다.
즉, 데이터와 프로세스를 한 곳에 두는 것이다. 데이터와 프로세스가 동일한 모듈(클래스) 내부에 위치하도록 프로그래밍하는 방식을 객체지향 프로그래밍(Object-Oriented Programming)이라고 부른다. 객체지향 설계의 핵심은 캡슐화를 이용해서 의존성을 적절히 관리함으로써 객체 간의 결합도를 낮추는 것이다.
그렇다면 무조건 객체지향은 좋고 절차지향은 나쁘다라는 의미일까?
그건 절대적으로 아니다. Spring Boot 프레임워크에서 개발을 하다보면 우리는 DTO와 Entity라는 객체을 정의해서 사용한다. DTO와 Entity는 단순히 데이터를 가지고 있는 역할만 한다. 이 데이터를 처리하는 프로세스는 다른 객체(비즈니스 로직을 수행하는)가 가지고 있다. 그래서 우리는 객체지향과 절차지향을 적절한 혼합을 통해서 개발하고 있다는 사실을 잊지 않았으면 좋겠다.
객체지향 프로그래밍
객체지향의 목표; 오늘은 오류 없이 잘 동작하고 내일은 쉽게 변경할 수 있는 코드
개발을 시작하는 시점에 구현에 필요한 모든 요구사항을 수집하는 것은 불가능하다. 만약, 모두 수집했다고 하더라도 개발 진행중에 요구사항은 변경된다. 그래서 우리가 해야 하는 일은 아래 조건을 부합하는 설계를 하는 것이다.
1. 첫번째, 오늘은 필요한 기능을 오류 없이 잘 실행되는 코드를 완성하는 것
2. 두번째, 오늘 작성한 코드를 내일 쉽게 변경할 수 있는 코드를 작성하는 것
이것이 객체지향의 목표이다. 객체지향은 의존성을 효율적으로 통제할 수 있는 다양한 방법을 제공함으로써 요구사항 변경에 좀 더 수월하게 대응할 수 있는 기능들을 제공한다. 그래서 과거의 다른 방법들 보다 객체지향을 선택하고 있는 이유 중 하나 일 것이다.
객체 지향하기: 깔끔한 설계
우리는 OOP방식으로 애플리케이션을 설계할 때, 우리는 무엇부터 생각하는가?
우리는 가장 먼저 어떤 클래스가 필요한지 고민하고 어떤 상태와 행동이 필요할지 고민한다. 그런데 이것은 OOP의 본질과는 거리가 멀다. 객체지향은 말그대로 객체를 지향한다라는 의미이다.
그렇기 때문에 객체지향의 패러다임으로 전환하기 위해서는 클래스가 아닌 객체에 초점을 맞춰야 한다. 이를 위해서 우리는 프로그래밍을 하는 동안 아래의 2가지에 집중해야 한다.
첫번째, 어떤 객체들이 필요한지 고민해보고 나열한다. 그렇게 되면 공통적인 상태와 행동을 가지는 객체들이 눈에 보인다. 클래스는 공통적인 상태와 행동을 공유하는 객체들을 추상화 해놓은 설계도 같은 것이다. 따라서 클래스의 윤곽을 잡기 위해서는 어떤 객체들이 어떤 상태와 행동을 가지는지를 먼저 결정해야 한다.
두번째, 객체를 독립적인 존재로 보는 것이 아니라, 기능을 구현하기 위해 협력하는 공동체의 일원으로 보는 것이다. 이렇게 다른 객체의 의존하고 협력하는 존재로 보게 되면 설계를 유연하고 확장 가능하게 만들어 준다.
객체들의 모양과 윤곽이 잡히면 공통된 특성과 상태를 가진 객체들을 타입(Type)으로 분류하고 이 타입을 기반으로 클래스를 구현한다. 이렇게 객체 중심적으로 생각하면 설계가 단순해지고 깔끔해 진다.
1단계: 필요한 객체
우리가 해결해야 하는 문제의 도메인을 정의 해보자.
우리는 좀 더 쉽게 온라인으로 “카페의 음료 주문 받자.”라는 문제를 해결하는 것이다. 이러한 문제를 해결하기 위해서 필요한 객체들이 무엇이 있는지 한번 생각해보고 적어보자.
객체의 의인화
우리는 현실 세계에 있는 객체(Object)를 코드라는 재료를 통해서 가상 세계의 객체로 만들어내는 역할을 수행한다.
그런데 현실 세계의 객체가 가상 세계의 객체로 넘어오면 수동적 존재였던 객체들은 모두 능동적으로 바뀌게 되고 그 객체에 생명이 깃든다. 그래서 현실 세계에서는 인간이라는 행동주체가 있어야 동작할 수 있던 객체들이 행동주체가 없이도 혼자서 동작할 수 있다. 객체를 정의하고 설계할 때 수동적인 존재가 아닌 능동적인 존재로 인식해야 한다.
2단계: 클래스 설계
- 객체들의 모양과 윤곽이 잡히면 공통된 행동과 상태를 가진 객체들을 Type으로 분류하고 이 타입을 기반으로 클래스를 추상화한다.
- 내부와 외부를 구분 짓고 어떤 것을 외부에 노출하고 어떤 것을 내부로 숨길지 경계를 잘 결정한다.
- 캡슐화를 통한 세부적인 내부 구현 숨기고, 외부와의 협력은 public인터페이스를 통해서만 한다.
- 객체 스스로가 상태를 관리하고, 판단하고, 행동하는 자율적인 객체가 되기 위해서는 외부의 간섭을 최소화 해야하기 때문에
3단계; 도구를 이용하여 변경에 유연한 클래스 구현
자율성 가진 객체
한 줄 요약
- 객체의 세부적인 내부상태는 감추고 메시지는 오직 public 인터페이스를 통해서만 주고 받아야 한다: 캡슐화
- 모든 객체는 자기 자신이 맡고 있는 일(or 책임, 기능, 문제)를 스스로 해결(처리)할 수 있어야 한다: 단일책임
- 밀접하게 연관된 작업만 수행하고 연관성 없는 기능은 다른 객체에게 위임해야 한다: 응집도
경계를 잘 짓는 것: 클래스는 내부와 외부로 구분되면 휼륭한 클래스를 설계하기 위한 핵심은 어떤 부분을 외부에 공개하고 어떤 부분을 감출지를 결정하는 것이다. 그래서 캡슐화와 접근 제어자는 객체를 두 부분으로 나눠주는 역할을 한다. 하나는 외부의 접근이 가능한 부분으로 퍼블릭 인터페이스라고 부른다. 다른 하나는 외부에서 접근하지 못하고 오직 내부에서만 접근이 가능한 부분으로 구현이라고 부른다.
객체지향의 핵심은 객체가 스스로 상태를 관리하고, 판단하고, 행동하는 자율적인 객체들의 공동체를 구성하는 것이다.
캡슐화: Encapsulation
객체 내부의 세부적인 사항(인스턴스 변수와 구현에 직접 접근;)을 감추는 것을 말한다.
캡슐화를 통해서 객체 내부의 접근을 제한하면 객체와 객체 사이의 결합도를 낮출 수 있기 때문에 비즈니스 변화에 쉽게 대응(코드 수정)할 수 있다.
이제 TicketOffice에는 오직 TicketSeller만 접근이 허용되고 Theater는 직접 접근 할 수 없다. (인스턴스 변수의 접근 제한자와 public method가 없기 때문에.)
public class Theater {
private final TicketSeller ticketSeller;
public void enter(Audience audience){
// 잘못된 코드 Case#1
// - Theater가 TicketSeller의 인스턴스 변수 ticketOffice에 직접 접근
ticketSeller.getTicketOffice().getTicket();
// 수정된 코드 Case#1
// - TicketSeller 클래스에 getTicketOffice() 삭제;
ticketSeller.sellTo(audience);
...
}
public class TicketSeller {
private TicketOffice ticketOffice;
...
// 내부 구현을 노출하는 인터페이스(=METHOD)는 삭제
// 또는 접근 제한자를 public -> private or protected로 수정
private TicketOffice getTicketOffice(){
return this.ticketOffice;
}
public void sellTo(Audience audience){
// 기존 티켓 구매하는 코드를 여기로 옮긴다.
}
}
TicketSeller는 Ticket 판매라는 하나의 책임을 가지게 되었고, TicketSeller만 TicketOffice에 있는 Ticket의 판매 수익과 현금을 이용할 수 있게 되었다. 이로써 의존성 정도 수준(느슨한 결합)을 최소한으로 가지게 되었다.
Theater는 TicketSeller가 내부에 TicketOffice 인스턴스를 포함하고 있다는 사실을 모른다.(캡슐화)
단순히 Theater는 TicketSeller의 인터페이스(public 메서드)에 의존한다.
TicketSeller#sellTo(Audience audience) 메서드를 호출하면 Ticket이 생성 됐다는 사실은 인터페이스 영역이고, TicketSeller 안에 TicketOffice 인스턴스가 있다라는 사실은 구현의 영역이다.
객체를 인터페이스(public interface)와 구현이라는 2가지 영역으로 나누고 인터페이스만을 공개하는 것은 객체 사이의 결합도를 낮추고 변경하기 쉬운 코드를 작성하기 위해 따라야 하는 가장 기본적인 설계 원칙이다.
자판기를 예로 들어보자. 우리는 자판기의 자세한 동작(or 실행 or 구현) 원리는 모른다. 하지만, 우리는 돈을 넣고 원하는 음료 버튼(인터페이스)을 누르면 음료가 나온다라는 사실만을 알고 있어도 사용하는데 지장이 없다. 만약 음료수 제조법이 바뀐다(내부 비즈니스 로직)고 해서 우리가 자판기와 소통하는데는 변화가 없다. 왜냐하면 인터페이스(동전 넣는 곳 or 음료를 선택하는 버튼)는 변화지 않기 때문이다.
내부 구현을 외부에 노출하지 않도록 코드를 수정하고 변화된 부분은 자신의 문제를 스스로 책임지고 해결하므로써 자율적인 존재가 되었다.
캡슐화와 응집도
핵심은 객체 내부의 상태를 캡슐화하고 객체 간에 오직 **메시지(public method)**를 통해서만 상호작용하도록 만드는 것이다. Theater는 TickerSeller의 내부 구현에 대해서는 전혀 알지 못한다. 단지, TicketSeller가 sellTo 메시지를 이해하고 예상되는 결과 값으로 응답할 수 있다는 사실만 알고 있다.
만약, TicketSeller가 의존하고 있는 TicketOffice의 변경이 사항이 발생하더라도 변경의 전파가 TicketSeller 내부 구현 영역까지만 퍼진다.
public class TicketSeller {
private final TicketOffice ticketOffice;
public TicketSeller(TicketOffice ticketOffice) {
this.ticketOffice = ticketOffice;
}
public void sellTo(Audience a) {
ticketOffice.plusAmount(a.buy(ticketOffice.getTicket()));
}
}
객체의 응집도를 높이는 것은 객체는 자신의 데이터를 스스로 처리하는 자율적인 존재로 만드는 것으로 시작한다. 외부의 간섭을 최대한 배제하고 메시지를 통해서만 협력하는 자율적인 객체들의 공동체를 만드는 것이 휼륭한 객체지향 설계를 얻을 수 있는 지름길인 것이다.
상속과 다형성: 변경에 유연한 코드1
상속은 공통된(or 기본적인) 상태나 행동은 부모 클래스(추상 클래스)에서 정의하고 특정한 행동(public 인터페이스, 메서드)에 대해서는 각각의 자식 객체에서 따로 정의해야하는 경우[TEMPLATE METHOD 패턴], 우리는 상속을 사용한다.
이렇게 부모 클래스(=추상클래스)를 상속 받은 자식 클래스들은 부모 클래스의 모든 public 인터페이스에 응답할 수 있다. 그래서 외부의 다른(부모-자식 관계를 맺고 있지 않는) 객체가 볼 때는 동일한 타입의 객체로 간주하게 된다.
그럼 여기서 한 가지 문제가 발생한다. A클래스는 B클래스를 의존하고 있다. 그런데 A클래스에서 B클래스에게 C메세지를 보내는데, 실제 C메세지에 대한 요청을 처리할 수 있는 메서드는 B-1클래스와 B-2클래스에 정의되어 있다는 사실이다. 즉, 클래스 사이의 의존성과 객체 사이의 의존성이 동일하지 않을 수 있다.
컴파일 시간 의존성과 실행 시간 의존성의 차이가 발생하는 경우, 우리는 클래스 코드만으로 A객체가 어떤 객체에 의존하고 있는지 알 수 없다. 단순히 B클래스와 동일한 타입의 객체에 의존하고 있다는 힌트만 가지고 있다.
정확하게 어떤 객체인지 알기 위해서는 많은 코드를 찾아봐야 한다. 이렇게 유연성과 확장성을 코드를 이해하고 디버깅하기는 점점 더 어려워 진다.
다형성: 변경에 유연한 코드2
객체지향 세계에서 객체 간의 상호작용 수단은 오직 메시지를 보내는 것이다(send to message). 메시지를 받은 객체는 적절한 메서드를 찾아서 실행하고 그 결과 값을 이용해서 응답한다. 여기서 의문이 들어야 한다. 하나의 메시지에는 하나의 메서드로 맵핑되어 있지 않나?
다형성을 사용하면 변경의 유연함과 확장성을 이점을 가져갈 수 있다. 하지만, 단점도 있다. 어떤 객체가 어떤 객체에 의존하고 있는지를 클래스만 확인해서 확인이 어렵고 디버깅 또한 어렵다.그리고 코드의 가독성도 떨어진다. 그래서 무조건 객체를 추상화하여 다형성을 적용하는 코드가 무조건 좋다고 볼 수 없다.
구현을 감싼 추상화: 변경에 유연한 코드3
추상 클래스의 추상 메서드를 통해서 public 인터페이스을 구현하지 않고 자식 클래스에게 위임함으로써 부모 객체의 구체적인 public 인터페이스에 결합되는 것을 방지하여 변경에 유연함과 확장성 가지는 설계를 구성할 수 있다.
예를 들어, Barista 객체의 책임을 정의할 때 “Barista는 아메리카노를 만든다.” 라고 정의하면 구체적인 인터페이스에 결합된다.
하지만, “Barista는 음료를 만든다.”라는 상위적 표현을 바꾸면 구체적인 상황에 결합되지 않고 폭 넓은 의미를 가질 수 있게 된다. 그래서 Barista는 Beverage에게 다양한 메시지를 보낼 수 있고, Beverage를 상속 받은 객체라면 요청 받은 메시지의 응답할 수 있을 것이다.
이렇게 추상화를 통해서 구현을 감싸는 형태로 설계를 한다면 크게 3가지 정도 이점을 얻을 수 있다.
- 비즈니스 로직을 설계 할 때, 구체적인 내용은 제외하고 일반적인 개념들만 이용하여 전체적인 큰 그림과흐름을 그리는데 효과적이다.
- 위에서도 살펴 본 것 처럼 기능 확장성이 매우 좋다. 예를 들어, 허브차를 신메뉴로 넣고 싶을 경우, Beverage 클래스를 상속받는 클래스를 하나 더 구현하면 된다.
- 추상화를 이용해서 정의한 전체 비즈니스 로직(상위 협력 흐름)을 그대로 따르게 할 수 있다. 예를 들면, JPA의 DataSource의 경우, Database와 상호작용하는 부분에 대해서 추상화로 감싸서 해당 클래스를 상속 받아 구현하게 만들어서 Database 종류에 상관 없이 동일한 인터페이스 흐름을 탈 수 있게 강제할 수 있다.
객체지향 패러다임의 본질
객체는 독립적인 존재가 아니다; 객체들의 공동체
객체는 독립적인 존재가 아니다. 그리고 하나의 객체로 하나의 애플케이션을 만든다라는 것은 어불성설이다. 객체지향 세계에서 기능 구현은 객체 간의 상호작용을 통해서만 가능하다. 객체 간의 상호작용은 오직 메시지(Send to message)를 통해서만 가능하다. “주문한 음료의 결제 금액”을 계산하고 싶을 때, Cashier는 Beverage에게 주문한 음료수의 가겨을 물어보는 message를 보낸다. message를 수신한 Beverage는 자기 스스로 적절한 method를 찾아서 수신한 메시지를 처리하고 응답한다.
위 예제처럼, 객체지향 세계에서 애플리케이션 설계란, 애플리케이션에서 제공할 기능을 완료하는데 필요한 더 작은 기능을 찾아내고 이를 객체들에게 할당하는 반복적인 과정을 통해서 객체들의 공동체를 만들어 가는 것이다.
객체로 부터 시작해야 한다; 올바른 객체 설계
“음료 주문”이라는 도메인이 있을 때, 우리는 어디서부터 설계를 시작하는가? 대부분의 자바 개발자는 클래스와 클래스에 필요한 상태(인스턴스 변수)가 무엇이 필요한지 고민하고 설계를 시작한다. 클래스부터 설계를 시작하면 변경의 유연함과 확장성을 놓칠 수 있는 설계가 나온다.
객체지향은 말 그대로 객체를 지향한다. 그래서 우리는 시스템(Application)의 목적을 중심으로 필요한 객체를 먼저 생각해보고 설계를 할 것이다. 필요한 객체들의 모양과 윤곽이 잡히면 공통적인 상태와 행동을 가지는 객체들을 타입으로 분류하고 이 타입을 기반으로 클래스를 구현한다. 이렇게 객체 중심적으로 생각하면서 설계를 하면 단순해지고 깔끔해진다.
객체의 상태와 행동은 어떻게 정해야 할까?; 협력,책임,역할
“음료 주문” 시스템에서 우리는 어떤 프로세스로 음료를 주문할 수 있을까?
- 음료를 선택한 후, 주문한다.
- 주문한 음료의 총 결제 금액을 계산하다.
- 주문한 음료를 레시피에 맞게 제조한다.
- 제조가 완료된 음료를 고객에게 전달한다.
위 프로세스대로 라면, Cashier 객체는 주문 받은 음료의 총 결제 금액과 주문 받은 음료를 Barista 객체에게 알려 줄 책임이다. 그 이유는 2번과 3번의 요구사항을 완료하기 위해서 협력하고 있기 때문이다.
객체의 행동을 결정하는 것은 그 객체가 어떤 기능을 완성하기 위해서 협력(상호작용)하고 있는지에 따라 결정된다. 협력이라는 문맥을 고려하지 않고 객체의 행동을 결정하는 것은 아무런 의미가 없는 객체를 만드는 것과 다들게 없다. 상태는 그 행동을 하는데 있어서 필요한 정보가 상태가 된다. 여기서 필요한 정보란, 단순히 행동을 처리하는데 필요한 데이터가 될 수도 있고 자신이 가지고 있지 않는 정보(상태)를 가지고 있는 객체의 정보까지 말한다.
객체의 행동과 상태 정의하기: 협력, 책임,역할
협력 → 애플리케이션의 기능을 작은 단위로 쪼개진 기능의 한 줄 요약본 책임 → 쪼개진 기능을 구현 시키기 위한(협력에 참여하기 위해서) 작은 기능(객체들이 수행하는 행동)
객체지향 원칙을 따르는 애플리케이션의 제어 흐름은 어떤 하나의 객체에 의해 통제되지 않고 다양한 객체들 사이에 균형 있게 분배되는 것이 일반적이다. 객체들은 요청의 흐름에 따라서 자신에게 분배된 로직(쪼개진 작은 기능)을 실행하면서 애플리케이션의 전체 기능을 완성한다.
다양한 객체들이 “음료 주문”이라는 기능을 완성하기 위해 메시지를 서로 주고 받으면서 상호작용을 한다. 이 처럼 객체들이 애플리케이션의 기능을 구현하기 위해 더 작은 단위로 쪼개진 기능을 수행하는 상호작용을 협력이라고 부르고, 객체가 협력에 참여하기 위해 수행하는 로직은 책임이라고 부른다. 객체들이 협력 안에서 수행하는 책임들이 모여 객체가 수행하는 역할을 구성한다.
협력; Context
한 줄 요약
- 애플리케이션의 기능을 구현하기 위해서 쪼개진 기능 정의서
객체가 가질 수 있는 상태와 행동을 어떤 기준으로 결정해야 할까? 그것은 바로 그 객체가 어떤 협력(목표)에 참여하고 있는가?로 결정할 수 있다. Cahier라는 객체는 “음료 주문”을 위한 협력에 참여하고 있고 그 안에서 **“음료 주문을 처리”**는 책임(작은 기능, 크다 작다에 “작은”을 의미하는게 아니라, 주요 기능을 완성하기 위해서 쪼개진 기능을 말함.)을 지고 있다. 그래서 객체의 행동을 결정 하는 것은 객체가 참여하고 있는 협력에서 결정되고, 상태는 객체가 행동하는데 있어서 필요한 정보에 의해서 결정된다.
그래서 협력은 일종의 문맥이다. “음료 주문”이라는 문맥을 토대로 협력에 필요한 객체들을 설계 할 수 있도록 도와준다.
즉, 애플리케이션의 기능은 여러 개의 협력이 모여서 애플리케이션의 기능을 완성시킨다.(= 객체들이 모여 공동체를 만들고 공동체에서 서로 상호작용하여 작은 기능들을 만들고, 그 작은 기능이 모여 애플리케이션의 기능을 완성 시킨다.)
책임; 하는 것과 아는 것
한 줄 요약
- 협력에 참여하기 위해서 객체가 맡고 있는 전체 기능
협력에 참여하기 위해 객체가 수행하는 행동을 책임이라고 부른다.
책임이란, 객체에 의해 정의되는 응집도(행동과 관련된 상태를 한 클래스에 모아 놓는 것) 있는 행위의 집합으로, 객체가 유지해야 하는 정보(상태)와 수행할 수 있는 행동(메서드)에 대해 추상적으로 서술한 문장이다.
그래서 책임과 메시지의 크기는 다르다. 책임은 객체가 수행할 수 있는 행동을 종합적이고 간략하게 서술하기 때문에 메시지 보다 추상적이고 개념적으로 더 크다.
객체의 구성은 “객체가 무엇을 할 수 있는가?”와 “객체가 무엇을 알고 있는가?”로 구성되어 있다. 객체의 책임을 크게 “하는 것(doing)”과 “아는 것(knowing)”의 두 가지 범주로 나누어 세분화 하고 있다.
하는 것
- 객체를 생성하거나 계산을 수행하는 등의 스스로 하는 것
- 다른 객체에게 메시지를 보내는 것
- 다른 객체의 활동을 제어하고 조절하는 것 → Beverage의 레시피
아는 것
- 행동하기 위해서 필요한 정보
- 관련된 객체에 대해서 아는 것
- 자신이 유도하거나 계산할 수 있는 것에 관해 아는 것
Cashier가 “주문을 받는다.”라는 것은 하는 것과 관련된 책임이다. 가격을 어떻게 계산해야하는지에 대해서는 아는 것에 대한 책임이다.
Beverage의 “가격 계산”과 “레시피”는 하는 것에 대한 책임이고, 할인 정책 적용에 대한 부분은 아는 것에 대한 책임이다.
Barista의 “음료 제조”는 하는 것에 대한 책임이고, “음료 레시피”에 대한 부분은 아는 것에 책임이다.
Cashier가 “order”라는 메세지를 수신하고 Beverage를 인스턴스 변수로 포함하고 있는 이유는 협력 안에서 “주문 받은 음료의총 결제 금액”를 계산할 책임을 가지고 있기 때문이다. Beverage는 calculatedBeverage 메시지를 수신할 수 있고, price와 discountPolicy를 속성으로 가지는 이유는 협력 안에서 가격을 계산할 책임을 할당 받았기 때문이다. 이 처럼 협력 안에서 객체에게 할당한 책임이 public 인터페이스와 내부의 속성을 결정한다.
객체는 자신이 맡은 책임을 수행하기 위해서 필요한 정보를 알고 있을 책임이 있다. 또한 객체 자신이 할 수 없는 작업을 도와줄 객체를 알고 있을 책임을 가지고 있다.
'CS' 카테고리의 다른 글
디자인패턴 기반으로 알아보는 SOLID - (2) (6) | 2023.10.31 |
---|---|
디자인패턴 기반으로 알아보는 SOLID (2) | 2023.10.17 |
컴퓨터특강 1주차 : Clean Code (1) | 2023.09.18 |
[가상면접 사례로 배우는 대규모 시스템 설계 기초]사용자 수에 따른 규모 확장성사용자 수에 따른 규모 확장성 (0) | 2023.09.04 |