GOF의 디자인 패턴 1장, 서론의 두번째를 정리해봅니다.

카탈로그 조직화 하기

영역을 기준으로

  • 앞에서 말한 디자인 패턴들은 추상화 수준과 배움의 깊이가 다름
  • 그렇기에 목적과 범위를 기준으로 조직화함

  • 목적
    • 생성: 객체들의 생성과정에 관여
    • 구조: 클래스나 객체의 합성에 관한 패턴
    • 행동: 클래스나 객체들의 상호작용 방법과 책임 분산 방법
  • 범위
    • 클래스: 클래스와 서브클래스 간의 관련성을 다루는 패턴
      • 주로 상속
      • 컴파일 타임에 정적으로 결정됨
    • 객체: 객체 관련성을 다루는 패턴
      • 런타임에 변경할 수 있음
      • 조금더 동적인 성격을 지님
      • 대부분은 상속을 이용함

복합적으로 얽힐 경우 어떤 식으로 이해할 수 있는 조금더 구체적인 구분에 대해 알아보자.

  • 생성 클래스 패턴
    • 생성 책임의 일부를 서브클래스가 담당
  • 생성 객체 패턴
    • 생성 책임의 일부를 다른 객체에게 위임
  • 구조 클래스 패턴
    • 상속을 이용해서 클래스를 복합함
  • 구조 객체 패턴
    • 상속을 이용해서 객체를 합성하는 방법을 정의
  • 행동 클래스 패턴
    • 상속을 이용해서 알고리즘과 제어 흐름을 기술
  • 행동 객체 패턴
    • 하나의 작업을 수행하기 위해 객체 집합이 어떻게 협력하는지 기술

패턴간 참조관계를 기준으로

  • 이 그림을 아직 이해할 수는 없다.
  • 다만 위 그림은 패턴 간의 참조 관계를 기준으로 나타낸 것이다.
  • 나중에 알 수 있겠지

디자인 패턴을 이용하여 문제를 푸는 방법

  • 디자인 패턴을 사용하면 어떤 문제를 어떻게 해결하기를 지향하는 것일까?

적당한 객체 찾기

기본 용어

  • 객체: data + procedure
  • procedure: method or operation
  • 객체는 요청 혹은 message을 받으면 연산을 수행한다.
  • 요청은 객체가 연산을 실행하게 하는 유일한 방법이다.
  • 연산은 객체의 내부 데이터의 상태를 변경하는 유일한 방법이다.
  • 이 두개의 제약을 적용한 것을 캡슐화되어 있다고 말한다.

객체지향 설계가 어려운 이유와 방법들

  • 객체 지향 설계에 있어 가장 어려운 부분은 시스템을 구성한 객체의 분할을 결정하는 일이다.
  • 캡슐화, 크기, 종속성, 유연성, 성능, 진화, 재사용성 등을 고려해야 한다.
  • 그리고 이것들을 고려하기 위한 판단은 모두 주관적이기 때문에 더더욱 어렵다.
  • 예를 들어, 시스템의 협력관계, 책임성을 중심으로 설계하거나, 실세계를 모방한 모델로 만들거나 할 수 있다. 하지만 가장 좋은 방법은 없다.
  • 객체 지향 설계는 실세계와 대응 관계를 갖지 못할 때가 많다.
  • 흔히 실세계를 모방하는 방법이라 하지만, 저수준으로 갔을 때는 배열, 리스트와 같은 구현에 직접적으로 연관되는 녀석들로 되어 있는 경우가 많다.
  • 실세계를 그대로 반영하게 되면 현재의 실세계는 반영가능하나 미래는 불가능하다.
  • 이러한 점을 기반으로 우리는 설계 단계 동안에 새로운 추상화된 객체나 인터페이스를 만들어야 한다.
  • 이는 결국 설계의 유연성을 증진하기 위한 중요한 노력중 하나다.
  • 요약하면, 실세계를 그대로 모방하는 방식으로 OOP를 설계하기 어렵다는 것이다.
  • 그 과정에는 설계의 유연성, 객체의 크기등 다양한 변수 때문에 새로운 추상화를 만들어 관리할 필요성이 있기 때문이다.
  • 그렇기 때문에 OOP를 단순히 실세계를 모방하기 위한 방법론이라 칭하는 것은 일부만 이해하고 있는것이라 생각할 수 있겠다.

