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

디자인 패턴

  • 소프트웨어 설게에서 반복적으로 발생하는 문제에 대해 반복적으로 적용할 수 있는 해결 방법

  • 디자인패턴의 목적은 설계를 재사용하는 것

  • 디자인 패턴은 다양한 변경을 다루기 위해 반복적으로 재사용할 수 있는 설계의 묶음

💡 디자인 패턴이 설계를 재사용하기 위한 것이라면 프레임워크는 설계와 코드를 함께 재사용하기 위한 것이다.

→ 오오 … 그리고 둘 다 모두 일관성 있는 협력과 관련이 있다고 했음

리팩터링 구루

디자인 패턴은 특정한 변경을 일관성 있게 다룰 수 있는 협력 템플릿을 제공하고, 프레임워크는 특정한 변경을 일관성 있게 다룰 수 있는 확장 가능한 코드 템플릿을 제공한다. 디자인 패턴이 협력을 일관성 있게 만들기 위해 재사용할 수 있는 설계의 묶음이라면, 프레임워크는 일관성 있는 협력을 제공하는 확장 가능한 코드이다.

어쨌든 둘 다 모두 협력을 일관성 있게 만들기 위한 방법.

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

소프트웨어 패턴

  • 패턴은 반복적으로 발생하는 문제와 해법의 쌍으로 정의된다

  • 패턴을 사용함으로써 이미 알려진 문제와 이에 대한 해법을 문서로 정리할 수 있으며, 이 지식을 다른 사람과 의사소통할 수 있다

  • 패턴은 추상적인 원칙과 실제 코드 작성 사이의 간극을 메워주며 실질적인 코드 작성을 돕는다

  • 패턴의 요점은 패턴이 실무에서 탄생했다는 점이다

패턴은 지식 전달과 커뮤니케이션의 수간으로 활용할 수 있기 때문에 패턴에서 가장 중요한 요소는 패턴의 ‘이름’이다.

패턴 분류

패턴을 분류하는 가장 일반적인 방법은 패턴의 범위나 적용 단계에 따라

  • 아키텍처 패턴

  • 분석 패턴

  • 디자인 패턴

  • 이디엄

의 4가지로 구분할 수 있다.

디자인 패턴의 상위에는 소프트웨어의 전체적인 구조를 결정하기 위해 사용할 수 있는 아키텍처 패턴이 있다. 아키텍처 패턴은 미리 정의된 서브시스템들을 제공하고, 각 서브시스템들의 책임을 정의 하며, 서브시스템들 사이의 관계를 조직화하는 듀칙과 가이드라인을 포함한다. 아키텍처 패턴은 구체적인 소프트웨어 아키텍처를 위한 템플릿을 제공하며, 디자인 패턴과 마찬가지로 프로그래밍 언어나 프로그래밍 패러다임에 독립적이다.

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

아키텍처 패턴, 디자인 패턴, 이디엄이 주로 기술적인 문제를 해결하는 데 초점을 맞추고 있다면 분석 패턴은 도메인 내의 개념적인 문제를 해결하는 데 초점을 맞춘다. 분석 패턴은 업무 모델링 시에 발견되는 공통적인 구조를 표현하는 개념들의 집합이다.

패턴과 책임-주도 설계

객체지향 설계에서 가장 중요한 일은 올바른 책임을 올바른 객체에게 할당하고 객체 간의 유연한 협력 관계를 구축하는 일이다. 책임과 협력의 윤곽은 캡슐화, 크기, 의존성, 유연성, 성능, 확장 가능성, 재사용성 등의 다양한 요소들의 트레이드오프를 통해 결정된다.

  • 어떤 책임이 필요한가 ?

  • 이 책임을 어떤 객체에게 할당해야 하는가 ?

  • 유연하고 확장 가능한 협력 관계를 구축하기 위해서는 객체와 객체와 객체 간에 어떤 의존성이 존재해야 하는가 ?

패턴은 공통으로 사용할 수 있는 역할, 책임, 협력의 템플릿읻가. 패턴은 반복적으로 발생하는 문제를 해결하기 위해 사용할 수 있는 공통적인 역할과 책임, 협력의 훌륭한 예제를 제공한다.

  • STRATEGY

  • BRIDGE

  • OBSERVER

  • COMPOSITE

