시작

개발관련 도서를 처음으로 완독한 것 같다. 그동안 읽으려고 유명한 책들을 구매해서 시도해본 경험이 있지만, 내용이 어렵기도 하고 구매할 때와는 다른 나의 열정 덕분에 자연스래 방치된 책이 몇권 있다.

나는 망각곡선이 정확하다 못해 과하게 적용되는 사람이기 때문에 책을 읽고 내용을 기억하기 위해서는 고통스럽지만 정리라는 과정을 거쳐야한다는 것을 인정했다.

앞으로 책을 꾸준히 읽으면서 지식을 습득하겠다는 각오로 글을 쓴다.

사실 그동안 뭔가 내용을 정리할 때, 누가 봤을 때 흠잡을 곳이 없어야 된다는 생각이 강했고 그래서 있어보이게 정리해야된다는 생각이 있었다. (생각만 했다) 그래서 뭔가 정리하는 것에 스트레스를 받았고 굳이 해야하나라는 생각을 많이 했다. 적어도 당분간은 책을 읽고 내가 이해한 내용 그대로를 담백하게 정리해볼 생각이다. 공부를 하면서 늘 느끼지만 그동안 이해했다고 착각했던 것이 많다는 것이다. 나중에 내가 정리한 글을 봤을 때 우습다면, 그만큼 내가 성장했다는 것을 보여주는 지표가 될 수도 있겠다.


01. 협력하는 객체들의 공동체

객체지향을 설명할 때 현실 세계에 존재하는 다양한 물체와 개념들을 하나의 객체로 정의하고, 정의된 객체를 이용해 시스템을 구성한다고 얘기한다. 실제로 객체지향에 대해 배우면서 `현실세계의 모방`이라는 말을 들은 적이 있다. 이것은 객체지향의 추상적인 개념을 설명하기엔 적합하지만 실제로 현실세계와 객체지향으로 이루어진 시스템은 다르다. 객체지향 시스템은 현실세계를 참고하여 새롭게 창조된 하나의 소프트웨어 세계이다.

시스템을 자율성을 가진 객체로 구성한다. 각각의 객체는 역할과 책임을 가진다. 그리고 메시지를 이용해 서로 요청하고 응답하며 협력관계를 맺는다. 이렇게 각각의 유기적인 객체로 시스템의 기능을 구현하는 것이 객체지향 프로그램이다.

이 책에서 역할,책임,협력 이라는 용어를 자주 사용하고 강조한다. 조금 새로웠던 것은 역할이 책임의 집합이라는 개념이다. 각 객체는 여러가지 역할을 담당할 수 있다. 그리고 한 가지 역할을 여러 객체가 대체할 수 있다. 이런 유연성이 객체지향의 특징이자 장점이다.

협력관계를 이루는 객체들은 서로 메시지를 주고 받는다. 어떤 일을 수행하기 위해 다양한 객체가 연쇄적으로 요청과 응답을 수행한다. 요청받은 책임을 수행하기 위해서 객체는 상태와 행위를 가진다. 어떤 행위를 수행하기 위해서 상태가 활용된다. 요청에 따라 객체는 어떻게 해당 요청을 처리할지 스스로 결정한다.(메서드의 선택) 이렇게 실행시점에 어떤 메서드가 실행될지 결정되는 것도 객체지향의 특징이다. 요청의 처리를 각각의 객체가 자율적으로 처리하는 것이 곧 객체지향의 자율성이다. 각 객체가 상태와 행위를 가진 하나의 존재인 것이 객체지향의 캡슐화이다.

책에서 또 강조하는 것 중 하나는 클래스 =/ 객체 라는 것이다. 그동안 클래스와 객체는 동일한 개념이라고 생각했다. 그저 부르는 호칭의 차이라고 생각했다. 클래스는 객체를 코드로 구현하는 방법 중 하나이다.

02. 이상한 나라의 객체

객체는 상태와 행동 그리고 식별자를 가진다.
객체는 시간에 따라 상태가 변할 수 있는 가변 상태를 포함한다. 따라서 각 객체를 구분하기 위해서는 식별자라는 새로운 개념이 필요하다.

5년전 나와 지금의 나는 똑같은 나이다. 즉 상태가 변하더라도 고유한 나로 식별가능하다. 이러한 성질을 반영하기 위해 식별자를 사용한다.

객체의 행동은 상태에 의존적이다. 어떤 행동의 결과는 상태에 의존한다. 또 행동의 결과는 상태를 변경한다.

