We will find a way, we always have.

-interstellar

OOP

[오브젝트] 15장 디자인 패턴과 프레임워크

Redddy 2024. 3. 16. 13:57

애플리케이션을 설계하다 보면 요구사항을 구현하기 위해 과거와 비슷한 방법으로 문제를 해결하는 경우가 있다. 이렇게 설계를 재사용하는 것이 디자인 패턴의 목적이다. 디자인 패턴을 익힌다면 변경의 방향과 주기를 이해하는 것만으로도 필요한 역할과 책임, 역할들의 협력 방식을 순간적으로 떠올릴 수 있게 된다. (과연 정말 그런가..?)

 

디자인 패턴이 설계의 재사용이라면 프레임워크는 설계와 코드를 재사용하기 위한 것이다. 프레임워크는 애플리케이션의 아키텍쳐를 구현 구현 코드의 형태로 제공하며, 각 애플리케이션 요구에 따라 적절하게 커스터마이징할 수 있는 확장 포인트도 제공한다.

 

디자인 패턴은 특정한 변경을 일관성 있게 다룰 수 있는 협력 템플릿을 제공하고, 프레임워크는 특정한 변경을 일관성 있게 다룰 수 있는 확장 가능한 템플릿을 제공한다. 디자인 패턴과 프레임워크 모두 협력을 일관성 있게 만들기 위한 방법이다.

 

디자인 패턴과 설계 재사용

소프트웨어 패턴

패턴은 무엇일까? 패턴에는 아래와 같은 특징이 있다.

 

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

패턴의 가장 큰 가치는 경험을 통해 축적된 실무 지식을 효과적으로 요약하고 전달할 수 있다는 점이다. 그리고 패턴의 이름을 통해 커뮤니케이션 비용을 줄일 수 있다. (하지만 패턴을 모른다면 대화에 참여하기 어려울지도,, 그래도 해보자!)

 

패턴의 분류

패턴의 범위나 적용 단계에 따라 패턴을 분리할 수 있다.

 

디자인 패턴

  • 특정 정황 내에서 일반적인 설계 문제를 해결하며, 협력하는 컨포넌트 사이에서 반복적으로 발생하는 구조를 서술한다.
  • 프로그래밍 언어나 프로그래밍 패러다임에 독립적이다.

아키텍처 패턴

  • 소프트웨어의 전체적인 구조를 결정하기 위해 사용할 수 있는 패턴이다.
  • 구체적인 소프트웨어 아키텍처를 위한 템플릿을 제공하며 디자인 패턴과 마찬가지로 프로그래밍 언어나 프로그래밍 패러다임에 독립적이다.

이디엄

  • 디자인 패턴의 하위에 위치한다.
  • 특정 프로그래밍 언어에만 국한된 하위 레벨 패턴으로 주어진 언어의 기능을 사용해 컴포넌트, 혹은 컴포넌트 간의 특정 측면을 구현하는 방법을 서술한다.

분석 패턴

  • 도메인 내의 개념적인 문제를 해결한다.
  • 업무 모델링 시에 발견되는 공통적인 구조를 표현하는 개념들의 집합이다.

 

패턴과 책임 주도 설계

패턴은 공통으로 사용할 수 있는 역할, 책임, 협력의 템플릿이다. 패턴들의 세부적인 내용이 중요한 것이 아니다. 중요한 것은 특정한 상황에 적용 가능한 패턴을 알고 있다면 책임 주도 설계의 절차를 하나하나 따르지 않고도 시스템 안에 구현할 객체들의 역할과 책임, 협력 관계를 빠르고 손쉽게 구성할 수 있다. 마찬가지로 패턴의 구성 요소는 클래스(구체적)이 아니라 '역할'이다.

 

 

캡슐화와 디자인 패턴

또한 디자인 패턴은 협력을 일관성 있고 유연하게 만드는 것을 목적으로 하기 때문에 각 디자인 패턴은 특정한 변경을 캡슐화하기 위한 독자적인 방법을 정의하고 있다.

STRATEGY 패턴에 기반하여 영화 예매 시스템 설계를 표현하면, 영화에 적용될 할인 정책에 대한 알고리즘을 런타임에 동적으로 변경할 수 있다. TEMPLATE METHOD 패턴을 사용하면 변하는 부분을 자식 클래스로 분리함으로써 변경을 캡슐화한다.

디자인 패턴이 어떤 변경을 캡슐화하고 이를 위해 어떤 방법을 사용하는지 이해하는 것이 중요하다.

 

 

패턴은 출발점이다

