Post

설계 품질과 트레이드오프

4. 설계 품질과 트레이드오프

책임 주도 설계라는 이름에서 알 수 있는 것처럼 역할, 책임, 협력 중에서 가장 중요한 것은 책임

책임이 객체지향 애플리케이션 전체의 품질을 결정

객체지항 설계란 올바른 객체에게 올바른 책임을 할당하면서 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 활동

설계는 변경을 위해 존재하고 변경에는 어떤 식으로든 비용이 발생

훌륭한 설계란 합리적인 비용 안에서 변경을 수용할 수 있는 구조를 만드는 것

적절한 비용 안에서 변경할 수 있는 설계는 응집도가 높고 서로 느슨하게 결합돼 있는 요소로 구성

결합도와 응집도를 합리적인 수준으로 유지하기 위해서는 객체의 상태가 아닌 객체의 행동에 초점

데이터 중심의 설계를 살펴보고 객체지향 구조와의 차이점 확인

4.1 데이터 중심의 영화 예매 시스템

객체지향 설계에서는 두 가지 방법을 이용해 시스템을 객체로 분할

  1. 상태를 분할의 중심축으로 삼음 (상태 == 데이터)
  2. 책임을 분할의 중심축으로 삼음

데이터 중심의 관점에서 객체는 자신이 포함하고 있는 데이터를 조작하는 데 필요한 오퍼레이션을 정의

책임 중심의 관점에서 객체는 다른 객체가 요청할 수 있는 오퍼레이션을 위해 필요한 상태를 보관

객체의 상태 -> 구현 -> 불안정 -> 변하기 쉬움 -> 캡슐화 무너짐 -> 인터페이서 변경 초래 -> 변경에 취약

객체의 책임 -> 인터페이스 -> 캡슐화 -> 변경 파장 방지 -> 안정적인 설계

데이터를 준비하자

데이터 중심의 설계는 ‘데이터가 무엇인가’를 묻는 것으로 시작

데이터 중심의 설계에서는 객체가 포함해야 하는 데이터에 집중

캡슐화를 위해 접근자(accessor)수정자(mutator) 추가 (getter + setter)

4.2 설계 트레이드오프

캡슐화

상태와 행동을 하나의 객체 안에 모으는 이유는 객체의 내부 구현을 외부로부터 감추기 위해서

객체지향이 강력한 이유는 한 곳에서 일어난 변경이 전체 시스템에 영향을 끼치지 않도록 파급효과를 적절하게 조절할 수 있는 장치를 제공하기 때문

변경될 가능성이 높은 부분을 구현, 상대적으로 안정적인 부분을 인터페이스

객체를 설계하기 위한 가장 기본적인 아이디어는 변경의 정도에 따라 구현과 인터페이스를 분리하고 외부에서는 인터페이스에만 의존하도록 관계를 조절하는 것

객체지향에서 가장 중요한 원리는 캡슐화

객체지향 설계의 가장 중요한 원리는 불안정한 구현 세부사항을 안정적인 인터페이스 뒤로 캡슐화하는 것

설계가 필요한 이유는 요구사항이 변경되기 때문이고, 캡슐화가 중요한 이유는 불안정한 부분과 안정적인 부분을 분리해서 변경의 영향을 통제할 수 있기 때문

캡슐화란 변경 가능성이 높은 부분을 객체 내부로 숨기는 추상화 기법

응집도와 결합도

응집도: 모둘에 포함된 내부 요소들이 연관돼있는 정도

응집도는 객체 또는 클래스에 얼마나 관련 높은 책임들을 할당했는지를 나타낸다.

결합도: 의존성의 정도를 나타내며 다른 모듈에 대해 얼마나 많은 지식을 갖고 있는지를 나타내는 척도

결합도는 객체 또는 클래스가 협력에 필요한 적절한 수준의 관계만을 유지하고 있는지를 나타냄

일반적으로 좋은 설계란 높은 응집도와 낮은 결합도를 가진 모듈로 구성된 설계

좋은 설계란 오늘의 기능을 수행하면서 내일의 변경을 수용할 수 있는 설계

응집도와 결합도는 변경과 관련된 것

변경의 관점에서 응집도란 변경이 발생할 때 모듈 내부에서 발생하는 변경의 정도

응집도가 높을수록 변경의 대상과 범위가 명확해지기 때문에 코드를 변경하기 쉬워진다.

변경의 관점에서 결합도는 한 모듈이 변경되기 위해서 다른 모듈의 변경을 요구하는 정도

결합도가 높을수록 함께 변경해야 하는 모듈의 수가 늘어나기 때문에 변경하기가 어려워진다.

일반적으로 변경될 확률이 매우 적은 안정적인 모듈에 의존하는 것은 문제가 되지 않는다.(ex. String, ArrayList)

직접 작성한 코드의 경우에는 낮은 결합도를 유지하려고 노력해야 한다.

캡슐화의 정도가 응집도와 결합도에 영향을 미친다.

4.3 데이터 중심의 영화 예매 시스템의 문제점

데이터 중심의 설계는 캡슐화를 위반하고 객체의 내부 구현을 인터페이스의 일부로 만든다.

반면 책임 중심 설계는 객체의 내부 구현을 안정적인 인터페이스 뒤로 캡슐화한다.

데이터 중심의 설계가 가진 대표적인 문제점

  • 캡슐화 위반
  • 높은 결합도
  • 낮은 응집도

캡슐화 위반

getFee 메서드와 setFee 메서드 Moviee 내부에 Money 타입의 fee라는 이름의 인스턴스 변수가 존재한다는 사실을 퍼블릭 인터페이스에 노골적으로 드러낸다 -> 캡슐화 위반