중요한 것은 패턴을 따르면 특정한 상황에 적용할 수 있는 설계를 쉽고 빠르게 떠올릴 수 있다는 사실이다. 특정한 상황에 적용 가능한 패턴을 잘 알고 있다면 책임 주도 설계의 절차를 하나하나 따르지 않고도 시스템 안에 구현할 객체들의 역할과 책임, 협력 관계를 빠르고 손쉽게 구성할 수 있다.

패턴의 구성 요소는 클래스가 아니라 ‘역할’이다.

패턴을 구성하는 요소가 클래스가 아니라 역할이라는 사실은 패턴 템플릿을 구현할 수 있는 다양한 방법이 존재한다는 사실을 암시한다.

역할은 동일한 오퍼레이션에 대해 응답할 수 있는 팩임의 집함을 암시하기 때문에 하나의 객체가 여러가지 역할을 모두 수행하더라도 문제가 없다.

반대로 다수의 클래스가 동일한 역할을 구현할 수도 있다. 중복 할인 설계의 기본 구조는 COMPOSITE 패턴을 따른다.

💡 디자인 패턴의 구성요소는 클래스와 메서드가 아니라 역할과 책임이다.

패턴을 적용하기 위해서는 패턴에서 제시하는 구조를 그대로 표현하는 것이 아니라 패턴의 기본 구조로부터 출발해서 현재의 요구에 맞게 구조를 수정해야 한다.

캡슐화와 디자인 패턴

영화 예매 시스템에서 Movie 가 DiscountPolicy 상속 계층을 합성 관계로 유지해야 하는 다양한 설계 원칙과 이유에 대해 장황하게 설명했지만 사실 이 설계는 STRATEGY 패턴을 적용한 예다. STRATEGY 패턴의 목적은 알고리즘의 변경을 캠슐화하는 것이고 이를 구현하기 위해 객체 합성을 이용한다.

변경을 캡슐화하는 방법이 합성만 있는 것은 아니다. 상속을 이용할 수도 있다. 변경하지 않는 부분은 부모 클래스로, 변하는 부분은 자식 클래스로 분리함으로써 변경을 캡슐화라는 것이다. 이처럼 캡슐화하기 위해 합성 관계가 아닌 상속 관계를 사용하는 것을 TEMPLATE METHOD 패턴이라고 부른다. TEMPLATE METHOD 패턴운 부모 클래스가 알고리즘의 기본 구조를 정의하고 구체적인 단게는 자식 클래스에서 정의하게 함으로써 변경을 캡슐화할 수 있는 디자인 패턴이다. 다만 합성보다는 결합도가 높은 상속을 사용했지 때문에 STRATEGY 패턴처럼 런타임에 객체의 알고리즘을 변경하는 것은 변경하는 것은 불가능하나 알고리즘 교체와 같은 요구사항이 없다면 상대적으로 STRATEGY 패턴보다 복잡도를 낮출 수 있다는 면에서는 장점이라고 할 수 있다.

추상 클래스나 인터페이스를 사용해 변경을 캡슐화하는 합성과 달리 상속을 사용할 경우에는 추상 메서드를 이용해 변경을 캡슐화해야 한다.

핸드폰 과금 시스템 설계는 DECORATOR 패턴을 기반으로 한다. DECORATOR 패턴은 객체의 행동을 동적으로 추가할 수 있게 해주는 패턴으로서 기본적으로 객체의 행동을 결함하기 위해 객체 합성을 사용한다.

OverlappedDiscountPolicy의 예를 통해 살펴본 COMPOSITE 패턴은 개별 객체와 복합 객체라는 객체의 수와 관련된 변경을 캡슐화하는 것이 목적이다.

대부분의 디자인 패턴의 목적은 특정한 변경을 캡슐화함으로써 유연하고 일관성 있는 협력을 설계할 수 있는 경험을 공유하는 것이다. 디자인 패턴에서 중요한 것은 디자인 패턴의 구현 방법이나 구조가 아니다. 어떤 디자인 패턴이 어떤 변경을 캡슐화하는지를 이해하는 것이 중요하다. 그리고 각 디자인 패턴이 변경을 캡슐화하기 위해 어떤 방법을 사용하는지를 이해하는 것이 더 중요하다.

패턴은 출발점이다.

<GoF의 디자인 패턴>에서 저자들은 초심자와 전문가의 차이점으로 어떤 문제를 해결하기 위해 과거의 경험을 활용할 수 있는 능력을 보유했는지 여부를 들고 있다. 그러나 전문가와 초심자의 또 다른 차이점은 전문가는 다양한 실무 경험을 통해 어떤 컨텍스트에서 어떤 패턴을 적용해야 하는지, 그리고 이보다 더 중요한 것응로 어떤 패턴을 적용해서는 안 되는지에 감각을 익히고 있다는 점이다 .

