[Book] 객체지향의 사실과 오해
조영호님의
객체지향의 사실과 오해
정리글
책에 대한 감상
해당 책은 객체 세계의 역할, 책임, 협력의 관점을 기준으로 진행되어야 할 객체지향 방식의 설계와 개발 방식을 말하고 있다. 개발 경험을 돌아봤을 때, 나는 DB 데이터, 클래스의 상태 중심의 사고를 중시했다. 헌데, 이러한 생각이 잘 못되었다는 것을 책을 읽으면서 깨닫게 되었다. 행위가 중요하며, 상태는 결국 행위의 결과에 대한 단순화된 결과인 것이다. 또한, 상태는 행위의 결과에 대한 이해를 위한 도구이다.
이러한 사고의 전환은 내게 충격이었다. 그리고 현재 프로젝트에 대한 생각을 하게 되었다. 대부분의 개발은 기존 Legacy 시스템 및 코드를 기반으로 진행된다. 이러한 상황에서 기존 테이블 스키마, 객체에서 벗어나 해당 업무의 협력, 책임, 역할을 중심으로 객체에 책임을 부여하고 역할을 중심으로 분류하는 것은 쉽지 않은 일이라 생각된다. 특히나, 업무 배경 지식이 부족한 주니어 개발자인 나의 경우에는 Legacy 코드를 이해하는 것만으로도 벅차다 보니 납기에 맞추기 위해 객체가 가져야 할 책임, 역할에 대한 깊은 고려가 이루어지지 못하는 경우가 부지기수이다.
또한, 업무 도메인에만 종속적인 전문가 역시 업무 프로세스에 대한 보수성으로 인해 잘못된 설계의 방향으로 나아가게 될 수 도 있지 않을까 라는 생각이 들기도 했다. 대체로 한 도메인을 오래 담당하게 되면 업무 프로세스 전문가인 동시에 변화에 부정적이고 보수적인 생각이 자리잡게 될 것이다. 업무 지식과 변화에 대한 두려움의 결합은 결국 더 나은 객체지향의 설계에 대한 허들로 작용할 수 있지 않을까? 라는 생각도 하게 되었다.
객체지향은 플라톤의 이데아론
과 비슷한 면이 있는 것 같다. 이데아론은 실재와 현상을 구분하고 인간은 현상을 실재로 인식한다는 이론이다. 객체지향도 실제 물질이 존재하고, 프로그래머는 실제 물질에 대한 일부 특성만을 추상화해 하나의 그림자
를 만들어낸다. 예를 들어, 강아지를 프로그래머가 추상화를 한다면 필요한 행위를 찾고 이를 위한 개념(타입)을 정의할 것이다. 프로그래머가 만든 “강아지 그림자”는 짖는 모습을 가진 강아지이다.
이데아론과 객체지향은 서로 다른 점이 존재한다. 플라톤은 현상에 현혹되어서는 아니되며, 실재를 인식해야 한다고 주장한다. 이와 달리, 객체지향은 사람들이 해결되어야 할 문제를 정의하고 이를 위해 필요한 행위를 찾고 적절한 개념에 행위의 책임을 부여한다. 이 두 이론은 의도성
과 자율성
에 있어 차이를 가진다고 할 수 있다. 프로그래머는 자신들이 만들어낸 현상이 실재와는 다르다는 것을 알고 있으며, 의도적으로 실재를 추상화된 개념으로 실제 세상과는 다른 세상을 창조한다.
책의 막바지에서 “객체지향 패러다임은 아리스토텔레스의 분류법의 근간을 형성하는 아이디어에 기반한다” 가 언급된다. 객체들이 동일한 특성을 가진다면 그것들은 동일한 카테고리에 속한다. 또한 아리스토텔레스는 객체의 특성을 본직적인 속성
과 우연적인 속성
으로 분류했다. 본질이란 한 사물의 가장 핵심적이고 필수불가결한 속성. 본직적이지 않은 속성을 우연적 속성이라고 한다. 아리스토텔레스를 중심으로 한 그리스 철학은 1) 사물의 속성 자체에 주의를 기울이고, 2) 그 속성에 근거해서 사물을 범주화하며, 3) 그 범주들을 사용해 어떤 규칙을 만들어, 4) 사물들의 움직임을 그 규칙으로 설명하고자 했다.
프로그래밍 원리, 원칙 혹은 패러다임은 철학으로부터 많은 영향을 받는 것 같다. 소프트웨어 구축이라는 것이 개발자가 신이 되어 또 다른 세상을 창조해내는 것이기 때문일까? 개발자라는 길을 택하게 되면서 다양한 기술 습득이 주는 흥미도 있지만, 이와 같은 사상과 철학이 담긴 것들에 대해 사색을 하는 것 역시 내게 또 다른 흥미를 준다는 점이 개발자라는 직업을 매력적으로 느껴지게 한다.
1. 협력하는 객체들의 공동체
객체의 자율성
- 객체의 자율성은 객체의 내부와 외부를 명확하게 구분하는 것으로부터 나옴
- 다른 객체가
무엇
을 수행하는지는 알지만어떻게
수행하는지는 관심사가 아님 - 객체의 관점에서 자율성이란 자신의 상태를 직접 관리하고 상태를 기반으로 스스로 판단하고 행동할 수 있음을 의미
객체는 상태와 행위를 하나의 단위로 묶는 자율적인 존재
협력과 메시지
🏀 메시지
- 객체지향의 세계에서는 오직 한 가지 의사소통 수단만이 존재. 이를 메시지라고 함.
- 객체는 협력을 위해 다른 객체에게 메시지를 전송하고 다른 객체는 메시지를 수신.
- 송신자(Sender) - 수신자(Receiver)
메서드와 자율성
🏄🏻 메서드
- 객체가 수신된 메시지를 처리하는 방법
- 메시지와 메서드의 분리는 객체의 협력에 참여하는 객체들 간의 자율성을 증진시킴
객체지향의 본질
🏀 객체지향이란 시스템을 상호작용하는 자율적인 객체들의 공동체로 바라보고 객체를 이용해 시스템을 분할하는 방법
🏀 자율적인 객체란 상태
와 행위
를 함께 지니며, 스스로 자기 자신을 책임지는 객체를 의미
🏀 객체는 시스템의 행위를 구현하기 위해 다른 객체와 협력
한다. 각 객체는 협력 내에서 정해진 역할
을 수행하며 역할은 책임
의 집합
🏀 객체는 다른 객체와 협력하기 위해 메시지를 전송하고, 메시지를 수신한 객체는 메시지를 처리하는 데 적합한 메서드
를 자율적으로 선택
객체를 지향하라
- 클래스가 객체지향 프로그래밍 언어의 관점에서 매우 중요한 구성요소인 것은 분명하지만 객체지향의 핵심을 이루는 중심 개념이라고 말하기에는 무리가 있음
- 코드를 담는 클래스의 관점이 아닌 메시지를 주고받는 객체의 관점으로 사고의 전환 필요
객체지향의 핵심은 적절한 책임을 수행하는 역할 간의 유연하고 견고한 협력 관계를 구축하는 것
2. 이상한 나라의 객체
객체지향 패러다임은 지식을 추상화하고 추상화한 지식을 객체 안에 캡슐화함으로써 실세계 문제에 내재된 복잡성을 관리하려고 한다. 객체를 발견하고 창조하는 것은 지식과 행동을 구조화하는 문제다
- 레베카 워프스브록 -
객체지향과 인지능력
- 객체란 인간이 분명하게 인지하고 구별할 수 있는 물리적인 또는 개념적인 경계를 지닌 어떤 것
- 객체지향 패러다임의 목적은 현실 세계 모방이 아닌 현실 세계를 기반으로 새로운 세계를 창조하는 것
객체, 그리고 소프트웨어 나라
- 객체의 다양한 특성을 효과적으로 설명하기 위해서는 객체를
상태
,행위
,식별자
를 지닌 실체로 보는 것이 가장 효과적
객체
객체란 식별 가능한 개체 또는 사물.
객체는 구별 가능한 식별자, 특징적인 행동, 변경 가능한 상태를 가짐
상태
🏀 왜 상태가 필요한가?
- 상태를 이용하면 과거의 모든 행동 이력을 설명하지 않고도 행동의 결과를 쉽게 예측하고 설명할 수 있다.
- 상태는 근본적으로 세상의 복잡성을 완화하고 인지 과부하를 줄일 수 있는 중요한 개념
⚽ 상태와 프로퍼티
- 프로퍼티
- 객체의 상태를 구성하는 모든 특징을 통틀어 객체의 프로퍼티라고 한다.
- 객체를 구성하는 단순한 값은 속성(attribute)
상태
특정시점에 객체가 가지고 있는 정보의 집합
객체의 상태는 객체젱 존재하는정직인 프로퍼티
와동적인 프로퍼티 값
으로 구성된다. 객체의 프로퍼티는 단순한 값과 다른 객체를 참조하는 링크로 구분
- 객체는 다른 객체의 상태에 직접적으로 접근할 수도 상태를 변경할 수도 없다
- 객체는 스스로의 행동에 의해서만 상태가 변경되는 것을 보장함으로써 객체의 자율성을 유지
행동
상태와 행동
- 상태와 행동 사이의 관계
- 객체의 행동은 상태에 영향을 받는다
- 객체의 행동은 상태를 변경시킨다
협력과 행동
- 객체는 다른 객체와 적극적으로 상호작용하며 ‘협력하는 객체들의 공동체’에 참여하기 위해 노력한다.
- 객체의 행동은 객체가 협력에 참여할 수 있는 유일한 방법
- 객체는 협력에 참여하는 과정에서 자기 자신의 상태뿐만 아니라 다른 객체의 상태 변경을 유발할 수도 있다
- 객체의 행동으로 인해 발생하는 결과 두 가지 관점 설명
- 객체 자신의 상태변경
- 행동 내에서 협력하는 다른 객체에 대한 메시지 전송
행동
행동이란 외부의 요청 또는 수신된 메시지에 응답하기 위해 동작하고 반응하는 활동
행동의 결과로 객체는 자신의 상태를 변경하거나 다른 객체에게 메시지를 전달할 수 있다
객체는 행동을 통해 다른 객체와의 협력에 참여하므로 행동의 외부에 가시적이어야 한다
상태 캡슐화
- 객체지향에서의 모든 객체는 자신의 상태를 스스로 관리하는 자율적인 존재
- 객체는 상태를 캡슐 안에 감춰둔 채 외부로 노출하지 않는다
- 객체가 외부에 노출하는 것은 행동뿐이며, 외부에서 객체에 접근할 수 있는 유일한 방법 역시 행동뿐
- 상태를 외부에 노출시키지 않고 행동을 경계로 캡슐화
- 객체의 자율성을 높임
- 자율적인 객체는 스스로 판단하고 스스로 결정하기 때문에 객체의 자율성이 높아질수록 객체의 지능도 높아짐 –> 협력은 유연하고 간결해짐
결론 : 상태를 잘 정의된 행동 집합 뒤로 캡슐화하는 것은 객체의 자율성을 높이고 협력을 단순하고 유연하게 만든다. 캡슐화를 해야 하는 이유.
식별자
- 식별자 : 객체를 서로 구별할 수 있는 특정한 프로퍼티
- 모든 객체가 식별자를 가짐
- 객체가 아닌 단순한 값은 식별자를 가지지 않음
- Value(값) : 숫자, 문자열, 날짜, 시간, 금액 등과 같이 변하지 않는 양을 모델링
- 불변 상태를 가짐
- 값의 상태가 같은지를 통해
동등성
(equality)을 판단 - 값 객체(value object)
- 객체 : 변경되는 상태를 포함. 행동을 통해 상태 변경.
가변 상태
를 가짐동일성
(identical) : 식별자를 기반으로 객체가 같은지를 판단할 수 있는 성질- 참조 객체(reference object), 엔티티(entity)
식별자
식별자란 어떤 객체를 다른 객체와 구분하는 데 사용하는 객체의 프로퍼티
값은 식별자를 가지지 않기 때문에 상태를 이용한동등성
검사를 통해 두 인스턴스 비교
객체는 식별자를 이용한동일성
검사를 통해 두 인스턴스 비교
행동이 상태를 결정한다
객체지향에 갓 입문한 사람들이 가장 쉽게 빠지는 함정은 상태를 중심으로 객체를 바라보는 것
- 상태를 먼저 결정할 경우 캡슐화가 저해
- 객체를 협력자가 아닌 고립된 섬으로 만든다.
- 객체의 재사용성이 저하
- 객체지향 설계는 애플리케이션에 필요한 협력을 생각하고 협력에 참여하는 데 필요한 행동을 생각한 후 행동을 수행할 객체를 선택하는 방식으로 수행됨
- 행동을 결정한 후에야 필요한 정보가 무엇인지를 고려하게 되며 이 과정에서 필요한 상태가 결정됨
어떤 책임이 필요한가를 결정하는 과정이 전체 설계를 주도
- 책임-주도 설계(Responsibility-Driven Design)
상태는 행동의 결과로 초래된 부수효과를 쉽게 표현하기 위해 도입한 추상적인 개념일뿐
3. 타입과 추상화
추상화
어떤 양상, 세부 사항, 구조를 좀 더 명확하게 이해하기 위해 특정 절차나 물체를 의도적으로 생략하거나 감춤으로써 복잡도를 극복하는 방법(단순화)
추상화 방법
- 구체적인 사물들 간의 공통점은 취하고, 차이점은 버리는 일반화를 통해 단순화
- 중요한 부분을 강조하기 위해 불필요한 세부 사항 제거 통한 단순화
공통점을 기반으로 객체들을 묶기 위해 개념(=타입)
을 사용.
개념을 이용하면 객체를 여러 그룹으로 분류
할 수 있음
개념의 3가지 관점
- 심볼(symbol) : 개념을 가리키는 간략한 이름/명칭
- 내연(intension) : 개념의 완전한 정의. 객체가 개념에 속하는지 여부 확인 기준
- 외연(extension) : 개념에 속하는 모든 객체 집합
객체의 타입
- 어떤 객체가 어떤 타입에 속하는지를 결정하는 것은 객체가 수행하는 행동
- 어떤 행동을 하느냐에 따라 객체의 타입이 결정됨
- 객체의 내부적인 표현은 외부로부터 철저하게 감춰진다
- 내부 표현 방식이 다르더라도 어떤 객체들이 동일하게 행동한다면 그 객체들은 동일한 타입
타입은 추상화. 타입을 이용하면 객체의 동적인 특성을 추상화할 수 있음
결론적으로 객체의 타입을 결정하는 것은 객체의 행동
처리방식은 다를 수 있는데 여기서 다형성이 나오는 것
일반화/특수화
객체지향에서 일반화/특수화 관계를 결정하는 것은 객체의 상태를 표현하는 데이터가 아니라 행동
하는 것
일반적인 타입은 특수한 타입에 비해 더 적은 수의 행동을 가지며 특수한 타입은 일반적인 타입에 비해 더 많은 행동을 가짐
단, 특수한 타입은 일반적인 타입이 할 수 있는 모든 행동을 동일하게 수행할 수 있어야 함
캡슐화는 내부의 데이터는 감추고 행위를 외부에 노출을 함으로써 객체의 응집도를 높이고 결합도를 낮추는 것. 객체 혼자서 작동하는 것이 아닌 협력, 위임을 통해 객체지향 세계가 작동하고 있음. 따라서 외부에 노출하는 행위가 객체지향 세계에서는 중요한 것이며, 상태를 표현하는 속성보다 행위가 더 강조되는 이유이다.
4. 역할, 책임, 협력
협력은 요청과 함께 발생함.
어떤 객체가 어떤 요청에 대해 대답해 줄 수 있거나, 적절한 행동을 할 의무가 있는 경우 해당 객체가 책임
을 가진다고 말한다.
객체지향 설계는 협력에 참여하기 위해 어떤 객체가 어떤 책임을 수행해야 하고 어떤 객체로부터 메시지를 수신할 것인지를 결정하는 것으로부터 시작된다.
역할은 책임의 집합.
역할은 협력 안에서 구체적인 객체로 대체될 수 있는 추상적인 협력자. 즉, 다른 객체에 의해 대체 가능함.
객체지향 설계 기법
- 책임-주도 설계
- 레베카 워프스브록이 고안
- 협력에 필요한 책임들을 식별하고 적합한 객체에게 책임을 할당하는 방식으로 애플리케이션 설계
- 객체의
책임
을 중심으로 시스템을 구축하는 설계 방법 - 개별적인 객체의 상태가 아니라 객체의 책임과 상호작용에 집중
- 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악
- 시스템 책임을 더 작은 책임으로 분할
- 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당
- 객체가 책임을 수행하는 중에 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾는다
- 해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력하게 된다
- 디자인 패턴
- 패턴은 모범이 되는 설계
- 반복적으로 발생하는 문제와 그 문제에 대한 해법
- 디자인 패턴 모아놓은 사이트 : https://java-design-patterns.com
- <Head First Design Pattern> 책 개인적으로 정리 : https://github.com/BryceYangS/Java/tree/main/DesignPattern
- 테스트 주도 개발(Test-Driven Development)
- 테스트를 작성하는 것이 아니라 책임을 수행할 객체 또는 클라이언트가 기대하는 객체의 역할이 메시지를 수신할 때 어떤 결과를 반환하고 그 과정에서 어떤 객체와 협력할 것인지에 대한 기대를 코드의 형태로 작성하는 것
- 테스트 주도 개발은 객체의 역할과 책임에 대한 깊은 이해, 즉 객체지향에 대한 깊은 지식과 패턴 등의 종합적으로 이해하고 좋은 설계에 대한 감각과 경험을 길러야만 적용할 수 잇는 설계 기법
5. 책임과 메시지
- 객체의 책임을 지나치게 구체적으로 제한해서도 안되며, 너무 추상적이어서도 안된다. 자율성을 보장하는 한도 내에서 책임을 부여하는 의도를 명확하게 설명할 수 있어야 한다.
메서드
메서드
: 메시지를 처리하기 위해 내부적으로 선택하는 방법
다형성
다형성
: 서로 다른 유형의 객체가 동일한 메시지에 대해 서로 다르게 반응하는 것- 서로 다른 타입에 속하는 객체들이 동일한 메시지를 수신할 경우 서로 다른 메서드를 이용해 메시지를 처리할 수 있는 메커니즘
- 서로 다른 객체들이 다형성을 만족시킨다는 것은 객체들이 동일한 책임을 공유한다는 것을 의미
- 다형성은 객체들의 대체 가능성을 이용해 설계를 유연하고 재사용 가능하게 만든다. 다형성은 수신자의 종류를
캡슐화
한다. - 송신자와 수신자 간의 객체 타입에 대합 결합도를 낮추어준다.
- 예를 들어, ArrayList를 인자로 받는 수신자는 송-수신자 모두 상위 타입인 List 인터페이스로 메시지를 주고 받는다. 이를 통해 확장성을 확보할 수 있으며, 동시에 구체적인 타입에 의존하지 않는 낮은 결합도를 달성할 수 있는 것이다.
다형성 이점
- 협력이 유연해짐
- 송신자는 행위만 동일하다면 수신자가 누구인지 알 필요가 없음. 따라서 해당 행위에 한해 같은 타입의 객체로 변경해도 송신자의 행위엔 아무 문제가 없음
- 협력 수행의 방식 확장이 용이
- 송신자는 수신자가 행위를 하는 구체적 방법에 의존하지 않기 때문에 설령 행위의 구체적인 방식이 확장된다 할지라도 송신자에겐 영향을 주지 않음
- 협력의 방식을 재사용
- 비슷한 과정의 행위를 진행하는 데 있어 구체적 방법, 구체적 대상이 아무리 바뀌어도 전체적 흐름은 유지되기 때문에 비슷한 진행의 과정이라면 언제든 재사용이 가능
객체를 이용하는 중요한 이유는 객체가 다른 객체가 필요로 하는 행위를 제공하기 때문
협력 관계 속에서 다른 객체에게 무엇을 제공해야 하고 다른 객체로부터 무엇을 얻어야 하는가라는 관점에서 접근할 때만 훌륭한 책임을 수확할 수 있다
개인적인 잡념
- DDD Start라는 책을 읽으면서 동일한 엔티티이더라도 Bounded Context에 따라 하위 도메인의 역할이 다를 수 있다는 것을 알게 되었다. 즉, 논리적으로 같은 존재처럼 보이지만 하위 도메인에 따라 다른 용어를 사용하는 경우가 있다는 것이다.
- 예를 들어, 회원 도메인에서
회원
이라고 하더라도 주문 도메인 내에서는구매자
라는 역할을 수행하는 객체이다. 배송 도메인에서는보내는 사람
이다. - 이처럼 각 하위 도메인 내에서 개념적으로 동일한 존재이더라도 서로 다른 책임을 수행하는 객체일 수 있다는 것이다.
인터페이스
- 어떤 두 사물이 마주치는 경계 지점에서 서로 상호작용할 수 있게 이어주는 방법이나 장치
인터페이스의 세 가지 특징
- 인터페이스의 사용법을 익히기만 하면 내부 구조나 동작 방식을 몰라도 쉽게 대상을 조작하거나 의사를 전달할 수 있다
- 팩토리 클래스, 팩토리 메서드를 통해 구체적인 구현체를 가져온다. 이를 사용하는 메시지 송신자는 인터페이스로 받음으로써 사용하기만 하면 된다. 어떻게 동작하는지는 알 필요가 없다.
- 인터페이스 자체는 변경하지 않고 단순히 내부 구성이나 작동 방식만을 변경하는 것은 인터페이스 사용자에게 어떤 영향도 미치지 않는다.
- 대상이 변경되더라도 동일한 인터페이스를 제공하기만 하면 아무런 문제 없이 상호작용 가능
인터페이스와 구현의 분리 원칙
- 객체를 설계할 때 객체 외부에 노출되는 인터페이스와 객체의 내부에 숨겨지는 구현을 명확하게 분리해서 고려해야 한다는 것
- 외부에 노출되는 것은 인터페이스로, 내부 구현은 이와 분리해서 숨김
- 해당 원칙이 중요한 이유 : 소프트웨어는 항상 변경되기 때문