Skip to main content

오브젝트 - 조영호

1장

  • 소프트웨어 모듈의 세 가지 목적

    1. 실행 중에 제대로 동작하는 것.

    2. 변경에 용이하여야한다.

    3. 코드를 읽는 사람과 쉽게 의사소통이 가능해야한다.

      by. 로버트 마틴- 클린 소프트웨어: 애자일 원칙과 패턴, 그리고 실천 방법

  • 객체지향적인 설계(↔ 절차지향)를 통해 위의 목적들을 충족시킬 수 있다.

    • 1장의 예제에서 각 객체는 데이터들만을 보관하고, 로직은 하나의 객체 메소드에서 짜여져있는 것을 각각의 객체에 챔임을 분리하여 기능을 구현하고 의존성을 최대한 낮추는 방향으로 분리하여 객체들끼리 상호작용하게끔 코드를 개선함.
    • 응집도를 높이고, 결합도를 낮추었다.
  • 결국 설계는 트레이드오프의 산물이다. 어떤 경우에도 모든 사람들을 만족시킬 수 있는 설계를 만들 수는 없다. / 설계는 균형의 예술이다. 이러한 트레이드오프 과정이 설계를 어려우면서도 흥미진진한 작업으로 만든다.

2장

  • 영화예매시스템을 예제로 앞으로 이해하게 될 다양한 주제들을 살펴볼 것.
  • 진정한 객체지향 패러딤으로의 전환은 ‘클래스’가 아닌 ‘객체’에 초점을 맞추는 것이다.
    • 어떤 클래스가 필요한지를 고민하기 전에 어떤 객체들이 필요한지 고민하라. 클래스는 공통적인 상태와 행동을 공유하는 객체들을 추상화한 것이다.
    • 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐라.
  • 클래스 구현하기.
    • Screening
      • 인스턴스 변수의 가시성은 private, 메서드의 가시성은 public
      • 클래스의 경계를 구분 짓는 것이 중요하다. 그 이유는 경계의 명확성이 객체의 자율성을 보장하기 때문이다. 또 더 중요한 이유로 프로그래머에게 구현의 자유를 제공하기 때문이다.
      • 일반적으로 객체의 상태는 숨기고 행동만 외부에 공개해야한다.
      • 객체의 자율성을 보장 - 객체지향의 핵심은 스스로 상태를 관리하고, 판단하고, 행동하는 자율적인 객체들의 공동체를 구성하는 것이다. 객체가 자율적으로 행동하려면 외부의 간섭을 최소화 해야할 것이며 외부의 간섭을 최소화하기 위해서 경계를 구분짓는다.
      • 프로그래머에게 구현의 자유를 제공 - 경계를 구분짓음으로써 클래스를 구현하는 입장에서는 클래스 내부에 public(외부에서 사용되는 것들)을 제외하고 private은 내부에서만 사용됨이 보장되기 때문에 외부에 대한 영향을 고려하지 않고 자유롭게 구현 및 수정이 가능해진다. 클래스를 사용하는 클라이언트 프로그래머 입장에서는 내부 구현은 무시한채 인터페이스만 알고 있어도 클래스를 사용할 수 있게 된다.
      • 경계를 짓는 것에 있어서(객체의 변경을 관리하는 기법) 다양한 방법이 있으며 대표적인 것이 ‘접근 제어’
    • Reservation
      • 객체들 사이에 이뤄지는 상호작용 - 협력(Collaboration)
      • 메시지와 메스드를 구분하는 것은 중요하다. 메시지와 메서드의 구분에서부터 다형성의 개념이 출발한다.
    • Movie, DiscountPolicy, DiscountCondition
      • 상속, 구현, 다형성, 추상화,
      • 설계가 유연해질수록 코드를 이해하고 디버깅하기는 점점 더 여려워지며, 유연성을 억제하면 코드를 이해하고 디버깅하기는 쉬워지지만 재사용성과 확장 가능성은 낮아진다. 유연성과 가독성 사이에서 트레이드 오프가 존재.
    • 코드 재사용의 관점에서 상속과 합성 (p70)
      • 코드 재사용은 어떠한 객체에서 다른 객체에 의존하여 특정 메소드를 사용하는 것도 재사용.
      • 상속은 캡슐화를 위반하며 설계를 유연하지 못하게 만듬 (부모 클래스와 자식 클래스의 결합도가 강해서)
      • 합성은 상속이 가지는 두 가지 문제점을 모두 해결한다.
      • 상속을 절대 사용하지 말라는 것은 아니며, 코드 재사용을 위해서는 합성을 선호하는 것이 옳으며, 다형성을 위해 인터페이스를 재사용하는 경우에는 상속과 합성을 조합해서 사용할 수 밖에 없다.