접근자와 수정자에 과도하게 의존하는 설계 방식 -> 추측에 의한 설계 전략(designing-by-guessing strategy)

객체가 사용될 협력을 고려하지 않고 객체가 다양한 상황에서 사용될 수 있을 것이라는 막연한 추측을 기반으로 설계를 진행

그래서 대부분의 내부 구현이 퍼블릭 인터페이스에 그대로 노출 -> 캡슐화 위반

높은 결합도

객체 내부의 구현이 객체의 인터페이스에 드러난다는 것은 클라이언트가 구현에 강하게 결합된다는 것을 의미

데이터 중심 설계는 객체의 캡슐화를 약화시키기 때문에 클라이언트가 객체의 구현에 강하게 결합됨

이 결합도로 인해 어떤 데이터 객체를 변경하더라도 제어 객체를 함께 변경할 수밖에 없다.

데이터 중심의 설계는 전체 시스템을 하나의 거대한 의존성 덩어리로 만들어 버리기 때문에 어떤 변경이라도 일단 발생하고 나면 시스템 전체가 요동침

낮은 응집도

각 모듈의 응집도를 수정하기 위해서는 코드를 수정하는 이유가 무엇인지 살펴보아야 한다.

낮은 응집도의 문제

  • 변경의 이유가 서로 다른 코드들을 하나의 모듈안에 뭉쳐놓았기 때문에 변경과 아무 상관이 없는 코드들이 영향을 받게 된다.
  • 하나의 요구사항 변경을 반영하기 위해 동시에 여러 모듈을 수정해야 한다.

단일 책임 원칙(Single Responsibility Principle, SRP)

클래스는 단 한 가지의 변경 이유만 가져야 함

4.4 자율적인 객체를 향해

캡슐화를 지켜라

캡슐화는 설계의 제1원리

데이터 중심의 설계가 낮은 응집도와 높은 결합도라는 문제로 몸삶을 앓게 된 근본적인 원인은 바로 캡슐화의 원칙을 위반했기 때문

객체는 자신이 어떤 데이터를 가지고 있는지를 내부에 캡슐화하고 외부에 공개해서는 안된다.

객체에게 의미 있는 메서드는 객체가 책임져야 하는 무언가를 수행하는 메서드

속성의 가시성을 private으로 설정했다고 해도 접근자와 수정자를 통해 속성을 외부로 제공하고 있다면 캡슐화 위반

스스로 자신의 데이터를 책임지는 객체

상태와 행동을 객체라는 하나의 단위로 묶는 이유는 객체 스스로 자신의 상태를 처리할 수 있게하기 위해서

객체 내부에 저장되는 데이터보다 객체가 협력에 참여하면서 수행할 책임을 정의하는 오퍼레이션이 더 중요

  • 이 객체가 어떤 데이터를 포함해야 하는가?
  • 이 객체가 데이터에 대해 수행해야 하는 오퍼레이션은 무엇인가?

두 질문을 조합하면 객체의 내부 상태를 저장하는 방식과 저장된 상태에 대해 호출할 수 있는 오퍼레이션의 집합을 얻을 수 있다.

4.5 하지만 여전히 부족하다

캡슐화 위반

내부 구현의 변경이 외부로 퍼져나가는 파급 효과(ripple effect)는 캡슐화가 부족하다는 명백한 증거

캡슐화의 진정한 의미

캡슐화는 변경될 수 있는 어떤 것이라도 감추는 것

속성의 타입이건, 할인 정책의 종류건 상관 없이 내부 구현의 변경으로 인해 외부의 객체가 영향을 받는다면 캡슐화를 위반한 것

설계에서 변하는 것이 무엇인지 고려하고 변하는 개념을 캡슐화해야 한다.

킵슐화란 변하는 어떤 것이든 감추는 것

높은 결합도

낮은 응집도

4.6 데이터 중심 설계의 문제점

캡슐화를 위반했기 때문

캡슐화를 위반 -> 변경에 취약

데이터 중심의 설계가 변경에 취약한 이유

  • 데이터 중심의 설계는 본질적으로 너무 이른 시기에 데이터에 관해 결정하도록 강요
  • 데이터 중심의 설계에서는 협력이라는 문맥을 고려하지 않고 객체를 고립시킨 채 오퍼레이션을 결정

데이터 중심 설계는 객체의 행동보다는 상태에 초점을 맞춘다

데이터 주도 설계는 시작하는 처음부터 데이터에 관해 결정하도록 강요하기 때문에 너무 이른 시기에 내부 구현에 초점을 맞추게 된다.

접근자와 수정자는 public 속성과 큰 차이가 없기 때문에 객체의 캡술화는 완전히 무너짐 -> 설계가 실패한 이유

객체의 인터페이스는 구현을 캡슐화하는 데 실패하고 코드는 변경에 취약 -> 설계가 실패한 이유

결론적으로 데이터 중심의 설계는 너무 이른 시기에 데이터에 대해 고민하기 때문에 캡슐화에 실패

데이터 중심 설계는 객체를 고립시킨 채 오퍼레이션을 정의하도록 만든다

중요한 것은 객체가 다른 객체와 협력하는 방법

데이터 중심 설계에서 초점은 객체의 외부가 아니라 내부로 향한다.

객체의 구현이 이미 결정된 상태에서 다른 객체와의 협력 방법을 고민하기 때문에 이미 구현된 객체의 인터페이스를 억지로 끼워맞출 수밖에 없다. -> 설계가 변경에 유연하게 대처 불가능

참조

  1. 오브젝트
This post is licensed under CC BY 4.0 by the author.