객체 지향의 사실과 오해 - 역할, 책임, 협력
객체 지향의 사실과 오해를 읽고 정리한 글
섹션 - 04. 역할, 책임, 협력
객체의 모양을 빚는 것은 객체가 참여하는 협력이다. 어떤 협력에 참여하는지가 객체에 필요한 행동을 결정하고, 필요한 행동이 객체의 상태를 결정한다. 개별적인 객체의 행동이나 상태가 아닌 객체들 간의 협력에 집중해야 한다.
내용
객체지향의 설계의 품질을 결정하는 개념 - 역할, 책임, 협력
협력
협력은 요청과 응답이다. 요청과 응답을 통해 협력관계가 형성이 된다. 다양한 객체는 동일한 목적을 달성하기 위해 협력한다. 그리고 요청과 응답은 협력에 참여하는 객체가 수행할 책임을 정의한다.
책임
객체는 적절한 행동을 할 의무가 있으면 책임을 가진다고 말한다. 즉, 협력관계에 있는 객체는 어떤 요청에 적절히 응답해야 하는 책임을 가진다. 객체의 책임은 객체가 알아야 하는 정보와 객체가 수행할 수 있는 행위에 대해 개략적으로 서술한 문장이고, ‘객체가 무엇을 알고 있는가?’와 ‘무엇을 할 수 있는가?’로 분류할 수 있다.
- 하는 것 - doing
- 객체를 생성하거나 계산을 하는 스스로 동작하는 것
- 다른 객체의 행동을 시작시키는 것
- 다른 객체의 활동을 제어하고 조절하는 것
- 아는 것 - knowing
- 개인적인 정보에 관해 아는 것
- 관련된 객체에 관해 아는 것
- 자신이 유도하거나 계산할 수 있는 것에 관해 아는 것
즉, 책임은 객체의 외부에 제공해 줄 수 있는 정보(knowing)와 외부에 제공해 줄 수 있는 서비스(doing)의 목록이다.
⚠️ 책임을 어떻게 구현할 것인가의 문제는 객체와 책임을 고려하고 난 후에 해도 늦지 않고, 오히려 성급하게 구현하면 변경에 취약하고 다양한 협력에 참여할 수 없는 비 자율적인 객체를 낳는다.
책임 안에서 메시지
객체가 다른 객체에게 책임을 수행하도록 요청을 보내는 것을 메시지 전송이라 한다. 두 객체 간의 협력은 메시지를 통해 이뤄지고, 협력을 요청하는 객체를 송신자라 하고, 요청을 처리하는 객체를 수신자라 한다. 한 가지 주의할 점은 책임과 메시지의 수준이 같지 않다는 점이다. 책임은 객체가 협력에 참여하기 위해 수행해야 하는 행위를 상위 수준에서 개략적으로 서술한 것이므로, 책임을 결정, 정제 후 변환하면 여러 메시지로 분할이 된다.
⚠️ 책임이 협력이라는 문맥 속에서 한 객체의 관점에서 무엇을 할 수 있는지를 나열하는 것이라면 메시지는 협력에 참여하는 두 객체 사이의 관계를 강조한 것이다.
객체지향 설계는 협력에 참여하기 위해 어떤 객체가 어떤 책임을 수행해야 하고 어떤 객체로 부터 메시지를 수신할 것인지를 결정하는 것으로부터 시작된다. 어떤 클래스가 필요하고 어떤 메서드를 포함해야 하는지 결정하는 것은 책임과 메시지에 대한 대략적인 윤관을 잡은 후에 시작해도 늦지 않다.
역할
객체가 수행하는 책임의 집합은 객체가 협력 안에서 수행하는 역할을 암시하고, 동일한 역할을 수행할 수 있다는 것은 협력 내에서 동일한 책임을 수행할 수 있다는 것을 의미한다. 이렇듯, 역할의 개념을 사용하면 협력을 추상화해서 인지 과부하를 줄일 수 있고, 다양한 객체들이 협력에 참여할 수 있기 때문에 재사용성이 높아진다. 즉, 역할을 통해 재사용 가능하고 유용한 객체지향 설계를 할 수 있다.
⚠️ 역할을 대체할 수 있는 객체는 동일한 메시지를 이해할 수 있는 객체로 한정된다.
협력안에서 추상화
협력 안에 여러 종류의 객체가 참여할 수 있게 함으로 협력을 추상화할 수 있다는 것이다. 역할을 이용하면 추상화를 통해 협력을 단순화할 수 있다.
대체 가능성
역할은 협력 안에서 구체적인 객체로 대체될 수 있는 추상적인 협력자이면 역할은 다른 객체에 의해 대체 가능하다. 역할의 대체 가능성은 행위 호환성을 의미하고, 행위 호환성은 동일한 책임의 수행을 의미한다.
협력에서 일어나는 흔한 오류
- 데이터를 저장하기 위해 객체가 존재한다는 선입견
- 객체는 행위를 수행하기 위한 재료
- 객체지향이 클래스와 클래스 간의 관계를 표현하는 시스템의 정적인 측면에 중점을 둔다는 선입견
- 클래스를 어떻게 구현할 것인가가 아니라 객체가 협력안에서 어떤 책임과 역할을 수행할 것인지를 결정
협력을 생각하지 않은 체 객체 설계
데이터나 클래스를 중심으로 애플리케이션을 설계하는 이유는 협력이라는 문맥을 고려하지 않고 각 객체를 독립적으로 바라보기 때문이다. 하지만 협력이라는 문맥에서 떼어놓은 객체 만으로 설계하기에는 어떤 데이터가 필요하고 어떤 클래스로 구현하는지 고민하는 데 도움이 되지 않는다.
협력을 따라 흐르는 객체의 책임
- 견고하고 깔끔한 협력을 설계
- 설계에 참여하는 객체들이 주고받을 요청과 응답의 흐름을 결정한다.
- 요청과 응답의 흐름을 이용해 객체에게 책임을 할당한다.
- 요청과 응답의 흐름은 객체가 협력에 참여하기 위해 수행될 책임이 된다.
- 객체의 행동을 결정한다.
- 책임은 객체가 외부에 제공하게 될 행동이 된다.
- 행동을 결정한 후, 그 행동을 수행하는 데 필요한 데이터를 고민한다.
- 객체가 협력에 참여하기 위해 필요한 데이터와 행동이 어느 정도 결정된 후, 클래스의 구현 방법을 결정한다.
객체지향 설계 기법
객체지향 설계란 기능을 구현하기 위한 협력 관계를 고안하고, 협력에 필요한 역할과 책임을 식별한 후, 이를 수행할 수 있는 적절한 객체를 식별해 나그는 과정이다. 이러한 객체지향 설게 중 3가지의 설계 기법을 역할, 책임, 협력 관점에서 보려고 한다.
책임-주도 설계(Responsibility-Driven Design)
협력에 필요한 책임들을 식별하고 적합한 객체에게 책임을 할당하는 방식으로 애플리케이션을 설계하는 방법이다.
설계 순서
- 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악한다.
- 시스템 책임을 더 작은 책임으로 분할한다.
- 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당한다.
- 객체가 책임을 수행하는 중에 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾는다.
- 해당 객체 또는 역할에게 책임을 할당함으로 두 객체가 협력하게 한다.
💡 책임-주도 설계에서는 시스템의 책임을 객체의 책임으로 변환하고, 각 객체가 책임을 수행하는 중에 필요한 정보나 서비를 제공해줄 협력자를 찾아 해당 협력자에게 책임을 할당하는 순차적인 방식으로 객체들의 협력 공동체를 구축한다.
디자인 패턴(Design Pattern)
특정 문제를 해결하기 위해 이미 식별한 역할, 책임, 협력의 모음이다. 즉, 반복적으로 발생하는 문제와 그 문제에 대한 해법을 뜻한다. 패턴은 해결하려고 하는 문제가 무엇인지 명확하게 서술하고, 패턴을 적용할 수 있는 상황과 적용할 수 없는 상황을 함께 설명한다.
테스트-주도 개발(Test-Driven Develpoment)
구체적인 코드를 작성해나가며 역할, 책임, 협력을 식별하고 식별된 역할, 책임, 협력이 적합한지 피드백 받는 방법이다.
⚠️ 협력 안에서 객체의 역할과 책임이 무엇이고 이 것이 클래스와 같은 프로그래밍 언어 장치로 구현되는 방식에 대한 감각을 갖춰야만 효과적인 테스트를 작성할 수 있다. 즉, 책임-주도 설계의 기본 개념과 다양한 원칙과 프랙티스, 패턴을 종합적으로 이해하고 좋은 설계에 대한 감각과 경험을 길러야만 적용할 수 있는 설계 기법이다.
설계 순서
- 실패하는 테스트를 작성
- 테스트를 통과하는 가장 간단한 코드 작성
- 리팩터링
💡 테스트를 작성하기 위해 객체의 메서드를 호출하고 반환값을 검증하는 것은 객체가 수행해야 하는 객체가 요청받은 메시지에 책임을 가지고 적절한 응답을 하는지 확인하는 것이다. 그래서 테스트에 필요한 간접 입력 값을 제공하기 위해 스텁(stub)을 추가하거나 간접 출력 값을 검증하기 위해 목 객체(mock object)를 사용하는 것은 객체와 협력해야 하는 협력자에 관해 고민한 결과를 코드로 표현한 것이다.
정리
객체들은 협력을 통해 각자의 책임을 가진다. 그리고 협력 관계에서 여러 책임을 가지며 자신의 역할을 맡는다. 이 역할 안에는 이 역할을 수행할 수 있는 객체는 모두 들어갈 수 있는 대체 가능한 특성을 가지고, 이 특성으로 인해 역할을 추상화할 수 있다.
객체지향 설계를 하면 각 객체들이 협력에 필요한 책임들만 가지게 되어 의미있는 데이터들을 주고받을 수 있다는 장점과 함께 역할을 맡을 수 있는 객체는 모두 올 수 있는다는 특징을 통해 재사용성이라는 장점을 가진다.
테스트-주도 개발을 잘하기 위해서는 협력 안에서 객체의 역할과 책임이 무엇인지 잘 파악해 프로그래밍 언어로 구현하는 감각을 갖춰야 효과적인 테스트를 작성할 수 있다는 것을 알았다.