객체의 크기 결정

  • 객체의 규모는 어떤 기준으로 정할 수 있을까?
  • 디자인 패턴에서는 이 문제에 대한 답을 제시한다.
  • 퍼사드: 서브시스템 객체로 표현하는 방법
  • 플라이급: 규모는 작으나 개수는 많은 객체를 다루는 방법
  • 객체를 작은 규모로 분할하는 방법 등이 있다.

객체 인터페이스의 명세

  • Signiture: 연산의 이름, 매개변수로 받는 객체들, 반환 값
  • Interface: 객체가 정의하는 연산의 모든 시그니처를 말함
    • 즉, 객체가 받아서 처리할 수 있는 연산의 집합을 말함
  • Type: 특정 인터페이스를 지칭하는 이름
  • SuperType: 다른 인터페이스가 포함하는 인터페이스
  • SubType: 다른 인터페이스를 포함하는 인터페이스
  • 당연하게도 SuperType으로 SubType들을 관리할 수 있음
  • SubType에 구현에 따라 다른 결과가 나올 수 있다.
  • 즉, 어떤 요청과 그 요청을 처리할 객체를 런타임에 연결짓는 것을 동적 바인딩이라 한다.
  • 이러한 동적 바인딩은 동일한 인터페이스를 가진 다른 객체로 대체할 수 있도록 한다.
  • 이러한 성질을 다형성이라 한다.
  • 이는 객체지향 시스템의 핵심 개념이다.
    • 사용자 정의를 단순화
    • 객체간 결합도를 없앰
    • 런타임에 관련성을 다양하게 만들어줌
  • 디자인 패턴은 인터페이스에 정의해야 하는 요소, 데이터 등을 기반으로 인터페이스 작성을 도와준다.
  • 또한 인터페이스간의 관련성도 정의한다.

객체 구현 명세하기

  • 그럼 객체는 어떻게 정의해야 할까?
  • 그 구현은 class에서 수행한다.

  • 클래스 이름
  • Line
  • 연산 이름
  • Line
  • 데이터

  • 왼쪽 객체가 오른쪽 객체를 생성함
  • 점선 채운 삼각형

  • 상속 관계 표현
  • 실선 빈 삼각형

  • 추상 클래스: 모든 서브 클래스 사이의 공통되는 인터페이스를 정의
  • 정의한 모든 연산이나 일부 연산의 구현을 서브클래스로 넘김
  • 그렇기 때문에 추상 클래스는 인스턴스화 할 수 없다. 모든 연산이 정의되지 않았기 때문
  • 이렇게 정의만하고 구현을 하지 않은 연산을 추상 연산이라 한다.
  • 그리고 이런 추상 클래스가 아닌 클래스는 구체 클래스라 한다.
  • 추상 클래스, 추상 연산의 이름은 italic으로 표기한다.

  • Mixed In 클래스: 다른 클래스들에게 선택적인 인터페이스 혹은 기능을 제공하려는 목적을 가진 클래스
  • 다중 상속을 사용해야만 한다.
  • 왼쪽은 구체 클래스를 상속하고, 오른쪽은 추상 클래스를 상속받았다.
  • 그렇기 때문에 MixinOperation()의 경우에는 필수적으로 구현해야 한다.
  • 하지만 다중 상속은 죽음의 다이아몬드와 같은 문제가 있기 때문에, 요즘언어에서는 지원하지 않는다.

