본 글은 필자가 오브젝트(조영호, 위키북스, 2019.06)라는 책을 읽으며 인상 깊었던 글귀들을 정리해 놓은 글이다.
필자는 대학교에서 OOP(Object Oritented Programming) 수업을 수강했었음에도,
해당 책에 있는 내용들이 매우 값지게 느껴졌다.
'단순히 OOP가 무엇인가?'의 관점을 넘어 OOP를 실천하고자 하는 프로그래머들에게 하나의 지침서가 될 수 있는 내용이라고 생각한다.
본 글은 시리즈로 발행될 예정이며, 추후에 정리하는대로 후속편이 업로드 될 예정이다.
객체는 상태(state)와 행동(behavior)를 함께 가지는 복합적인 존재이다.
객체는 스스로 판단하고 행동하는 자율적인 존재다.
설계가 필요한 이유는 변경을 관리하기 위해서라는 것을 기억하라
부모 클래스에 기본적인 알고리즘의 흐름을 구현하고 중간에 필요한 처리를 자식 클래스에게 위임하는 디자인 패턴을 Template Method 패턴이라고 부른다.
한 가지 간과해서는 안 되는 사실은 코드의 의존성과 실행 시점의 의존성이 다르면 다를수록 코드를 이해하기 어려워진다는 것이다. (중략) 반면 코드의 의존성과 실행시점의 의존성이 다르면 다를수록 코드는 더 유연해지고 확장 가능해진다. (중략) 무조건 유연한 설계도, 무조건 읽기 쉬운 코드도 정답이 아니다.
상속이 가치 있는 이유는 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받을 수 있기 때문이다.
상속은 두 가지 관점에서 설계에 안 좋은 영향을 미친다. 하나는 상속이 캡슐화를 위반한다는 것이고, 다른 하나는 설계를 유연하지 못하게 만든다는 것이다.
객체지향의 본질은 협력하는 객체들의 공동체를 창조하는 것이다.
객체지향 패러다임의 관점에서 핵심은 역할(role), 책임(responsibility), 협력(collaboration)이다. 클래스, 상속, 지연 바인딩이 중요한 것은 아니지만 다분히 구현 측면에 치우쳐져 있다.
이처럼 객체들이 애플리케이션의 기능을 구현하기 위해 수행하는 상호작용을 협력이라고 한다. 객체가 협력에 참여하기 위해 수행하는 로직은 책임이라고 부른다. 객체들이 협력 안에서 수행하는 책임들이 모여 객체가 수행하는 역할을 구성한다.
메시지가 객체를 선택해야 하는 두 가지 중요한 이유가 있다. 첫 째, 객체가 최소한의 인터페이스를 가질 수 있게 된다. 둘 째, 객체는 충분히 추상적인 인터페이스를 가질 수 있게 된다.
객체의 행동은 객체가 협력에 참여할 수 있는 유일한 방법이다. 객체가 협력에 적합한지를 결정하는 것은 그 객체의 상태가 아니라 행동이다.
추상화의 첫 번째 장점은 세부 사항에 억눌리지 않고도 상위 수준의 정책을 쉽고 간단하게 표현할 수 있다는 것이다. (중략) 추상화의 두 번쨰 장점은 설계를 유연하게 만들 수 있다는 것이다.
객체지향 설계란 올바른 객체에게 올바른 책임을 할당하면서 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 활동이다.
설계는 변경을 위해 존재하고 변경에는 어떤 식으로든 비용이 발생한다. 훌륭한 설계란 합리적인 비용 안에서 변경을 수용할 수 있는 구조를 만드는 것이다.
객체의 상태는 구현에 속한다. 구현은 불안정하기 때문에 변하기 쉽다. (중략) 그에 비해 객체의 책임은 인터페이스에 속한다. (중략) 따라서 책임에 초점을 맞추면 상대적으로 변경에 안정적인 설계를 얻을 수 있게 된다.
변경될 가능성이 높은 부분을 구현이라고 부르고 상대적으로 안정적인 부분을 인터페이스라고 부른다는 사실을 기억하라.
복잡성을 다루기 위한 가장 효과적인 도구는 추상화다. 다양한 추상화 유형을 사용할 수 있지만 객체지향 프로그래밍에서 복잡성을 취급하는 주요한 방법은 캡슐화다.
캡슐화는 설계의 제1원리다. 데이터 중심의 설계가 낮은 응집도와 높은 결합도라는 문제로 몸살을 앓게 된 근본적인 원인은 바로 캡슐화의 원칙을 위반했기 때문이다.
객체는 스스로의 상태를 책임져야 하며 외부에서는 인터페이스에 정의된 메서드를 통해서만 상태에 접근할 수 있어야 한다. 여기서 말하는 메서드는 단순히 속성 하나의 값으 반환하거나 변경하는 접근자나 수정자를 의미하는 것은 아니다. 객체에게 의미있는 메서드는 객체가 책임져야 하는 무언가를 수행하는 메서드다.
우리가 상태와 행동을 객체라는 하나의 단위로 묶는 이유는 객체 스스로 자신의 상태를 처리할 수 있게 하기 위해서다. 객체는 단순한 데이터 제공자가 아니다.
객체지향 애플리케이션을 구현한다는 것은 협력하는 객체들의 공동체를 구축한다는 것을 의미한다. (중략) 올바른 객체지향 설계의 무게 중심은 항상 객체의 내부가 아니라 외부에 맞춰져 있어야 한다.
챕터 5장에서는 GRASP 패턴을 다루기도 했다.
GRASP(General Responsibility Assignment Software Patterns)이란?
각 객체에 책임을 부여하는 원칙으로, 9가지의 원칙으로 이루어져 있는 패턴이다.
구체적인 지침보다는 철학에 가까우며, 객체에 책임을 부여할 때 고려할 수 있는 원칙이다.
데이터 중심의 설계는 행동보다 데이터를 먼저 결정하고 협력이라는 문맥을 벗어나 고립된 객체의 상태에 초점을 맞추기 때문에 캡슐화를 위반하기 쉽고, 요소들 사이의 결합도가 높아지며, 코드를 변경하기 어려워진다.
책임 할당 과정은 일종의 트레이드오프 활동이다. 동일한 문제를 해결할 수 있는 다양한 책임 할당 방법이 존재하며, 어떤 방법이 최선인지는 상황과 문맥에 따라 달라진다.
데이터보다 행동을 먼저 결정하라 협력이라는 문맥 안에서 책임을 결정하라
객체에게 할당된 책임의 품질은 협력에 적합한 정도로 결정된다. (중략) 책임은 객체의 입장이 아니라 객체가 참여하는 협력에 적합해야 한다.
클래스를 결정하고 그 클래스의 책임을 찾아 나서는 대신 메시지를 결정하고 이 메시지를 누구에게 전송할지 찾아보게 되었다. (중략) 객체를 가지고 있기 때문에 메시지를 보내는 것이 아니다. 메시지를 전송하기 때문에 객체를 갖게 된 것이다.
책임을 정보 전문가(INFORMATION EXPERT), 즉 책임을 수행하는데 필요한 정보를 가지고 있는 객체에게 할당하라.
LOW COPLING PATTERN : 설계의 전체적인 결합도가 낮게 유지되도록 책임을 할당하라.
HIGH COHESION PATTERN : 높은 응집도를 유지할 수 있게 책임을 할당하라.
CREATOR PATTERN 객체 A를 생성해야 할 때 어떤 객체에게 객체 생성 책임을 할당해야 하는가? B가 A 객체를 포함하거나 참조한다. B가 A 객체를 기록한다. B가 A 객체를 긴밀하게 사용한다. B가 A 객체를 초기화하는데 필요한 데이터를 가지고 있다.
코드를 통해 변경의 이유를 파악할 수 있는 첫 번째 방법은 인스턴스 변수가 초기화되는 시점을 살펴보는 것이다. 응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함꼐 초기화한다.
코드를 통해 변경의 이유를 파악할 수 있는 두 번째 방법은 메서드들이 인스턴스 변수를 사용하는 방식을 살펴보는 것이다. (중략) 메서드들이 사용하는 속성에 따라 그룹이 나뉜다면 클래스의 응집도가 낮다고 볼 수 있다.
POLYMORFISM PATTERN : 타입을 명시적으로 정의하고 각 타입에 다형적으로 행동하는 책임을 할당하다라
PROTECTED VARIATIONS PATTERN : 설계에서 변하는 것이 무엇인지 고려하고 변하는 개념을 캡슐화하라.
객체 디자인에서 기본이 되는 것 중의 하나는 책임을 어디에 둘지를 결정하는 것이다. 나는 십년 이상 객체를 가지고 일했지만 처음 시작할 때는 여전히 적당한 위치를 찾지 못한다. 늘 이런 점이 나를 괴롭혔지만, 이제는 이런 경우에 리팩터링을 사용하면 된다는 것을 알게 되었다. - Martin Fowler
긴 메서드는 응집도가 낮기 때문에 이해하기도 어렵고 재사용하기도 어려우며 변경하기도 어렵다.
작고, 명확하며, 한 가지 일에 집중하는 응집도 높은 메서드는 변경 가능한 설계를 이끌어내는 기반이 된다.
책임 주도 설계 방법에 익숙하지 않다면 일단 데이터 중심으로 구현한 후 이를 리팩토링하더라도 유사한 결과를 얻을 수 있다는 것이다.
Notion에서 Postgres를 샤딩하면서 얻은 교훈 (번역) (0) | 2022.06.20 |
---|---|
Cache 파헤치기 (0) | 2022.02.15 |
CQRS 패턴, 코드에 순식간에 적용해보기 (0) | 2021.02.14 |
댓글 영역