본문으로 건너뛰기

15장 디자인 패턴과 프레임워크

서문

  • 디자인 패턴
    • 소프트웨어 설계에서 반복적으로 발생하는 문제에 대해 반복적으로 적용할 수 있는 해결방법
  • 프레임워크
    • 설계와 코드를 함께 재사용하기 위한 것.
    • 애플리케이션의 아키텍처를 구현 코드 형태로 제공한다.

01. 디자인 패턴과 설계 재사용

소프트웨어 패턴

  • 패턴이란 무엇인가
    • 패턴은 반복적으로 발생하는 문제와 해법의 쌍의로 정의된다.
    • 패턴을 사용함으로써 이미 알려진 문제와 이에 대한 해법을 문서로 정리할 수 있으며, 이 지식을 다른 사람과 의사소통할 수 있다.
    • 패턴은 추상적인 원칙과 실제 코드 작성 사이의 간극을 메워주며 실질적인 코드 작성을 돕는다.
    • 패턴의 요점은 패턴이 실무에서 탄생했다는 점이다.

내가 사용하는 패턴의 정의는 하나의 실무 컨텍스트에서 유용하게 사용해 왔고 다른 실무 컨텍스트에서도 유용할 것이라고 예상되는 아이디어 - 마틴 파울러

  • 패턴이 지닌 큰 가치는 경험을 통해 축적된 실무 지식을 효과적으로 요약하고 전달할 수 있다는 점이다.
    • 패턴의 이름은 의사소통에 도움이 된다.

패턴 분류

  • 아키텍처 패턴, 분석패턴, 디자인 패턴, 이디엄 등이 있다.
  • 디자인 패턴
    • 특정 정황 내에서 일반적인 설계 문제를 해결하며, 협력하는 컴포넌트들 사이에서 반복적으로 발생하는 구조를 서술한다.
    • 디자인 패턴은 중간 규모의 패턴으로, 특정한 설계 문제를 해결하는 것을 목적으로 하며, 프로그래밍 언어나 프로그래밍 패러다임에 독립적이다.
  • 아키텍처 패턴
    • 아키텍처 패턴은 미리 정의된 서브시스템들을 제공하고, 각 서브시스템들의 책임을 정의하며, 서브시스템들 사이의 관계를 조직화하는 규칙과 가이드라인을 포함한다.
  • 이디엄
    • 특정 프로그램이 언어에만 국한된 하위 레벨 패턴으로, 주어진 언어의 기능을 사용해 컴포넌트, 혹은 컴포넌트 간의 특정 측면을 구현하는 방법을 서술한다.
    • C++의 COUNTER POINTER
  • 분석 패턴
    • 도메인 내의 개념적인 문제를 해결하는데 초점을 맞춘다.

패턴과 책임-주도 설계

  • 패턴을 다르면 특정한 상황에 적용할 수 있는 설계를 쉽고 빠르게 떠올릴 수 있다.
    • 책임 주도 설계의 절차를 하나하나 따르지 않고도 시스템 안에 구현할 객체들의 역할과 책임, 협력 관계를 빠르고 손쉽게 구성할 수 있다.
  • 패턴의 구성 요소는 클래스가 아니라 역할이다.
    • 어떤 구현 코드가 어떤 디자인 패턴을 따른다고 이야기할 때는 역할, 책임, 협력의 관점에서 유사성을 공유한다는 것이지 특정한 구현 방식을 강제하는 것은 아니라는 점을 이해하자.
    • 디자인 패턴은 단지 역할과 책임, 협력의 템플릿을 제안할 뿐 구체적인 구현 방법에 대해서는 제한을 두지 않는다.

패턴은 출발점이다.

  • 패턴은 출발점이지 목적지가 아니다.
    • 많은 전문가들은 널리 요구되는 유연성이나 공통적으로 발견되는 특정한 설계 이슈를 해결하기 위해 적절한 디자인 패턴을 이용해 설계를 시작한다.
    • 그러나 패턴은 설계의 목표가 되어서는 안된다.
    • 패턴은 단지 목표로 하는 설계에 이를 수 있는 방향을 제시하는 나침반에 불과하다.
    • 디자인 패턴이 현재의 요구사항이나 적용 기술, 프레임워크에 적합하지 않다면 패턴을 그대로 따르지 말고 목적에 맞게 패턴을 수정하라.
  • 패턴을 사용하면서 부딪히게 되는 대부분의 문제는 패턴을 맹목적으로 사용할 때 발생한다.
    • 패턴을 적용하는 컨텍스트의 적절성은 무시한 채 패턴의 구조에만 초점을 맞추는 것이다.
    • 망치를 들면 모든 것이 못으로 보인다고 패턴을 익힌 후에는 모든 설계를 패턴으로 해결하려고 시도하기 쉽다. → 패턴 만능주의
  • 해결하려는 문제가 아니라 패턴이 제시하는 구조를 맹목적으로 따르는 것은 불필요하게 복잡하고 난해하며, 유지보수하기 어려운 시스템을 낳는다.
    • 부적절한 상황에서 부적절하게 사용된 패턴으로 인해 소프트웨어의 엔트로피가 증가하는 부작용을 낳기 쉽다.
    • 패턴을 남용하지 않기 위해서는 다양한 트레이드오프 관계 속에서 패턴을 적용하고 사용해 본 경험이 필요하다.
  • 정당한 이유 없이 사용된 모든 패턴은 설계를 복잡하게 만드는 장애물이다.
    • 패턴은 복잡성의 가치가 단순성을 넘어설 때만 정당화돼야 한다.
  • 패턴은 출발점이다.
    • 공통적인 문제에 적절한 해법을 제공하지만 공통적인 해법이 우리가 직면한 문제에 적합하지 않을 수도 있다.
    • 문제를 분석하고 창의력을 발휘함으로써 패턴을 현재의 문제에 적합하도록 적절하게 수정하라.
    • 비록 패턴이 현재의 문제에 딱 들어맞지 않는다고 해도 참조할 수 있는 모범적인 역할과 책임의 집합을 알고 있는 것은 큰 도움이 될 것이다.