클래스 상속 대 인터페이스 상속

  • 클래스 상속
    • 이미 정의된 객체의 구현을 바탕으로 함
  • 인터페이스 상속
    • 특정 객체가 다른 객체 대신에 사용될 수 있는 경우를 대비하여 만들어짐

구현에 따르지 않고, 인터페이스에 따르는 프로그래밍

구현이 아닌 인터페이스에 따라 프로그래밍하세요.

  • 클래스 상속은 부모 클래스의 정의한 구현을 재사용하여 확장하려는 메커니즘이다.
  • 즉, 구현의 재사용을 염두에 두었다고 할 수 있다.
  • 하지만, 이 뿐만 아니라 다형성을 끌어낼 수 있어야 한다.
  • 그렇기 때문에 어떤 변수를 구체 클래스의 인스턴스로 선언하는 일은 피해야 한다.
  • 물론 상위 클래스를 상속한 하위 클래스의 경우 상위 클래스로 선언했을 때 다형성으로 동작하기는 한다.
  • 하지만 인터페이스 개념으로 다루게 된다면..
  • 인터페이스만 만족하면 사용하면 된다. 구체 타입을 알 필요가 없다.

재사용을 실현 가능 한 것으로

객체 합성을 사용하세요.

상속 대 합성

  • 기능의 재사용을 위해 구사하는 대표적인 방법은 클래스 상속, 객체 합성이다.
  • Subclassing: 클래스 상속 - White box reuse
  • Composition: 객체 합성 - Black box reuse

클래스 상속

  • 장점
    • 컴파일 시점에 정의됨
    • 부모 구현을 쉽게 수정가능함
    • 또는 일부만 재정의함
  • 단점
    • 런타임 상속 클래스 구현 변경 불가 (컴파일 시점에 결정되니까)
    • 부모 클래스의 구현에 종속적임
      • 부모 클래스의 구현이 다 드러나 캡슐화가 깨진다는 의견도 있음
    • 이런 구현의 종속성은 서브 클래스 재사용시 문제가 생김
      • 새 문제에 맞지 않는다면, 부모 클래스를 재작성해야 함
    • 위의 문제에서 벗어나기 위해서는 추상 클래스에서만 상속받는 것이 있겠다.
      • 그러면 서브 클래스에서만 구현을 바꾸면 된다.

객체 합성

  • 장점
    • 인터 페이스만 바라보게 할 수 있어 캡슐화를 유지할 수 있다.
    • 대체 역시 가능하다.
    • 객체는 인터페이스에 맞춰 구현되어 구현사이 종속성이 줄어든다.
  • 단점
    • 클래스의 수가 적어지고 객체의 수는 많아짐

위임

  • 런타임에 행동의 복합을 가능하게 한다.
  • 실선 화살표는 Window가 rectangle instance에 대한 참조를 가지고 있음을 보여준다.
  • area() 연산을 위임하여 처리했다.
  • 단점은, 구조가 이해하기 어려울 수 있다는 점이다.
  • 이는 런타임 객체에 따라 그 결과가 다르기 때문이다.

상속 대 매개변수화된 타입

  • Generic을 말한다.
  • 매개변수로 타입을 정의하지 않는다.

런타임 및 컴파일 타임의 구조를 관계짓기

  • 객체지향 프로그램의 실행구조는 코드 구조와 일치하지 않는 경우가 있다.
  • 코드 구조는 컴파일 시점에 확정되지만, 런타임에서는 교류하는 객체들에 의해 구조가 달라질 수 있다.

  • 객체 관계에서는 집합과 인지라는 것이 있다.
  • 집합: 한 객체가 다른 객체를 소유하거나 책임을 짐
  • 인지: 한 객체가 다른 객체에 대해 알고 있음
  • 왼쪽이 오른쪽을 포함, 오른쪽은 왼쪽을 인지
  • 이 두개는 사실 구현상으로 보았을 때 구분하기가 까다로울 수 있다. 결국 상호참조니까
  • 그래서 이러한 구조는 사용 목적에 따라 결정해야 한다.
  • 집합의 경우, 강력한 영속성을 띈다. 즉 하위 요소가 무조건적으로 포함된다는 의미다.
  • 인지의 경우 변경이 잦다. 좀더 동적이다.