3장 역할, 책임, 협력

  • 객체지향 패러다임의 관점에서 핵심은 역할, 책임, 협력이다. 클래스, 상속, 지연 바인딩 등은 구현 측면의 것들.

  • 객체지향 원칙을 따르는 어플리케이션의 제어 흐름은 어떤 하나의 객체에 의해 통제되지 않고 다양한 객체들 사이에 균형 있게 분배되는 것이 일반적.

    • 이 과정에서 객체들이 수행하는 상호작용을 ‘협력’
    • 객체가 협력에 참여하기 위해 수행하는 로직은 ‘책임’
    • 객체들이 협력 안에서 수행하는 책임들이 모여 객체가 수행하는 ‘역할’을 구성.
  • 협력

    • 협력이란 어떤 객체가 다른 객체에게 무엇인가를 요청하는 것 (메세지 전송(message sending))
      • 메세지를 수신한 객체는 메서드를 실행해 요청에 응답한다.
      • 여기서 포인트는 객체가 요청받은 메시지를 처리할 방법을 스스로 선택한다는 것이며, 이것은 객체가 자신의 일을 스스로 처리할 수 있는 자율적인 존재라는 것을 의미한다.
    • 두 객체가 상호작용을 통해 더 큰 책임을 수행.
    • 객체 사이의 협력을 설계할 때는 객체를 서로 분리된 인스턴스가 아닌 협력하는 파트너로 인식.
    • 객체의 행동(method)는 협력이 결정하며, 객체의 상태(멤버 변수)는 행동이 결정한다.
  • 책임

    • 협력에 참여하기 위해 객체가 수행하는 행동을 책임이라고 부른다.
    • 크레이그 라만은 책임을 크게 ‘하는 것’과 ‘아는 것’의 두 가지 범주로 나누어 세분화한다.
      • 영화 예매 시스템에서 Screening이 영화를 예매할 수 있어야하는 것은 하는 것에 포함되는 책임이며, 자신이 상영할 영화를 아는 것은 ‘아는 것’에 대한 책임이다.
    • 중요한 것은 객체의 상태가 아니라 행동이다.
  • 역할

    • 객체가 특정한 협력 안에서 수행하는 책임의 집합을 역할이라고 부른다.

    • 역할은 인터페이스와 같은 것(추상화)으로 생각할 수 있다. 즉 다른 것으로 교체할 수 있는 책임의 집합이다.

      그리고 그러한 역할을 하는 객체(구현체)를 어떤 것으로 할지 생각해볼 수 있다.

    • 그렇다면 한 종류의 객체만 협력에 참여하는 상황에서 역할이라는 개념(추상화)를 고려하는 것이 유용할까?

      • 개인적인 저자의 견해로는 설계 초반에는 적절한 책임과 협력의 큰 그림을 탐색하고, 역할과 객체를 명확하게 구분하는 것(추상화)은 그렇게 중요하지 않다. 애매하다면 단순하게 객체로 시작하고 반복적으로 책임과 협력을 정제해가면서 필요한 순간에 객체로부터 역할을 분리해내는 것이 가장 좋은 방법이라고 말함.
    • 추상화와 동일하게 역할은 추상화 계층만을 이용하면 중요한 정책을 상위 수준에서 단순화할 수 있다는 장점과 설계가 좀 더 유연해진다는 장점을 가진다.

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

  • 객체지향 설계란 올바른 객체에게 올바른 책임을 할당하면서 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 활동이다.[Evers09].
  • 이 정의에는 객체지향 설계에 관한 두 가지 관점이 섞여 있다. 첫 번째 관점은 객체지향 설계의 핵심이 책임이라는 것이며 두 번째 관점은 책임을 할당하는 작업이 응집도와 결합도 같은 설계 품질과 깊이 연관돼 있다는 것이다.
  • 설계는 변경을 위해 존재하고 변경에는 어떤 식으로든 비용이 발생한다. 훌륭한 설계란 합리적인 비용안에서 변경을 수용할 수 있는 구조를 만드는 것이다.
  • 01 데이터 중심의 영화 예매 시스템
    • 객체지향 설계에서는 상태를 분할의 중심축으로 삼을 수 있고, 책임을 분할의 중심축으로 삼아 객체를 분할 할 수 있다. 결론적으로 변경과 관련된 이유로 책임에 초점을 맞추는 것이 좋다.
    • 영황 예매 시스템을 상태를 분할의 중심축으로 삼아 데이터 중심의 설계를 해봄으로써 그 이유를 이해해보자.
  • 02 설계 트레이드오프
    • 이제 비교해보자. 데이터 중심 설계와 책임 중심 설계의 장단점을 비교하기 위해 캡슐화, 응집도, 결합도를 사용할 것이다.
    • 변경될 가능성이 높은 부분을 구현, 상대적으로 안정적인 부분을 인터페이스로 부른다는 것을 기억할 것.
    • 설계가 필요한 이유는 요구사항이 변경되기 떄문이고, 캡슐화가 중요한 이유는 변경의 영향을 통제할 수 있기 때문이다.
    • 결합도와 응집도는 변경과 관련되어있다.
    • 결합도는 한 모듈이 변경되기 위해서 다른 모듈의 변경을 요구하는 정도로 측정할 수 있다.
    • 캡슐화의 정도가 응집도와 결합도에 영향을 미친다. 따라서 응집도와 결합도를 고려하기 전에 먼저 캡슐화를 향상시키기 위해 노력하라.
  • 03 데이터 중심의 영화 예매 시스템의 문제점
    • 데이터 중심의 영화 예매 시스템은 각 객체들이 필요한 변수와 get,set메소드를 가지며 Agency 객체가 다른 객체들을 사용해 예매 로직을 구현하는 식으로 설계되었다.
    • 데이터 중심의 설계는 캡슐화를 위반하고 객체의 내부 구현을 인터페이스의 일부로 만들며 책임 중심의 설계는 객체의 내부 구현을 안정적인 인터페이스 뒤로 캡슐화한다.
    • 캡슐화의 관점
      • set, get메소드를 만들어서 얼핏보면 캡슐화가 이루어진 것처럼 보이지만 내부의 인스턴스 변수가 존재한다는 것을 노골적으로 드러낸다.
      • 설계할 때 협력에 관해 고민하지 않으면 캡슐화를 위반하는 과도한 접근자와 수정자를 가지게 되는 경향이 있다. (데이터 중심 설계는 내부에 저장할 데이터에 초점이 맞췄기 때문에 협력과 그 사이의 책임에 대한 고민이 되지 않았다.)
    • 결합도의 관점
      • 객체 내부의 구현이 객체의 인터페이스에 드러난다는 것은 클라이언트가 구현에 강하게 결합된다는 것을 의미하며, 단지 객체의 내부 구현을 변경했음에도 의존하는 모든 클라이언트들도 함께 변경해야 한다.
      • 예전에 get, set메소드를 굳이 사용하는 것과 관련해 궁금증이 있었는데, 책에서도 get 메소드를 사용하는 것은 인스턴스 변수 fee의 가시성을 private에서 public으로 변경하는 것과 거의 동일하다고 설명.
    • 응집도의 관점
      • 서로 다른 이유로 변경되는 코드가 하나의 모듈 안에 공존할 때 모듈의 응집도가 낮다고 말한다.
  • 04 자율적인 객체를 향해
    • 캡슐화를 지켜라
      • 캡슐화는 설계의 제 1원리다. 데이터 중심의 설계가 낮은 응집도와 높은 결합도라는 문제로 몸살을 앓게 된 근본적인 원인은 바로 캡슐화의 원칙을 위반했기 때문이다.
      • 객체는 스스로의 상태를 책임져야 하며 외부에서는 인터페이스에 정의된 메서드를 통해서만 상태에 접근할 수 있어야 한다.
      • 속성의 가시성을 private으로 설정했다고 해도 접근자와 수정자를 통해 속성을 외부로 제공하고 있다면 캡슐화를 위반하는 것이다.
    • 이 객체가 어떤 데이터를 포함해야되는지, 이 객체가 데이터에 대해 수행해야 하는 오퍼레이션은 무엇인지와 같은 두가지 관점에서 고민하여서 객체를 캡슐화하는 과정을 통해 개선.
  • 05 하지만 여전히 부족하다
    • 객체가 가지는 상태에 대한 처리를 해당하는 객체가 직접 처리하지만 메소드의 인자값으로 객체의 상태 변수들을 받아오고 이것의 의미는 인터페이스를 통해 외부에 노출하고 있는 것이다.
      • 만약 상태변수들이 변경되면 해당 메소드를 사용하는 곳까지 변경이 일어나야한다. 이러한 내부 구현의 변경이 외부로 퍼져나가는 파급 효과는 캡슐화가 부족하다는 명백한 증거이다.
    • 외에의 다른 객체들도 캡슐화가 덜 됨.
    • 캡슐화는 단순히 객체 내부의 데이터를 외부로부터 감추는 것 이상의 의미를 가지며 캡슐화는 ‘변경될 수 있는 어떤 것이라도 감추는 것을 의미한다’ 내부 속성을 외부로부터 감추는 것은 데이터 캡슐화라고 불리는 캡슐화의 한 종류일 뿐이다.
  • 06 데이터 중심 설계의 문제점
    • 데이터 중심의 설계가 변경에 취약한 이유는 두 가지다.
      1. 데이터 중심의 설계는 본질적으로 너무 이른 시기에 데이터에 관해 결정하도록 강요한다.
      2. 데이터 중심의 설계에서는 협력이라는 문맥을 고려하지 않고 객체를 고립시킨 채 오퍼레이션을 결정한다.
    • 데이터 중심 설계는 객체의 행동보다는 상태에 초점을 맞춘다.
    • 데이터 중심 설계는 객체를 고립시킨 채 오퍼레이션을 정의하도록 만든다.