패턴은 출발점이다. 패턴은 공통적인 문제에 적절한 해법을 제공하지마 ㄴ공통적인 해법이 우리가 직면한 문제에적합하지 않을 수도 있다. 문제를 분석하고 창의력을 발휘함으로써 패턴을 현재의 문제에 적합하도록 적절하게 수정하라.

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

코드 재사용 대 설계 재사용

가장 이상적인 형태의 재사용 방법은 설계 재사용과 코드 재사용을 적절한 수준으로 조합하는 것이다.

코드 재사용만을 강조하는 컴포넌트는 실패했다. 추상적인 수준에서의 설계 재사용을 강조하는 디자인 패턴은 재사용을 위해 매번 유사한 코드를 작성해야만 한다. 설계를 재사용하면서도 유사한 코드를 반복적으로 구현하는 문제를 피할 수 있는 방법이 바로 프레임워크이다.

프레임워크란 ‘추상 클래스나 인터페이스를 정의하고 인스턴스 사이의 상호작용을 통해 시스템 전체 혹은 일부를 구현해 놓은 재사용 가능한 설계’ 또는 ‘애플리케이션 개발자가 현재의 요구사항에 맞게 커스터마이징할 수 있는 애플리케이션의 골격’을 의미한다.

→ 결국은 문제 해결을 위한 하나의 해결책

프레임워크는 코드를 재사용함으로써 설계 아이디어를 재사용한다.

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

프레임워크의 핵심은 추상 클래스나 인터페이스와 같은 추상화라고 할 수 있다.

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

상위 정책이 세부 사항보다 더 다양한 상황에서 재사용될 수 있어야 한다. 하지만 상위 정책이 세부 사항에 의존하게 되면 상이 정책이 필요한 모든 경우에 세부 사항도 항상 함께 존재해야 하기 때무에 상위 정책의 재사용성이 낮아진다. 이 문제를 해결할 수 있는 가장 좋은 방법은 의존성 역전 원칙에 맞게 상위 정책과 세부 사항 모두 추상화에 의존하게 만드는 것이다.

의존성 역전 원칙의 관점에서 세부 사항은 ‘변경’을 의미한다. 동일한 역할을 수행하는 객체들 사이의 협력 구조를 다양한 애플리케이션 안에서 재사용하는 것이 핵심이다.

이를 위해서는 변하는 것과 변하지 않는 것을 서로 분리해야 한다. 여기서 변하지 않는 것은 상위 정책에 속하는 역할들의 협력 구조다. 변하는 것은 구체적인 세부 사항이다. 프레임워크는 여러 애플리케이션에 걸쳐 재사용 가능해야 하기 때무에 변하는 것과 변하지 않는 것들을 서로 다른 주기로 배포할 수 있도록 별도의 ‘배포 단위’로 분리해야 한다.

이를 위한 첫걸음은 변하는 부분과 변하지 않는 부분을 별도의 패키지로 분리하는 것이다.

중요한 것은 패키지 사이의 의존성 방향이다. 의존성 역전 원리에 따라 추상화에만 의지하도록 의존성의 방향을 조정하고 추상화를 경계로 패키지를 분리했기 때문에 서부 사항을 구현한 패키지는 항상 상위 정책을 구현한 패키지에 의존해야 한다. 이것은 8장에서 설명한 컨텍스트 독립성의 패키지 버전이다.

이를 통해 일관성 있는 협력과 프레임워크 사이의 관계를 이해할 수 있다. 프레임워크는 여러 애플리케이션에 걸쳐 일관성 있는 협력을 구현할 수 있게 해준다.

제어 역전 원리

로버트 마틴은 훌륭한 객체지향 설계는 의존성이 역전된 설계라는 점을 강조한다.

여기서 협력을 제어하는 것은 프레임워크라는 것에 주목하라. 우리는 프레임워크가 적절한 시점에 실행할 것으로 예상되는 코드를 작성할 뿐이다. 과거의 좋았던 시절에는 우리가 직접 라이브러리의 코드를 호출했지만 객체지향의 시대에는 그저 프레이무어크가 호출하는 코드를 작성해야만 한다. 제어가 우리에게서 프레임워크로 넘어가 버린 것이다. 다시 말해서 제어가 역전된 것이다.

Last updated