객체를 설계할 때는 행동을 먼저 생각한다. 상태를 먼저 생각하고 설계하는 것은 객체의 자율성과 캡슐화를 저해한다.

이 부분이 그동안 내가 객체지향을 잘못 사용하고 있었다는 점을 알려줬다. 항상 프로젝트를 설계하면 어떤 객체가 존재해야하고 해당 객체는 어떤 상태값들을 가져야하는 지를 현실세계와 프로그램을 동작을 대강 상상하며, 먼저 객체의 상태들을 정의했다. 그 후 필요한 메서드들을 작성하며 필요한 행동을 정의했다.
그러면서 자연스럽게 뭔가 필요한 상태값이 다른 객체에 존재하는 상황을 마주했고, 그럴 때마다 단순히 해당 값을 조회하고 변경하는 메서드들이 필요했다. 이것이 객체의 자율성을 낮추어 코드 변경을 어렵게 했던 것 같다.

객체지향은 현실세계의 은유이다. 현실세계의 개념을 이용해 객체들을 표현하지만 결코 같지 않다. 은유이기 때문에 보다 직관적으로 프로그램의 구조를 파악하고 상상할 수 있다. 객체는 현실세계의 개념보다 더 능동적이라는 특징이 있다. 예를 들어, 현실에서 음료수는 인간에 의해 마셔진다. 하지만 객체지향에서 자율성이 보장된 음료수 객체는 스스로 자신의 양을 줄이거나 늘리는 책임을 가진다.

03. 타입과 추상화

인간의 본능 중 하나는 복잡한 세계를 단순화하는 것이다. 그 과정에서 추상화가 사용된다.
추상화는 두가지 차원에서 이루어진다.
1. 차이점을 무시하고 공통점을 취함으로써 단순하게 만든다.
2. 중요한 부분의 강조를 위해 불필요한 세부사항을 제거한다.
ex) 지하철 노선도는 실제 지형과 거리를 무시하고 노선간 연결성에만 집중하여 본질적인 목적에 집중하였다.

여기서 '개념'이 등장한다. 추상화를 통해 하나의 개념을 만들어낸다. 그리고 개념은 객체를 분류하는데 사용된다. 객체를 분류하는 체라고 생각할 수 있다.
소프트웨어 세계에서 타입은 0과1로 이루어진 데이터를 어떻게 사용할 것인지 정의하는 도구이다. 그리고 이러한 타입의 목적과 개념의 정의는 일맥상통한다. 따라서 개념을 타입으로 정의한다.
타입을 통해 객체를 분류할 때는 행동을 기준으로 분류한다. 상태가 아니라 행동이라는 점이 핵심이다. 어떤 타입에 속하기 위해서는 해당 타입이 가지는 책임을 수행할 수 있어야 하고, 그 책임의 수행이 곧 행동이다. 행동을 어떤 방식으로 하는지는 상관없다. 즉 어떤 상태를 가지는지는 고려할 필요가 없다. 상태를 기준으로 분류하면 캡슐화가 깨지고 유연한 객체지향 구조가 파괴된다.

타입에는 계층이 존재한다. 일반화와 특수화, 슈퍼타입과 서브타입이라고 부른다. 슈퍼타입은 서브타입의 추상화이다. 즉 중요한 부분을 남기고 세부사항을 제거함으로써 더 단순화 시킨 타입이다. 서브타입을 슈퍼타입의 책임을 모두 수행할 수 있다. 이는 서브타입이 슈퍼타입을 대체할 수 있음을 의미한다. 이것이 객체지향이 다형성이라는 특징을 가지는 이유이다.

중요한 것은 타입과 클래스는 같지 않다는 것이다. 클래스는 타입을 구현하는 하나의 방법일 뿐이다. 객체를 분류하는 것은 타입이고, 타입을 나누는 기준은 객체가 수행하는 행동이다.

책에서 반복적으로 강조하는 내용 중 하나는 행동의 중요성이다. 3장에서는 슈퍼타입과 서브타입을 다루는데, 생각해보면 기존에 코딩을 하면서 상태를 기준으로 부모객체와 자식객체를 생각했던 것 같다. 비슷한 상태값을 가지면 상속을 통해 슈퍼타입과 서브타입 관계를 맺어야한다고 생각했다. 이러한 부분이 상태를 인터페이스에 노출하여 객체지향의 캡슐화를 파괴했을 것이다.