5장 책임 할당하기

  • 이전 장에서 데이터 중심의 접근법을 취했을 경우의 문제점들에 대해 살펴보았으며 책임에 초점에 맞추는 것은 2장에서 살펴보았다.

  • 책임에 초점을 맞춰서 설계할 때 가장 큰 어려움은 어떤 책임을 할당할지를 결정하기가 쉽지 않다는 것이다. 책임 할당 과정은 일종의 트레이드오프 활동이며 다양한 책임 할당 방법이 존재한다. 올바른 책임을 할당하기 위해서는 다양한 관점에서 설계를 평가할 수 있어야 한다.

  • 이번 장에서 살펴볼 GRASP 패턴은 책임 할당에 대한 답을 제시해 줄것.

  • 책임 중심 설계를 위해서는 두 가지 원칙을 따라야 한다.

    1. 데이터보다 행동을 먼저 결정하라.
    2. 협력이라는 문맥 안에서 책임을 결정하라.
  • 02 책임 할당을 위한 GRASP 패턴

    • 설계를 시작하기 전에 도메인에 대한 개략적인 모습을 그려 보는 것이 유용하다.
    • 정보 전문가 패턴
      • 어플리케이션이 제공해야하는 기능을 어플리케이션 책임으로 생각하고, 핵심 기능을 메시지로 구체화해라.
      • 그러면 해당 메시지를 누구에게 할당해야할까 ? 이 때 해당 책임(메시지)를 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것이며 이를 GRASP패턴에서는 정보 전문가 패턴이라고 부른다.
      • 이제 책임을 할당한 객체의 내부 구현을 고민한다. 그 과정에서 스스로 처리할 수 없는 작업이 있다면 외부에 도움을 요청해야하며 이러한 요청은 새로운 메시지가 되고 이 메시지는 새로운 객체의 책임으로 할당된다. 이와 같은 과정으로 협력 공동체가 구성된다.
    • 낮은 결합도 패턴, 높은 응집도 패턴
      • 책임을 할당하는 방법은 여러 케이스가 가능하며 이 때 어떤 객체에 할당하든 모두 전문가인 경우, 응집도와 결합도를 고려하여서 할당하라.
    • 창조자 패턴
      • 창조자 패턴은 객체를 생성할 책임을 어떤 객체에게 할당할지에 대한 지침을 제공한다.
  • 03 구현을 통한 검증

    • 클래스 응집도 판단하기

      • 클래스가 하나 이상의 이유로 변경돼야 한다면 응집도가 낮은 것.

      • 클래스 인스턴스를 초기화하는 시점에 경우에 따라 서로 다른 속성들을 초기화하고 있다면 응집도가 낮은 것.

      • 메소드 그룹이 속성 그룹을 사용하는지 여부로 나뉜다면 응집도가 낮은 것.

        → 서로 다른 객체로 분리하여 응집도를 높인다.

    • 서로 다른 객체로 분리하였지만 기존에 해당 객체와 협력하는 객체에서 코드가 늘어났으며 확장성이 떨어졌다. → 다형성을 활용하여 해결.

    • 클래스를 변경에 따라 분리하고 인터페이스를 이용해 변경을 캡슐화하는 것은 설계의 결합도와 응집도를 향상시키는 매우 강력한 방법이다. 하나의 클래스가 여러 타입의 행동을 구현하고 있는 것처럼 보인다면 클래스를 분리하고 다형성 패턴에 따라 책임을 분산시키자.

6장 메시지와 인터페이스

  • 메시지, 오퍼레이션, 메서드, 퍼블릭 인터페이스, 시그니처
  • 디미터 법칙
  • 묻지 말고 시켜라
  • 의도를 드러내는 인터페이스
  • 명령-쿼리 분리 원칙
    • 참조 투명성