변화에 대비한 설계

  • 변화에 대비하여 설계하기 위해서는 앞으로 일어날 변화에 대해 생각해보아야 한다.
  • 다음과 같은 상황에서 디자인 패턴을 통해 재설계가 필요하게 된다.
  1. 객체 생성
    • 클래스 이름을 명시해서 생성하면 대체가 불가능해진다.
    • 특정 구현에 종속적이게 된다.
    • 앞으로 구현에 얽매인다.
    • 디자인 패턴: 추상 팩토리, 팩토리 메서드, 원형
  2. 특정 연산에 대한 의존성
    • 특정한 연산을 사용하면 요청을 만족하는 한가지 방법에만 매이게 된다.
    • 디자인 패턴: 책임 연쇄, 명령
  3. 하드웨어와 소프트퉤어 플랫폼에 대한 의존성
    • 특정 플랫폼에 종속된 소프트웨어는 다른 플랫폼에 이식하기 어렵다.
    • 디자인 패턴: 추상 팩토리, 가교
  4. 객체의 표현이나 구현에 대한 의존성
    • 사용자에게 객체 표현 방법, 저장 방법, 구현 방법 등의 정보를 감추어 변화의 정도를 제어한다.
    • 디자인 패턴: 추상 팩토리, 가교, 메멘토, 프록시
  5. 알고리즘 의존성
    • 알고리즘 자체를 확장, 대체, 최적화할 수 있는 것에 대응해야 한다.
    • 디자인 패턴: 빌더, 반복자, 전략, 템플릿 메서드, 방문자
  6. 높은 결합도
    • 높은 결합도를 가지면 독립적으로 재사용하기 어렵다.
    • 하나의 거대한 시스템이 되어버린다.
    • 디자인 패턴: 추상 팩토리, 가교, 책임 연쇄, 명령, 퍼사드, 중재자, 감시자
  7. 서브클래싱을 통한 기능 확장
    • 단순히 확장만을 이유로 새로운 서브클래스를 만들면 클래스 수를 엄청나게 증가시킬 수 있다.
    • 또, 서브 클래싱하는 클래스마다 매번 해야하는 초기화, 소멸에 대한 구현 오버헤드도 있다.
    • 객체 합성과 위임을 사용하여 유연하게 처리하자.
    • 다만 객체 합성을 많이 사용하면 이해하기가 어려워진다.
    • 디자인 패턴: 가교, 책임 연쇄, 장식자, 감시자, 전략
  8. 클래스 변경이 편하지 못한 점
    • 디자인 패턴: 적응자, 장식자, 방문자

디자인 패턴을 고르는 방법

  • 패턴이 어떻게 문제를 해결하는지 파악하자.
  • 패턴의 의도를 보자.
  • 패턴들 간의 관련성을 파악하자.
  • 비슷한 목적의 패턴들을 모아서 공부합시다.
  • 재설계의 원인을 파악하자.
  • 설계에서 가변성을 가져야 하는 부분이 무엇인지 파악하자.

디자인 패턴 사용 방법

  1. 전체를 훑는 기분으로 한번 읽는다.
  2. 다시 처음으로 가서 구조, 참여자, 협력 방법을 다시 공부한다.
  3. 확실한 이해를 위해 예제 코드를 본다.
  4. 실제 프로젝트에서 사용할 참여자 이름을 정한다.
  5. 클래스를 정의한다.
  6. 패턴에 정의한 연산에 대해 이름을 정의한다.
  7. 연산을 구현한다.

Reference

profile
Goal, Plan, Execute.

0개의 댓글