04. 역할, 책임, 협력

훌륭한 객체지향 설계란 조화를 이루며 적극적으로 상호작용하는 협력적인 객체를 창조하는 것이다. 비록 따로 떼어놓고 봤을 때는 비합리적이더라도 말이다.
어떠한 기능을 수행하기 위해 객체들은 서로 협력한다. 협력은 요청과 응답으로 이루어진다. 객체는 메시지를 통해 다른 객체에게 요청하고 요청받은 객체는 이에 응답함으로써 협력의 흐름이 구성된다. 메시지를 받는다는 것은 해당 요청을 수행할 책임이 있다는 것을 의미한다. 그리고 이러한 책임의 집합이 역할이다. 역할이라는 개념을 도입함으로써 우리는 조금 더 유연한 프로그램을 구성할 수 있다. 역할은 여러 종류의 객체가 수행할 수 있다. 역할이 가진 책임을 수행할 수 있는 객체라면 어떤 객체라도 해당 역할을 대체할 수 있다. 즉 역할은 협력의 추상화이다. 이를 통해 중복되는 협력의 형태를 하나로 단순화하고, 객체를 바꿔가면서 다양한 협력을 구성할 수 있다. 이 책에서는 판사와 집행관, 증인의 협력을 예시로 들었다.
객체지향 설계를 함에 있어서도 협력을 중심으로 설계해야한다. 먼저 협력관계를 생각하고, 그 때 필요한 책임을 추려내서 객체에게 할당하는 것이다. 이것이 책임-주도 설계의 원리이다. 이는 행동을 먼저 생각하고 객체를 설계하라는 말과 같은 의미가 된다.
이러한 책임-주도 설계의 베스트 프렉티스를 모아둔 것이 디자인 패턴이다. 디자인 패턴을 적절히 참고하고 수정하면 보다 효율적으로 정확한 객체지향 설계를 수행할 수 있다.
또 한가지 기법은 테스트-주도 개발이다. 테스트를 먼저 작성하고 필요한 객체들을 정의하는 기법이다. 테스트 자체가 하나의 기능을 테스트하는 것이고 이는 협력의 결과로 볼 수 있다. 즉 자연스럽게 협력을 먼저 생각하게 되고, 협력에 필요한 역할과 책임을 순서로 고려하고 작성하게 된다. 따라서 보다 체계적으로 객체지향 설계를 수행할 수 있게 되는 것이라고 생각한다.

테스트-주도 개발이 각광받는 이유에 대해서 조금 더 이해할 수 있게 됐다. 객체를 설계함에 있어서 역할을 먼저 고려해야한다는 점을 알았다. 아직 헷갈리는 점은 자바에서는 상속받을 수 있는 슈퍼클래스는 하나인데, 만약 역할과 개념적 일반화가 둘 다 존재한다면 어떤 식으로 설계해야할지 잘 모르겠다.
이전 장과 마찬가지로 그동한 그럴듯한 객체는 그 이름에 맞는 상태변수나 메서드를 가져야한다는 생각이 있었다. 하지만 협력을 먼저 생각하다보면 전혀 다른 양상이 될 수 있다는 것을 알았다. 그리고 책에서는 후자를 지향해야한다고 말하는 것 같다.

05. 책임과 메세지

자율적인 책임

객체지향의 핵심은 객체에게 적절한 책임을 부여하는 것이다. 여기서 적절한 책임이란 객체의 자율성을 보장하는 책임이다.
그 방법 중 하나는 어떻게가 아니라 무엇을 수행할 것인지 정의하는 것이다. 무엇을 수행할 것인지 책임을 부여한다면, 어떻게 수행할 지는 객체 스스로 판단할 수 있다. 이때 객체의 자율성이 보장된다.

예를들어, 객체1이 회의 내용을 기록할 책임을 가진다고 하자. 이때 수행할 수 있는 방법은 노트북활용, 필기, 휴대폰 활용, 녹음 등으로 다양하다. 여기서 만약 "노트북을 활용해 기록하라" 라는 책임을 가지는 것과 "기록하라" 라는 책임을 가지는 것은 객체의 자율성 차이가 있다. 후자의 경우는 방법을 객체가 선택함으로써 자율성이 보장된다.

메시지, 다형성