02. 프레임워크와 코드 재사용

코드 재사용 대 설계 재사용

  • 디자인 패턴은 프로그래밍 언어에 독립적으로 재사용 가능한 설계 아이디어를 제공하는 것을 목적으로 한다.
    • 언어에 종속적인 구현 코드를 정의하지 않기 때문에 디자인 패턴을 적용하기 위해서는 설계 아이디어를 프로그래밍 언어의 특성에 맞춰 가공해야 하고 매번 구현 코드를 재작성해야 한다는 단점이있다.
  • 프레임워크란?
    • 추상 클래스나 인터페이스를 정의하고 인스턴스 사이의 상호작용을 통해 시스템 전체 혹은 일부를 구현해 놓은 재사용 가능한 설계
    • 애플리케이션 개발자가 현재의 요구사항에 맞게 커스터마이징할 수 있는 애플리케이션의 골격
  • 프레임워크는 코드를 재사용함으로써 설계 아이디어를 재사용한다.

상위 정책과 하위 정책으로 패키지 분리하기

  • 프레임워크의 핵심은 추상 클래스나 인터페이스와 같은 추상화라고 볼 수 있다.
    • 추상클래스, 인터페이스로 인한 일관성 있는 협력을 통해 재사용성을 향상시킨다.
    • 협력을 일관성 있고 유연하게 만들기 위해서는 추상화를 이용해 변경을 캡슐화해야 한다.
  • 상위 정책이 세부 사항보다 더 다양한 상황에서 재사용될 수 있어야 한다.
    • 상위 정책이 세부 사항에 의존하게 된다면 상위 정책이 필요한 모든 경우에 세부 사항도 함께 존재해야 하기 때문에 재사용성이 낮아진다.
    • 의존성 역전 원칙에 맞춰 상위 정책과 세부사항 모두 추상화에 의존하라.
  • 변하는 것과 변하지 않는 것을 서로 분리하라.
    • 상위 정책 패키지와 하위 정책 패키지를 물리적으로 분리하고 나면 상위 정책 패키지를 여러 애플리케이션에서 재사용할 수 있는 기반이 마련된다. → 프레임워크
  • 우리는 동일한 프레임워크를 사용하는 여러 애플리케이션에 걸쳐 일관성 있게 코드를 설계하고 구현할 수 있다.
  • 동일한 프레임워클르 사용하는 애플리케이션은 구현 방식에 일관성이 있기 때문에 이해하기도 쉽다.

제어 역전 원리

  • 의존성 역전 원리는 전통적인 설계 방법과 객체지향을 구분하는 가장 핵심적인 원리다.
    • 의존성 역전 원리에 따라 구축되지 않는 시스템은 협력 흐름을 재사용할 수도 없으며 변경에 유연하게 대처할 수도 없다.
    • 시스템이 진화하는 방향에는 항상 의존성 역전 원리를 따르는 설계가 존재해야 한다.
    • 만약 요구사항이 빠르게 진화하는 코드에서 의존성 역전 원리가 적절하게 지켜지지 않고 있다면 그곳에는 변경을 적절하게 수용할 수 없는 하향식의 절차적인 코드가 존재할 수 밖에 없다.
  • 의존성을 역전시킨 객체지향 구조에서는 반대로 프레임워크가 애플리케이션에 속하는 서브 클래스의 메서드를 호출한다.
    • 프레임워크를 사용할 경우 개별 애플리케이션에서 프레임워크로 제어 흐름의 주체가 이동한다.
    • 즉, 의존성을 역전시키면 제어 흐름의 주체 역시 역전된다.
    • 이를 제어 역전 원리, 또는 할리우드 원리라고 한다.
  • 프레임워크에서는 일반적인 해결책만 제공하고 애플리케이션에 따라 달라질 수 있는 특정한 동작은 비워둔다.
    • 이렇게 완성되지 않은 채로 남겨진 동작을 훅이라고 부른다.
    • 훅은 프레임워크 코드에서 호출하는 프레임워크의 특정 부분이다.
    • 재정의된 훅은 제어 역전 원리에 따라 프레임워크가 원하는 시점에 호출된다.
  • 협력을 제어하는 것은 프레임워크다.
    • 우리의 코드는 수동적인 존재다.
    • 프레임워크가 우리의 코드를 호출해줄 때까지 그저 넋 놓고 기다리고 있을 수 밖에 없다.