"객체지향의 사실과 오해" 책을 읽고 정리한 내용입니다.
객체지향의 개념
- 시스템을 상호작용하는 자율적인 객체들의 공동체로 바라보고, 객체를 이용해 시스템을 분할하는 방법이다.
- 자율적인 객체란 상태와 행위를 함께 지니며 스스로 자기 자신을 책임지는 객체를 의미한다.
- 객체는 시스템의 행위를 구현하기 위해 다른 객체와 협력한다. 각 객체는 협력 내에서 정해진 역할을 수행하며 역할은 관련된 책임의 집합이다.
- 객체는 다른 객체와 협력하기 위해 메시지를 전송하고, 메시지를 수신한 객체는 메시지를 처리하는데 적합한 메서드를 자율적으로 선택한다.
은유
소프트웨어 객체는 현실 세계의 객체와 전혀 다르다.
현실 속의 객체의 의미 일부가 소프트웨어 객체로 전달되기 때문에 프로그램 내의 객체는 현실 속의 객체에 대한 은유다.
은유 관계에 있는 실제 객체의 이름을 소프트웨어 객체의 이름으로 사용하면 표현적 차이를 줄여 소프트웨어의 구조를 쉽게 예측할 수 있고 이를 효과적으로 사용할 경우 이해하기 쉽고 유지보수가 용이한 소프트웨어를 만들 수 있다.
우리의 목적은 현실을 모방하는 것이 아니다. 새롭게 창조하는 것이다. 현실을 닮아야 한다는 어떤 제약이나 구속도 없다.
추상화
- 어떤 양상, 세부 사항, 구조를 좀 더 명확하게 이해하기 위해 특정 절차나 물체를 의도적으로 생략하거나 감춤으로써 복잡도를 극복하는 방법
- 복잡성을 다루기 위한 추상화의 두 차원
- 구체적인 사물들 간의 공통점은 취하고 차이점은 버리는 일반화를 통해 단순하게 만드는 것
- 중요한 부분을 강조하기 위해 불필요한 세부 사항을 제거함으로써 단순하게 만드는 것
타입(개념)
공통점을 기반으로 객체들을 묶기 위한 그릇
분류:
- 객체에 특정한 개념을 적용하여 특정한 집합의 멤버로 분류하는 것
수많은 사물들을 개념의 틀로 분류해가며 세상을 추상화함으로써 복잡한 세상을 제어 가능한 수준으로 단순화 할 수 있다.
객체는 데이터(데이터 주도 설계)가 아니라 그 객체가 어떤 행동(책임 주도 설계)을 하느냐에 따라 객체의 타입이 결정된다.
객체를 결정하는 것은 행동이다. 데이터는 단지 행동을 따를 뿐이다.
타입은 추상화다. 타입을 이용하면 객체의 동적인 특정을 추상화할 수 있고, 결국 타입은 시간에 따른 객체의 상태 변경이라는 복잡성을 단순화할 수 있는 효과적인 방법인 것이다.
객체를 분류하는 기준은 타입이며, 타입을 나누는 기준은 객체가 수행하는 행동이다. 클래스는 타입을 결정한 후 프로그래밍 언어를 이용하여 타입을 구현할 수 있는 한 가지 방법일 뿐이다.
역할, 책임, 협력
협력
- 협력은 한 객체가 다른 객체에게 도움을 요청할 때 시작된다.
- 전체적으로 협력은 다수의 연쇄적인 요청과 응답의 흐름으로 구성된다.
책임
- 협력이라는 문맥 속에서 요청을 수신하는 한 쪽의 객체 관점에서 무엇을 할 수 있는지를 나열하는 것
- 객체의 외부에 제공해 줄 수 있는 정보와 외부에 제공해 줄 수 있는 서비스의 목록
- 책임은 객체의 공용 인터페이스를 구성한다.
- 메시지 전송:
- 객체가 다른 객체에게 주어진 책임을 수행하도록 요청을 보내는 것
- 협력을 위해 한 객체가 다른 객체로 접근할 수 있는 유일한 방법
- 책임을 결정한 후 협력을 정제하면서 이를 메시지로 변활할 때는 하나의 책임이 여러 메시지로 분할되는 것이 일반적이다.
역할
어떤 협력에 참여하는 특정한 객체가 협력 안에서 차지하는 책임이나 임무를 의미
동일한 역할을 수행할 수 있다는 것은 해당 객체들이 협력 내에서 동일한 책임의 집합을 수행할 수 있다는 것을 의미한다.
역할의 가장 큰 가치는 하나의 협력 안에 여러 종류의 객체가 참여할 수 있게 함으로써 협력을 추상화할 수 있다는 것이다.
동일한 역할을 수행하는 객체들이 동일한 메시지를 수신할 수 있기 때문에 동일한 책임을 수행할 수 있다.
목표는 객체들의 협력을 통해 달성되며, 목표는 더 작은 책임으로 분할되고 책임을 수행할 수 있는 적절한 역할을 가진 객체에 의해 수행된다.
협력에 참여하는 각 객체는 책임을 수행하기 위해 다른 객체에게 도움을 요청하기도 하며, 이를 통해 연쇄적인 요청과 응답으로 구성되는 협력 관계가 완성된다.
각 객체가 가져야 하는 상태와 행위에 대해 고민하기 전에 그 객체가 참여할 문맥인 협력을 정의하라.
객체를 충분히 협력적으로 만든 후에 협력이라는 문맥 안에서 객체를 충분히 자율적으로 만들자.
객체지향 설계 기법
객체지향 시스템을 설계하는 절차
- 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악한다.
- 시스템 책임을 더 작은 책임으로 분할한다.
- 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당한다.
- 객체가 책임을 수행하는 중에 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾는다.
- 해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력하게 한다.
테스트-주도 개발
- 테스트-주도 개발의 기본 흐름은 실패하는 테스트를 작성하고, 테스트를 통과하는 가장 간단한 코드를 작성한 후, 리팩터링을 통해 중복을 제거하는 것이다.
- 객체가 이미 존재한다고 가정하고 객체에게 어떤 메시지를 전송할 것인지에 관해 먼저 생각하자.
- 테스트를 작성하는 것이 아니라, 책임을 수행할 객체 또는 클라이언트가 기대하는 객체의 역할이 메시지를 수신할 때 어떤 결과를 반환하고 그 과정에서 어떤 객체와 협력할 것인지에 대한 기대를 코드의 형태로 작성하는 것이다.
책임과 메시지
- 자율성:
- 자기 스스로의 원칙에 따라 어떤 일을 하거나 자신을 통제해서 절제하는 성질이나 특성
- 자율적인 객체:
- 스스로의 의지와 판단에 따라 각자 맡은 책임을 수행하는 객체
- 객체가 자율적이기 위해서는 객체에게 할당되는 책임의 수준 역시 자율적이어야 한다.
- 어떤 책임이 자율적인지를 판단하는 기준은 문맥에 따라 다르다. (포괄적이고 추상적인 책임을 선택한다고 해서 무조건 좋은 것은 아니다)
- 재판이라는 협력 안에서는 '증언하라'라는 책임이 증언을 하게 될 객체의 자율권을 보장하는 가장 적절한 수준의 책임이지만 다른 상황에서는 오히려 '설명하라'라는 책임이 자율권을 보장하는 최선의 선택이 될 수도 있다.
- 자율적인 책임의 특징은 객체가 '어떻게' 해야 하는가가 아니라 '무엇'을 해야 하는가를 설명한다는 것이다.
- 적절한 책임이 자율적인 객체를 낳고, 자율적인 객체들이 모여 유연하고 단순한 협력을 낳는다. 따라서 협력에 참여하는 객체가 얼마나 자율적인지가 전체 어플리케이션의 품질을 결정한다.
- 메시지:
- 객체가 자신에게 할당된 책임을 수행하도록 만드는 외부에서 전달되는 요청
- 객체는 메시지를 수신하면 먼저 해당 메시지를 처리할 수 있는지 여부를 확인하고 메시지를 처리할 수 있다고 판단되면 자신에게 주어진 책임을 다하기 위해 메시지를 처리할 방법인 메서드를 선택하게 된다.
- 다형성:
- 서로 다른 타입에 속하는 객체들이 동일한 메시지를 수신할 경우 서로 다른 메서드를 이용해 메시지를 처리할 수 있는 메커니즘을 가리킨다.
- 다형성은 송신자와 수신자 간의 객체 타입에 대한 결합도를 메시지에 대한 결합도로 낮춤으로써 달성된다.
- 개별 객체가 아니라 객체들이 주고받는 메시지에 초점을 맞추자.
유연하고 확장 가능하고 재사용성이 높은 협력의 의미
- 메시지 송신자는 수신자에 대한 어떤 가정도 하지 않기 때문에 수신자를 다른 타입의 객체로 대체하더라도 송신자는 알지 못한다. 따라서 송신자에 대한 파급효과 없이 유연하게 협력을 변경할 수 있다.
- 송신자에게 아무런 영향도 미치지 않고서도 수신자를 교체할 수 있기 때문에 협력의 세부적인 수행 방식을 쉽게 수정할 수 있다.
- 협력에 영향을 미치지 않고서도 다양한 객체들이 수신자의 자리를 대체할 수 있기 때문에 다양한 문맥에서 협력을 재사용할 수 있다.
인터페이스
인터페이스의 특성
- 인터페이스의 사용법만 알고 있으면 대상의 내부 구조나 동작 방법을 몰라도 상호작용이 가능하다.
- 인터페이스가 변경되지 않고 단순히 내부 구성이나 작동 방식이 변경되는 것은 인터페이스 사용자에게 아무런 영향도 미치지 않는다.
- 인터페이스가 동일하기만 하다면 어떤 대상과도 상호작용할 수 있다.
메시지가 인터페이스를 결정한다.
- 객체가 다른 객체와 상호작용할 수 있는 유일한 방법은 '메시지 전송'이다.
- 객체의 인터페이스는 객체가 수신할 수 있는 메시지의 목록으로 구성되며 객체가 어떤 메시지를 수신할 수 있는지가 객체가 제공하는 인터페이스의 모양을 빚는다.
객체지향적인 사고 방식을 이해하기 위해 중요한 세 가지 원칙
- 좀 더 추상적인 인터페이스
- 최소 인터페이스
- 인터페이스와 구현 간에 차이가 있다는 점을 인식
- 구현: 객체를 구성하지만 공용 인터페이스에 포함되지 않는 모든 것(내부 구조와 작동 방식. 예) 메서드)
객체가 가져야 할 상태와 메서드 구현은 객체 내부에 속한다. 이 부분을 수정하더라도 객체 외부에 영향을 미쳐서는 안 된다. 객체 외부에 영향을 미치는 변경은 객체의 공용 인터페이스를 수정할 때뿐이다.
추상클래스와 인터페이스의 차이
- 인터페이스와 추상 클래스는 존재 목적이 다르다.
- 추상 클래스는 그 추상 클래스를 상속받아서 기능을 이용하고, 확장시키는 데 있고, 다중 상속의 모호성 때문에 하나만 상속받을 수 있다.
- 인터페이스는 함수의 껍데기만 있는데, 그 이유는 그 함수의 구현을 강제하기 위함이다. 구현을 강제함으로써 구현 객체의 같은 동작을 보장할 수 있다.
구조와 기능
구조
- 미래에 대비하는 가장 좋은 방법은 변경을 예측하는 것이 아니라 변경을 수용할 수 있는 선택의 여지를 설계에 마련해 놓는 것이다.
- 기능이 아닌 안정적인 구조를 중심으로 설계하자.
- 객체지향은 객체의 구조에 집중하고 기능이 객체의 구조를 따르게 만든다.
- 시스템 기능은 더 작은 책임으로 분할되고 적절한 객체에게 분배되기 때문에 기능이 변경되더라도 객체 간의 구조는 그대로 유지된다.
- 도메인 모델:
- 사용자가 프로그램을 사용하는 대상 분야를 도메인이라고 하고 모델은 대상을 단순화해서 표현한 것을 의미한다.
- 도메인 모델을 기반으로 설계하고 구현하는 것은 사용자가 도메인을 바라보는 관점을 그대로 코드에 반영할 수 있게 한다.
- 도메인에 대한 사용자의 관점을 반영해야 하는 이유는 사용자들이 누구보다도 도메인의 '본질적인' 측면을 가장 잘 이해하고 있기 때문이다.
- 본질적이라는 것은 변경이 적고 비교적 그 특성이 오랜 시간 유지된다는 것을 의미한다. 소프트웨어 개발의 가장 큰 적은 변경이며 변경은 항상 발생한다. 사용자 모델에 포함된 개념과 규칙은 비교적 변경될 확률이 적기 때문에 사용자 모델을 기반으로 설계와 코드를 만들면 변경에 쉽게 대처할 수 있을 가능성이 커진다.
기능
유스케이스:
- 사용자의 목표를 달성하기 위해 사용자와 시스템 간에 이뤄지는 상호작용의 흐름을 텍스트로 정리한 것
유스케이스의 가치는 사용자들의 목표를 중심으로 시스템의 기능적인 요구사항들을 이야기 형식으로 묶을 수 있다는 점이다.
산발적으로 흩어져 있는 기능에 사용자 목표라는 문맥을 제고함으로써 각 기능이 유기적인 관계를 지닌 체계를 이룰 수 있게 한다.
유스케이스는 공통의 사용자 목표를 통해 강하게 연관된 시나리오의 집합이다.
변경에 유연한 소프트웨어를 만들기 위해서는 유스케이스에 정리된 시스템의 기능을 도메인 모델을 기반으로 한 객체들의 책임으로 분배해야한다.
시스템은 사용자로부터 전송된 메시지를 수행하기 위해 책임을 수행하는 거대한 자율적인 객체이다. 시스템이라는 객체 안에는 더 작은 규모의 객체가 포함될 수 있다. 이제 시스템이 수행해야 하는 커다란 규모의 책임은 시스템 안에 살아가는 더 작은 크기의 객체들의 협력을 통해 구현될 수 있다.
마무리
개념 관점
- 개념 관점에서 설계는 도메인 안에 존재하는 개념과 개념들 사이의 관계를 표현한다. 도메인이란 사용자들이 관심을 가지고 있는 특정 분야나 주제를 말하며 소프트웨어는 도메인에 존재하는 문제를 해결하기 위해 개발된다. 이 관점은 사용자가 도메인을 바라보는 관점을 반영한다.
명세 관점
- 사용자의 영역인 도메인을 벗어나 개발자의 영역인 소프트웨어로 초점이 옮겨진다. 명세 관점은 도메인의 개념이 아니라 실제 소프트웨어 안에서 살아 숨쉬는 객체들의 책임에 초점을 맞추게 된다. 즉, 객체의 인터페이스를 바라보게 된다. 명세 관점에서 프로그래머는 객체가 협력을 위해 '무엇'을 할 수 있는가에 초점을 맞춘다. 인터페이스와 구현을 구분하자.
구현 관점
- 실제 작업을 수행하는 코드와 연관되어 있다. 구현 관점의 초점은 객체들이 책임을 수행하는데 필요한 동작하는 코드를 작성하는 것이다. 프로그래머는 '어떻게' 수행할 것인가에 초점을 맞추며 인터페이스를 구현하는데 필요한 속성과 메서드를 클래스에 추가한다.
세 가지 관점은 동일한 클래스를 세 가지 다른 방향에서 바라보는 것을 의미한다. 클래스는 세 가지 관점이라는 안경을 통해 설계와 관련된 다양한 측면을 드러낼 수 있다.
클래스가 은유하는 개념은 도메인(개념) 관점을, 클래스의 공용 인터페이스는 명세 관점을, 클래스의 속성과 메서드는 구현 관점을 반영한다.
클래스는 세 가지 관점을 모두 수용할 수 있도록 개념, 인터페이스, 구현을 함께 드러내야 하며 동시에 코드 안에서 세 가지 관점을 쉽게 식별할 수 있도록 깔끔하게 분리해야 한다.
'OOP | DDD' 카테고리의 다른 글
[도메인 주도 설계 핵심] DDD 요약 (0) | 2020.09.03 |
---|