패턴은 출발점이지 설계의 목표가 되어서는 안 된다. 정당한 이유 없이 사용된 모든 패턴은 설계를 복잡하게 만드는 장애물이다. 패턴은 복잡성의 가치가 단순성을 넘어설 때만 정당화돼야 한다.

 

 

프레임워크와 코드 재사용

코드 재사용 대 설계 재사용

설계를 재사용하는 디자인 패턴은 설계 아이디어를 매번 프로그래밍 언어의 특성에 맞춰 가공해야 한다. 재사용 관점에서 설계 재사용보다는 코드 재사용이 더 좋다. 오랜 시간 개발자들은 컴포넌트를 조립해서 코드를 재사용해왔는데 실제로 적용하는 과정에서 현실적이지 않다는 사실이 드러났다.

가장 이상적인 방법은 설계 재사용과 코드 재사용을 적절하게 조합하는 것이다. 구현책이 바로 프레임워크이다. 프레임워크는 '추상 클래스나 인터페이스를 정의하고 인스턴스 사이의 상호작용을 통해 시스템 전체 혹은 일부를 구현해 놓은 재사용 가능한 설계'를 의미한다. 또한 '애플리케이션 개발자가 현재의 요구사항에 맞게 커스터마이징할 수 있는 애플리케이션 골격'을 의미한다. 프레임워크는 애플리케이션에 대한 아키텍처를 제공하며 설계의 가변성을 미리 정의해뒀기 때문에 애플리케이션 설계자나 구현자는 애플리케이션에 종속된 부분에 대해서만 설계하면 된다.

 

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

프레임워크의 핵심은 추상 클래스나 인터페이스와 같은 추상화라고 할 수 있는데, 이들의 어떤 특징이 프레임워크의 재사용성을 향상시키는 것일까?

추상 클래스와 인터페이스는 일관성 있는 협력의 핵심재료이며 협력을 일관성있고, 유연하게 만들기 위해 추상화를 이용해 변경을 캡슐화한다. 그리고 협력을 구현하는 코드 안의 의존성을 가급적이면 추상 클래스나 인터페이스와 같은 추상화를 향하도록 작성해야 한다.

핸드폰 과금 시스템에서 구체적인 클래스들은 추상적인 클래스인 RatePolicy, AdditionalRatePolicy, FeeCondition에 의존하지만 추상화들은 구체 클래스에 의존하지 않는다는 것을 알 수 있다. 상위 정책은 변경에 안정적이지만 세부 사항은 자주 변경된다. 또한 상위 정책은 세부 사항에 비해 재사용될 가능성이 높다. 상위 정책이 세부 정책에 의존하면 세부 정책이 변경될 때마다 상위 정책이 영향을 받는다.

의존성 역전 원칙 관점에서 세부 사항은 '변경'이다. 핸드폰 과금 시스템의 설계를 요금을 계산하는 다양한 애플리케이션에 걸쳐 재사용하고 싶다고 가정해보자. 이를 위해 변하는 것과 변하지 않는 것을 분리해야 한다. 변하지 않는 것은 상위 정책에 속하는 역할들의 협력 구조이며 변하는 것은 구체적인 세부사항이다. 프레임워크는 여러 애플리케이션에 걸쳐 재사용 가능해야 하기 때문에 변하는 것과 변하지 않는 것들을 서로 다른 주기로 배포할 수 있도록 별도의 '배포 단위'로 분리해야 한다.

이를 위해 변하는 부분과 변하지 않는 부분을 별도의 패키지로 분리해야 하며 패키지 사이의 의존성 방향은 추상화에만 의존하도록해야 한다. 좀 더 나아가 상위 정책을 구현하고 있는 패키지가 충분히 안정적이고 성숙했다면 하위 정책 패키지로부터 완벽히 분리해서 별도의 배포 단위로 만들 수 있다.

 

제어 역전 원리

의존성 역전 원리는 전통적인 설계 방법과 객체지향을 구분하는 가장 핵심적인 원리다. 의존성 역전 원리에 따라 구축되지 않는 시스템은 협력 흐름을 재사용할 수도 없으며 변경에 유연하게 대처할 수도 없다. 의존성 역전 원리를 따른 코드는 추상화와 구체적인 사항이 서로 고립돼 있기 때문에 유지보수하기가 훨씬 쉽다.

 

 


 

디자인 패턴을 적용해봐야지! 하고 코드를 작성하는 것보다, 리팩터링 하면서 구조를 변경해보았더니 어? 이거 OO 패턴이었네? 형태가 더 아름다운 학습 패턴일 거 같다. 코드를 많이 작성해보자.