객체지향 패러다임의 중요한 특성 중 하나는 다형성이다. 다형성이란 서로 다른 객체가 동일한 메시지에 대해 서로 다르게 반응하는 것을 의미한다. 이것이 가능한 이유는 메시지와 메서드의 구분 덕분이다. 메시지는 외부에서 확인할 수 있는 객체의 책임이다. 메시지를 통해 외부 객체와 상호작용하고 요청받은 책임을 수행한다. 이때 수행하는 방법이 메서드이다. 메시지는 외부 인터페이스를, 메서드는 내부 구현을 의미한다.

중요한 것은 메시지와 메서드를 통해 객체의 내부와 외부를 구분하는 것이다. 객체의 내부를 외부에 노출시키지 않았을 때 비로소 객체지향의 장점이 드러난다. 내부의 변화가 외부에 영향을 끼치지 않는다. 인터페이스가 동일한 객체들간의 대체도 가능해진다. 유연한 프로그래밍이 가능해진다. 이를 위해서 이전부터 언급하던 책임을 먼저 설계하고 이후에 객체에 할당하는 책임-주도 설계가 사용된다. 다시 말하면 메시지를 먼저 생각하고 해당 책임을 수행할 객체를 선정하는 것이다. 이렇게 설계했을 때 캡슐화가 잘 된 객체들로 구성된 프로그램이 만들어질 수 있다.

인터페이스와 구현 분리 원칙

  • 추상적인 인터페이스
    객체의 자율성을 보장하기 위해 적당히 추상적인 책임을 부여한다. 너무 구체적인 인터페이스보다 추상적인 인터페이스가 수신 객체의 자율성을 보장한다.
  • 최소 인터페이스
    외부에서 알 필요가 없는 인터페이스는 최대한 노출하지 않는다. 이로써 객체 내부의 수정이 외부에 미치는 영향을 최소화할 수 있다. 책임-주도 설계를 따르면 최소 인터페이스를 얻을 수 있다.
  • 인터페이스와 구현간의 차이 인식
    객체의 메서드와 상태같은 내부 구현요소를 외부에 노출하지 않는다. 여기서 캡슐화가 사용된다.

책임의 자율성이 협력의 품질을 결정한다

  • 자율적인 책임은 협력을 단순하게 만든다.
    책임을 적절하게 추상화함으로써 세부적인 사항들을 무시한다. 협력의 표현이 단순해진다.

  • 자율적인 책임은 외부와 내부를 명확하게 분리한다.
    자율적인 책임은 외부에서 내부 정보를 알 필요가 없게 한다. 요청하는 객체가 몰라도 되는 사적인 부분이 객체 내부로 캡슐화되기 때문에 인터페이스와 구현이 분리된다.

  • 책임이 자율적일 경우 책임을 수행하는 외부적인 방법을 변경하더라도 외부에 영향을 미치지 않는다.
    외부에서 내부 작동방식을 모른다는 것은 작동방식이 변해도 외부에 영향을 끼치지 않는다는 것을 의미한다. 변경의 파급효과가 객체 내부로 캡슐화되기 때문에 두 객체간의 결합도가 낮아진다.

  • 자율적인 책임은 협력의 대상을 다양하게 선택할 수 있는 유연성을 제공한다.
    계속 같은 맥락에서 두 객체간 결합도가 낮다면, 대상을 대체할 수 있다. 즉 추상적인 책임이라면 해당 책임을 수행할 수 있는 객체의 스펙트럼이 더 넓어진다. 따라서 설계가 유연해지고 재사용성이 높아진다.

  • 객체가 수행하는 책임들이 자율적일 수록 객체의 역할을 이해하기 쉬워진다.
    추상적인 책임은 인터페이스 자체를 추상적으로 만든다. 이렇게 되면 세부적인 내용을 담지 않기 때문에 해당 인터페이스의 역할이 명확해진다.

    결국 객체지향의 핵심은 책임-주도 설계를 통해 객체들을 자율적으로 만드는 것이다. 이를 통해 객체지향의 장점을 누릴 수 있다.

마무리

책에서 전달하는 핵심 내용은 5장까지라고 생각한다. 6장과 7장은 이해를 돕기 위한 부록같은 느낌이다. 원래 장별로 내용을 정리할 생각은 없었지만 조금 체계적으로 하고자 그렇게 해보았다. 6,7장은 나중에 다시 읽어보고 내용을 정리해야겠다.

  1. 객체 지도
  2. 함께 모으기

0개의 댓글

Powered by GraphCDN, the GraphQL CDN