목차
- 05 책임과 메시지
- 자율적인 책임
- 설계의 품질을 좌우하는 책임
- 자신의 의지에 따라 증언할 수 있는 자유
- 너무 추상적인 책임
- ‘어떻게’가 아니라 ‘무엇’을
- 책임을 자극하는 메시지
- 메시지와 메서드
- 메시지
- 메서드
- 다형성
- 유연하고 확장 가능하고 재사용성이 높은 협력의 의미
- 송신자와 수신자를 약하게 연결하는 메시지
- 메시지를 따라라
- 객체지향의 핵심, 메시지
- 책임-주도 설계 다시 살펴보기
- What/Who 사이클
- 묻지 말고 시켜라
- 메시지를 믿어라
- 객체 인터페이스
- 인터페이스
- 메시지가 인터페이스를 결정한다
- 공용 인터페이스
- 책임, 메시지, 그리고 인터페이스
- 인터페이스와 구현의 분리
- 객체 관점에서 생각하는 방법
- 구현
- 인터페이스와 구현의 분리 원칙
- 캡슐화
- 책임의 자율성이 협력의 품질을 결정한다
- 자율적인 책임
05 책임과 메시지
서문
- 자신만의 명확한 책임을 부여받지 않은 현실의 사람들은 ‘책임감 분산’으로 인해 주어진 책임을 무시하는 경향이 있다.
- 객체도 사람과 마찬가지로 명확한 책임과 역할을 부여받을 때 좋은 협력자가 될 수 있다.
자율적인 책임
설계의 품질을 좌우하는 책임
- 객체는 요청을 수신했을 때에만 행동을 수행한다.
- 따라서 각 객체에게 적절한 책임을 할당해야 한다.
- 애플리케이션의 품질을 높이려면 모든 객체가 스스로의 의지로 판단하고 행동하는 자율성을 가져야 한다.
자신의 의지에 따라 증언할 수 있는 자유
- 모자 장수의 책임은 증언하는 것이다.
- 왕의 요청 방식에 따라, 자율성을 가지고 증언하거나 증언 방식이 왕에게 통제될 수 있다.
- 모자 장수에게 자율성을 주는 방식
- 증언하라
- 모자 장수에게 상세하게 요청하는 방식
- 목격했던 장면을 떠올려라
- 떠오르는 기억을 시간 순서대로 재구성하라
- 말로 간결하게 표현하라
- 모자 장수에게 자율성을 주는 방식
- 왕의 요청 방식에 따라, 자율성을 가지고 증언하거나 증언 방식이 왕에게 통제될 수 있다.
- 왕의 요청
public class King implements Judge {
private List<Object> baseOfJudgement;
@Override
public List<String> progressTrial(Witness witness) {
baseOfJudgement = new ArrayList<>();
// 1. 자율 보장
baseOfJudgement.add(witness.testify());
// 2. 책임 지시
((HatSeller) witness).recall();
((HatSeller) witness).reorganize();
baseOfJudgement.add(((HatSeller) witness).conciselyExpress());
return decide(baseOfJudgement);
}
private List<String> decide(List<Object> baseOfJudgement) {
System.out.println(baseOfJudgement);
// 1. 자율 보장 판단 근거 : [[contentsOfWitness with explanation(has more contents than interpretation)]]
// 2. 책임 지시 판단 근거 : [[[contentsOfWitness with interpretation(reduced)](with simple expressions)]]
baseOfJudgement.add("resultOfJudgement");
return baseOfJudgement.stream().map(Object::toString).toList();
}
}
- 모자 장수의 응답
public class HatSeller implements Witness {
List<String> memory = new ArrayList<>();
List<String> memo = new ArrayList<>();
List<String> recalledMemory;
List<String> reorganizedMemory;
List<String> conciseMemory;
@Override
public void witness(String contentsOfWitness) {
memory.add(contentsOfWitness + interpret(contentsOfWitness));
memo.add(contentsOfWitness + explain(contentsOfWitness));
}
private String interpret(String contents) {
return " with interpretation";
}
private String explain(String contents) {
return " with explanation(has more contents than interpretation)";
}
@Override
public List<String> testify() {
if (memo.get(memo.size() - 1).length() > memory.get(memory.size() - 1).length()) {
return memo;
}
return memory;
}
public void recall() {
recalledMemory = new ArrayList<>();
recalledMemory.add(memory.get(memory.size() - 1));
}
public void reorganize() {
reorganizedMemory = recalledMemory;
Collections.sort(reorganizedMemory);
}
public List<String> conciselyExpress() {
for (int i = 0; i < reorganizedMemory.size(); i = i + 2) {
conciseMemory = new ArrayList<>();
conciseMemory.add(reorganizedMemory.get(i) + "(reduced)");
}
return selectExpressions(conciseMemory);
}
private List<String> selectExpressions(List<String> conciseMemory) {
List<String> selectedExpressions = new ArrayList<>();
selectedExpressions.add(conciseMemory + "(with simple expressions)");
return selectedExpressions;
}
}
- 재판 결과
public class TrialApplication {
public static void main(String[] args) {
Witness witness = new HatSeller();
witness.witness("contentsOfWitness");
Judge judge = new King();
System.out.println(judge.progressTrial(witness));
// 1. 자율 보장 결과 : [[contentsOfWitness with explanation
(has more contents than interpretation)], resultOfJudgement]
// 2. 책임 지시 결과 : [[[contentsOfWitness with interpretation(reduced)]
(with simple expressions)], resultOfJudgement]
}
}
- 자율을 보장하는 경우 왕은 판단 근거로 더 자세한 목격담을 들을 수 있으며, 이를 통해 상세한 판결을 내릴 수 있다.
- 재판 애플리케이션의 사용자는 더 높은 품질의 실행 결과를 얻게 된다.
- 책임을 지시하는 경우 왕은 생략된 내용의 목격담만을 들을 수 있으며, 판결 근거가 부실해진다.
- 재판 애플리케이션의 사용자는 더 낮은 품질의 실행 결과를 얻게 된다.
- 객체에게 명확한 책임을 부여하면서도 자율성을 보장하면 좋은 애플리케이션을 설계할 수 있다.
너무 추상적인 책임
- 자율성을 보장하려고 너무 추상적인 책임을 부여하는 것도 문제가 될 수 있다.
- 상황에 따라 적합한 책임의 수준을 설정할 수 있는 개발자의 안목이 필요하다.
‘어떻게’가 아니라 ‘무엇’을
- 왕의 두 번째 방식의 요청처럼 ‘어떻게’ 해야 하는지 세세하게 언급하면 모자 장수의 자율성이 제한된다.
- 요청 시 ‘무엇’에 집중하면 자율적인 객체 간 협력을 구성할 수 있다.
책임을 자극하는 메시지
- 객체는 요청 메시지를 수신할 때만 행동하므로, 메시지는 객체 간 협력에 있어 핵심적인 역할을 한다.
메시지와 메서드
메시지
- 객체는 메시지를 통해서만 다른 객체에 접근할 수 있다.
- 메시지는 수신자(대상 객체 참조 변수명)과 메시지 이름(메서드명), 메시지의 인자(매개변수, parameter)로 구성되어 있다.
- 왕은 메시지를 보낼 대상이 증인이라는 사실을 인지해야 한다.
- 왕은 ‘증언하라’라는 메시지 이름을 통해 증인(모자 장수)에게 접근한다.
- 해당 과정에서 필요하다면 (어제, 왕국)이라는 인자를 함께 전달할 수 있고. 모자 장수는 추가 정보를 활용할 수 있게 된다.
- 객체참조변수명.메서드명(매개변수1, 매개변수2) 형태로 전송한다.
- 예) 증인.증언하라(어제, 왕국)
witness.testify(LocalDate.*now*().minusDays(1), Location kingdom);
- 수신자는 본인이 메시지를 처리할 수 있는지 확인 후, 처리할 수 있다면 반드시 수행해야 할 책임을 갖는다. 즉, 처리 가능한 요청에 대해 거절할 수 없다.
- 객체는 스스로 책임을 처리하지 않는다. 메시지를 통해 요청을 받았을 때만 처리한다.
- 객체는 자신의 책임을 자유롭게 처리할 수 있으며, 다른 객체는 해당 과정을 확인하거나 간섭할 수 없다.
- 따라서 올바른 응답값만 반환할 수 있다면 책임 수행 방법을 자유롭게 변경해도 외부에서 인지하거나 외부에 영향을 미치지 않는다.
- 이를 통해 내⋅외부의 철저한 분리가 이루어진다.
- 객체 간 결합도가 감소한다.
메서드
- 메서드는 메시지를 처리하기 위해 내부에서 선택하는 방법이다.
- 메시지 수신의 처리 순서는 다음과 같다.
- 메시지를 처리할 수 있는지 확인한다.
- 메시지를 처리하기 위해 사용할 메서드를 선택한다.
- 메시지를 처리한다.
- 요청에 대한 처리 결과(응답값)를 반환한다.
- 객체지향 언어의 핵심적 특징 중 하나는 컴파일 시간이 아니라 실행 시간에 수행 메서드를 선택할 수 있다는 것이다.
public class TrialApplication {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String witnessName = sc.next();
Witness witness = witnessName.equals("HatSeller") ? new HatSeller()
: witnessName.equals("Cook") ? new Cook()
: witnessName.equals("Alice") ? new Alice()
: null;
witness.witness("contentsOfWitness");
Judge king = new King();
System.out.println(king.progressTrial(witness));
}
}
- 재판 애플리케이션의 사용자는 실행 시간에 증언을 수행할 객체를 선택할 수 있으며, HatSeller, Cook, Alice는 각자의 메서드를 통해 수신된 요청을 처리한다.
다형성
- 다형성은 동일한 메시지를 수신해도 객체마다 다르게 반응하는 것이다.
- 메시지는 ‘무엇’이 실행될지는 결정하지만 ‘어떻게’ 실행할 것인지는 결정할 수 없다.
- 따라서 각 객체들은 수신된 메시지를 자율적인 방식으로 처리할 수 있다.
- 모자 장수는 기억 상기와 메모 참조 중 하나의 방법을 선택해 진술한다.
- 요리사는 항상 메모를 이용해서 진술한다.
public class Cook implements Witness { List<String> memory = new ArrayList<>(); List<String> memo = new ArrayList<>(); @Override public void witness(String contentsOfWitness) { memo.add(contentsOfWitness); } @Override public List<String> testify() { return memo; } }
3. 앨리스는 사건을 직접 목격하지 않고 모자 장수에게 목격하도록 요청한다. 이후 모자 장수에게 전해 들은 내용을 토대로 진술한다.public class Alice implements Witness { List<String> memory = new ArrayList<>(); @Override public void witness(String contentsOfWitness) { Witness witness = new HatSeller(); witness.witness(contentsOfWitness); hear(witness); } @Override public List<String> testify() { return memory; } private void hear(Witness witness) { memory = witness.testify(); } }
- 왕은 해당 과정이나 수신자의 종류를 알 수 없으며, 원하는 결과만 얻으면 되기 때문에 신경 쓰지 않는다.
- 따라서 각 객체들은 수신된 메시지를 자율적인 방식으로 처리할 수 있다.
- 다형성을 반대로 표현하면, 서로 다른 각 객체들이 동일한 책임을 가진다는 것을 의미한다.
- 다형성은 수신자의 종류를 캡슐화하여 설계의 유연성과 재사용성을 증진한다.
- 다형성을 통해 객체 타입 결합도를 메시지 결합도로 바꿔 결합을 약하게 할 수 있다.
유연하고 확장 가능하고 재사용성이 높은 협력의 의미
- 객체 간 정보가 적으면 다음과 같은 장점이 있다.
- 송신자가 수신자를 구체적으로 선택할 필요가 없으므로 협력이 유연해진다.
- 수신자의 변경은 송신자에게 영향을 미치지 않으므로, 요청 수행 방식을 쉽게 변경할 수 있다.
- 같은 요청을 여러 객체들이 수행할 수 있기 때문에, 다양한 방식으로 협력의 수행 과정을 재사용할 수 있다.
송신자와 수신자를 약하게 연결하는 메시지
- 메시지만을 통해 상호작용하는 객체의 관계는 객체 간 결합도를 낮춰 설계의 유연성, 확장성, 재사용성을 증진한다.
- 따라서 좋은 메시지를 선택하는 것이 중요하다.
메시지를 따라라
객체지향의 핵심, 메시지
- 객체지향 애플리케이션은 객체들 간 연쇄적인 메시지의 송∙수신을 통한 협력으로 구축된다.
- 객체지향의 위력은 객체들의 특성과 행위를 텍스트로 표현하는 클래스가 아니라 객체들 간 협력을 위한 메시지에서 나온다.
- 데이터-주도 설계는 데이터를 중심으로 객체를 설계하는 것으로서, 객체의 내부 구조를 드러냄으로써 자율성이 감소한다.
- 좋은 책임 설계는 객체 간 협력에서 각자에게 무엇을 제공하고 무엇을 얻느냐를 고려하는 메시지에 달려 있다.
- 메시지 중심 설계를 통해 객체가 메시지를 선택하는 것이 아니라, 메시지가 객체를 선택해야 한다.
책임-주도 설계 다시 살펴보기
- 책임-주도 설계는 책임을 다하기 위해 협력하는 객체들을 중심으로 설계하는 기법이다.
- 애플리케이션이 수행하는 여러 기능을 시스템의 책임으로 인식한다.
- 이를 구현하기 위해 협력할 객체들을 찾는다.
- 책임을 수행하기 위해 다른 객체의 도움이 필요하면 요청에 필요한 메시지를 결정한다.
- 결정된 메시지를 수신하기 적합한 객체를 선택한다.
- 수신자도 다른 객체의 도움이 필요하면 같은 과정을 반복한다.
What/Who 사이클
- 책임-주도 설계에서 What/Who 사이클은 필요한 행위(메시지 이름)를 먼저 결정하고 이후에 그 행위를 수행할 객체를 결정하는 과정을 뜻한다.
- 객체의 속성은 행위를 결정하지 않는다. 필요한 행위가 있으면 그때 수행 가능한 객체를 찾는 것이다.
- 이 과정을 통해 인터페이스를 설계한다.
- 이러한 인터페이스 설계 방법은 테스트-주도 설계의 핵심 아이디어가 된다.
묻지 말고 시켜라
- 데메테르 법칙은 메시지를 먼저 결정하고, 해당 메시지를 객체가 따르게 하는 방식으로 인터페이스를 설계하는 기법이다.
- 메시지를 결정하는 시점에 어떤 객체가 메시지를 수신할지 미리 알지 못하면, 수신자의 내부 구조를 고려한 메시지를 작성할 수 없게 된다.
- 객체의 자율성을 높이고 수신자의 캡슐화를 증진하며, 송신자와 수신자의 결합도가 낮아진다.
- 메시지가 ‘어떻게’ 해야 하는지가 아닌 ‘무엇’을 해야 하는 지를 담으면 인터페이스가 간소화된다.
- 외부에서의 해당 객체 의존도가 낮아진다.
- 객체 간 결합도가 낮아지고 설계가 유연해진다.
- 외부에서의 해당 객체 의존도가 낮아진다.
메시지를 믿어라
- 객체지향의 전체 시스템은 메시지를 통한 객체 연결이 모여 완성된다.
- 이를 통해 유연(협력 대상 자유롭게 교체)하고 재사용성(동일한 협력 과정에 다양한 타입 객체 참여)이 높은 설계를 할 수 있다.
객체 인터페이스
인터페이스
- 인터페이스는 두 사물이 만나는 경계에서 서로 상호작용할 수 있도록 연결해 주는 방법 또는 장치를 의미한다.
- 말, 글, 몸짓, (스마트폰을 사용하는) 손가락, 리모컨, 엘리베이터 버튼, GUI, API는 모두 인터페이스이다.
- 인터페이스의 특징은 다음과 같다.
- 내부 구조 또는 동작 방식을 알지 못해도 인터페이스의 사용 방법만 알면 해당 객체를 사용할 수 있다(추상화).
- 내부 구조의 변경은 인터페이스에게 전혀 영향을 끼치지 않는다(캡슐화).
- 수신 대상 객체가 바뀌어도 인터페이스가 동일하면 문제없이 상호작용할 수 있다(다형성).
- 예) 인터페이스를 가지는 자동차의 특징은 다음과 같다.
- 내부 구조 또는 동작 방식을 알지 못해도 인터페이스를 통해 자동차를 운전할 수 있다(추상화).
- 내부 구조가 변해도 동일한 방식으로 운전할 수 있다(캡슐화).
- 모든 종류의 자동차는 운전자에게 동일한 인터페이스를 제공한다(다형성).
메시지가 인터페이스를 결정한다
- 객체가 수신할 수 있는 메시지가 인터페이스의 형태를 결정한다.
공용 인터페이스
- 외부에 공개된 인터페이스를 공용 인터페이스라고 한다.
- 객체지향의 모든 상호작용은 메시지를 통해 이루어지므로, 사적인 인터페이스도 스스로에게 메시지를 전송해야만 접근할 수 있다.
- 왕이 모자 장수와 협력할 수 있는 유일한 수단은 ‘증언하라’라는 메시지를 보내는 것이다.
- 모자 장수의 공용 인터페이스는 ‘증언하라’라는 메시지를 수신할 수 있어야 한다.
- 메시지는 공용 인터페이스의 형태를 결정한다.
public interface Witness {
void witness(String contentsOfWitness);
List<String> testify();
}
책임, 메시지, 그리고 인터페이스
- 객체지향의 힘은 외부와 내부를 분리하는 것에 있다.
인터페이스와 구현의 분리
객체 관점에서 생각하는 방법
- 객체지향에서 인터페이스의 세 가지 원칙은 다음과 같다.
- 추상적인 인터페이스
- 예) ‘증언하라(testify)’
- 최소 인터페이스
- 예) ‘재판하라(progressTrial)’
public interface Judge { List<String> progressTrial(Witness witness); }
- 인터페이스와 구현의 차이 인식
- 추상적인 인터페이스
구현
- 객체에서 공용 인터페이스에 포함되지 않는 모든 부분은 구현에 속한다.
- 구현은 상태(field), 행동(method)으로 구성되어 있다.
- 구현과 공용 인터페이스를 통해 객체의 내⋅외부를 구분한다.
- 설계를 단순하고 유연하게 해 준다.
인터페이스와 구현의 분리 원칙
- 좋은 객체는 서로의 구현을 모른 상태에서 인터페이스를 통해 상호작용하는 객체다.
- 소프트웨어는 항상 변경되기 때문에, 인터페이스와 구현의 분리를 통해 변경의 파급효과를 최소화해야 한다.
- 변경 시 외부에 영향을 미치는 위험 지대를 공용 인터페이스로 한정함으로써 안전 지대를 늘린다.
- 변경에 의한 파급효과가 감소한다.
- 외부에 영향을 미치지 않고 내부 구조를 변경할 수 있으므로, 객체의 자율성이 향상된다.
- 이를 캡슐화라고 한다.
- 변경 시 외부에 영향을 미치는 위험 지대를 공용 인터페이스로 한정함으로써 안전 지대를 늘린다.
캡슐화
- 캡슐화는 객체의 상태와 행위를 캡슐화함으로써 내부의 정보를 은닉하는 것이다.
상태와 행위의 캡슐화
- 데이터 캡슐화라고 한다.
- 객체는 상태와 행동이 합쳐진 자율적인 단위다.
- 주로 상태는 data, 행동은 process로 구현된다.
- 전통적인 개발 방식과 달리, data와 process를 엄격하게 구분하지 않고 통합해서 관리한다.
- 주로 상태는 data, 행동은 process로 구현된다.
- 객체의 상태와 행동 중 외부의 요청 수신에 필요한 일부 행동만을 공용 인터페이스를 통해 외부에 노출한다.
- 상태는 객체 스스로 관리한다. 따라서 외부에 노출하지 않으며, 이는 객체를 자율적으로 만들어 준다.
사적인 비밀의 캡슐화
- 외부 객체로부터 내부 정보를 은닉함으로써 달성한다.
- 객체 내부의 변경사항을 외부에서 알지 못하도록 한다.
- 외부의 공격과 간섭으로부터 내부를 보호함으로써 객체의 자율성을 보장할 수 있다.
- 내부 정보를 은닉하기 위해 따로 외부와 소통할 수 있는 공용 인터페이스를 사용한다.
- 외부 객체는 요청 시 대상의 인터페이스에만 의존하고, 세부 구현 사항에 기반한 메시지를 전송할 수 없다.
- 객체 내부의 변경사항을 외부에서 알지 못하도록 한다.
책임의 자율성이 협력의 품질을 결정한다
- 책임의 자율성을 보장하면 이해가능성과 유연성이 증진된다.
- 이는 다음과 같은 협력 설계 품질의 향상으로 귀결된다.
- 협력과정을 단순하게 한다.
- 왕은 단순히 ‘증언하라’라고만 요청함으로써 세부 책임을 명시하는 경우에 비해 쉽게 모자 장수와 협력할 수 있다.
- 세부적인 사항들을 배제하고 단순한 공통사항만을 드러내는 추상화를 달성할 수 있다.
- 내∙외부를 명확하게 분리한다.
- 모자 장수는 자신의 증언 방법을 자율적으로 선택할 수 있다.
- 외부 객체로부터 내부 구현을 감추는 캡슐화를 달성할 수 있다.
- 내부 구현을 변경하더라도 외부에 영향을 주지 않는다.
- 자율성을 보장함으로써, 모자 장수가 어떤 방식으로 증언하든 왕은 동일하게 원하는 정보를 얻을 수 있다.
- 객체 간 결합도를 낮춤으로써, 내부의 구현사항으로 인해 외부의 메시지를 변경할 필요가 없어진다.
- 다양한 협력 대상을 선택할 수 있다.
- 증언 방식의 자율성이 보장되므로, 모자 장수뿐만 아니라 요리사나 앨리스도 증언이 가능하다.
- 동일한 책임을 수행 가능한 객체가 다양해지므로, 유연성과 재사용성이 증가한다.
- 객체 역할에 대한 이해가 쉬워진다.
- ‘증언하라’라는 책임은 모자 장수의 역할을 명확하게 이해할 수 있게 해 준다.
- 객체 내부의 책임이 강하게 연결되므로, 객체의 응집도가 높아진다.
- 협력과정을 단순하게 한다.
- 이는 다음과 같은 협력 설계 품질의 향상으로 귀결된다.
'Study' 카테고리의 다른 글
개발지식 스터디 발표 자료(빌더 패턴 & 팩토리 메서드 패턴) (0) | 2023.07.18 |
---|---|
객체지향의 사실과 오해 부록A 발표 자료 (2) | 2023.07.12 |
객체지향의 사실과 오해 스터디 7장 발표 자료 (0) | 2023.07.11 |
커뮤니케이션 스터디 발표 자료(재귀함수) (0) | 2023.06.23 |
객체지향의 사실과 오해 스터디 2장 발표 자료 (0) | 2023.06.20 |