1회독

  • 위 제목의 책을 훑어보는 식으로 1번 읽었고 더 정확한 내용 파악을 위해 2회독을 하며 글을 같이 작성하고자 함

 

GoF

  • GangOfFour의 줄임말로 이 책을 처음 집필하신 4명의 위대한 개발자 분들 되시겠다.

 

디자인 패턴

  • 객체지향 프로그래밍에서 좋은 설계를 하기 위한 반복된 패턴
  • 생성, 구조, 행위의 3가지 카테고리로 나누어 분류하는 것이 보통이다.

 

패턴 구성 요소

  • 패턴 이름 : 한 두 단어로 설계 문제와 해법 서술. 문서에서 표현하기 쉬워지며 설계의 의도를 명확히 하고 개발자들간의 소통이 원활해질 수 있음
  • 문제 : 언제 패턴을 사용해야 하는가 ?
  • 해법 : 설계를 구성하는 요소, 요소들간의 관계, 책임, 협력 관계를 서술. 추상적인 설명 제공
  • 결과 : 장단점 서술. 시간과 공간 사이의 균형을 맞춰야 함.

 

디자인 패턴 목적과 범위

GoF의 디자인 패턴 중 P.38

  • 각 패턴의 목적과 얼마만큼의 범위에 적용될지를 한 눈에 보고자 만든 표

 

디자인 패턴을 이용한 문제 해결 방법

1. 적당한 객체 찾기

  • 객체지향 프로그램은 객체로 만들고, 객체는 요청 또는 메시지를 받게 되면 메서드 또는 연산을 수행하게 됨
  • 요청은 객체가 연산을 실행하게 하는 유일한 방법이고, 내부 데이터를 변경하는 유일한 방법임. 이러한 접근의 제약사항으로 객체의 내부 상태는 캡슐화 된다고 말함.
  • 그렇기에 객체를 만듦에 있어 캡슐화, 크기 정하기, 종속성, 유연성, 성능, 진화, 재사용성 등을 고려해야 함.
  • 어떤 것에 우선순위를 주어야 할지 디자인 패턴을 공부한다면 알 수 있을 지도..?

2. 적당한 객체의 크기란

  • 객체의 크기나 개수는 딱 정해져 있는 것이 아님. 응용프로그램 전체가 하나의 객체로 만들어 질 수도 있음.
  • 퍼사드 패턴은 서브 시스템을 어떻게 객체로 표현할 수 있는지 설명하고, 플라이급 패턴은 규모는 작지만 개수는 많은 객체를 다루는 방법을 설명함.
  • 추상 팩토리 패턴과 빌더 패턴은 다른 객체를 생성하는 책임만 있는 객체를 만들어내기도 함.

3. 객체 인터페이스의 명세

  • 연산의 시그니처  : 객체의 연산은 연산의 이름, 매개변수로 받아들이는 객체들, 연산의 반환 값을 명세
  • 인터페이스 : 연산의 모든 시그니처들을 일컫는 말로 객체가 받아서 처리할 수 있는 연산의 집합.
  • 타입 : 특정 인터페이스를 나타낼 때 사용하는 이름. 객체가 특정 이름을 갖는 다는 것은 그 인터페이스의 연산들을 모두 사용할 수 있다는 의미.
  • 슈퍼타입, 서브타입 : 다른 인터페이스를 포함하는 인터페이스는 서브타입, 다른 인터페이스가 포함하는 인터페이스를 슈퍼타입
  • 동적 바인딩 : 어떤 요청과 그 요청을 처리할 객체를 프로그램 실행 중, 즉 런타임에 연결 짓는 것. 객체지향 프로그래밍이 갖는 다형성을 통해 구현 가능.

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

  • 하나의 객체가 여러 타입을 가질 수 있고 서로 다른 클래스의 객체들이 동일한 타입을 가질 수 있음. 즉, 객체의 구현은 다를지라도 인터페이스는 같을 수 있음.
  • 어떤 메시지에 대해 수신 측 객체는 메시지를 처리할 수 있는지의 여부만을 확인.
  • 클래스 상속은 객체의 구현을 정의할 때 이미 정의된 객체의 구현을 바탕으로 함. 코드와 내부 표현 구조를 공유하는 메커니즘.
  • 인터페이스 상속은 어떤 객체가 다른 객체 대신에 사용될 수 있는 경우를 지정하는 메커니즘.

5. 구현이 아닌 인터페이스에 따라 프로그래밍합니다.

  • 어떤 변수를 구체 클래스의 인스턴스로 선언하는 일은 피해야 함.
  • 대신 추상 클래스의 인터페이스를 따르는 인스턴스 변수를 정의.

6. 상속 대 합성

  • 상속은 서브 클래싱, 즉 부모 클래스에서 상속 받아 구현을 정의하는 것. '화이트박스 재사용'이라고도 하며 내부를 볼 수 있기 때문이라고 함.
    • 상속의 장점은 쉽게 정의할 수 있다는 점이 있음. 또한 서브 클래스에서 부모 클래스의 연산을 일부 재정의할 수도 있음.
    • 상속의 단점은 런타임에 구현을 변경할 수 없다는 점이 있음. 또한 부모 클래스의 구현을 일부 또는 전부 상속받기에 구현이 종속되며 캡슐화를 파괴한다는 주장까지 있음.
  • 합성은 다른 객체를 변수로 두는 것. 이 때 5번에서 설명했듯이 구현이 아닌 인터페이스로 변수를 선언해야 함.(포인터)
    • 합성의 장점은 다른 객체에 대해 참조자를 얻는 방식으로 런타임에 동적으로 정의할 수 있음.
    • 합성의 단점은 인터페이스의 정의에 주의를 기울여야 한다는 점이 있음. 그 인터페이스로 선언된 객체는 인터페이스만을 바라볼 수 밖에 없기 때문.
  • 보통 합성이 상속보다 더 나은 방법이라고 할 수 있으나 적절히 조합하여 사용하여야 좋은 설계라 할 수 있음.

7. 위임

  • 합성을 상속만큼 강력하게 만드는 방법.
  • 두 객체가 하나의 요청을 처리함.
  • 수신 객체가 연산의 처리를 위임자에게 보냄.
  • 위임에 전적으로 의존하는 패턴들이 있음. ( 중재자 패턴, 가교 패턴 등 )

8. 상속 대 매개변수화된 타입

  • 기능의 재사용에 이용할 수 있는 방법 중 하나.
  • C++ 기준 템플릿을 의미
  • 클래스 상속, 객체 합성에 이어 객체 지향 시스템에서 행동을 복합하는 방법
  • 어지간한 설계는 상속, 합성, 매개변수화된 타입을 통해 구현하게 됨.

9. 집합과 인지

  • 집합은 다른 객체를 포함하고 포함된 객체는 부분이 되며 생존주기를 같이 함
  • 인지는 알고 있음을 의미. 연산을 요청할 순 있지만 서로 책임을 지지는 않음.
  • 이 두 가지 개념의 구분은 쉽지 않음. 코드에서 같은 표현으로 나타나기 때문. (C++ 기준, 둘 다 포인터 타입으로 표현할 수 있음.)
class Car
{
Wheel* wheel;   //집합
Car* other_car; //인지
}
  • 차에 있어 바퀴는 없으면 안되는 존재이지만 다른 차는 없어도 되는 존재임. 집합과 인지의 차이.

10. 변화에 대비한 설계

  1. 특정 클래스에서 객체 생성 : 객체를 생성할 때 클래스 이름을 명시하면 인터페이스가 아닌 특정 구현에 종속됨. 변화를 수용하지 못함. -> [추상 팩토리, 팩토리 메서드, 원형]
  2. 특정 연산에 대한 의존성 : 특정 연산을 사용하면 한 가지 방법에 메이게 됨. 요청의 처리 방법을 직접 코딩하는 방식을 피하면 컴파일, 런타임 시점을 모두 만족, 요청 처리 방법을 쉽게 변경 가능 -> [책임 연쇄, 명령]
  3. 하드웨어와 소프트웨어 플랫폼에 대한 의존성 : 소프트웨어, 하드웨어 플랫폼마다 기능에 존재하는 시스템 인터페이스가 모두 다름. 특정 플랫폼에 종속되면 다른 플랫폼에 이식하기가 어려워짐. 플랫폼 종속성을 제거하는 것은 시스템 설계에 있어 중요함. -> [추상 팩토리, 가교]
  4. 객체의 표현이나 구현에 대한 의존성 : 사용자가 객체의 표현 방법, 저장 방법, 구현 방법, 존재의 위치에 대한 모든 방법을 알고 있다면 객체가 바뀔 때 사용자도 바뀌어야 함. 이런 정보를 사용자에게 감춤으로서 변화의 파급을 막을 수 있음. -> [추상 팩토리, 가교, 메멘토, 프록시]
  5. 알고리즘 의존성 : 알고리즘 자체를 확장, 최적화, 교체 할 수 있는데 알고리즘에 종속된 객체라면 알고리즘이 변할 때마다 변경해야 함. 변경이 가능한 알고리즘은 분리. -> [빌더, 반복자, 전략, 템플릿 메서드, 방문자]
  6. 높은 결합도 : 높은 결합도를 가진 클래스들은 독립적으로 재사용하기 어려움. 약한 결합도는 클래스 자체의 재사용을 가능하게 하고 시스템의 이해와 수정, 확장이 용이, 이식성을 증대 시켜줌. 디자인 패턴은 낮은 결합도를 지향. -> [추상 팩토리, 가교, 책임 연쇄, 명령, 퍼사드, 중재자, 감시자]
  7. 서브 클래싱을 통합 기능 확장 : 서브 클래싱은 쉽지 않은 작업. 부모 클래스를 모두 이해해야 하며 단순 확장의 이유로 서브 클래스를 만든다면 클래스의 수를 엄청나게 증가시킬 수 있음. 객체 합성과 위임을 통해 새로운 기능성을 추가할 수 있지만 시스템이 이해하기 어려워지는 단점이 있음. 많은 디자인 패턴에서 서브클래스를 정의하고 적절한 합성을 통해 기능을 재정의하는 방법을 사용함. -> [가교, 책임 연쇄, 장식자, 감시자, 전략]
  8. 클래스 변경이 편하지 못한 점 : 클래스의 변경이 편하지 않거나 어떤 변경 시 기존 서브 클래스의 다수를 수정해야 한다면 -> [적응자, 장식자, 방문자]

 

용어

  • 툴킷 : 사전에 정의된 라이브러리, 이미 있는 클래스. 응용프로그램은 툴킷을 이용하기도 함. 예를 들어 테이블, 스택, 리스트 같은 컬렉션 클래스를 들 수 있음. 응용프로그램의 작업 수행에 도움이 되는 기능을 제공할 뿐. 코드 재사용을 강조한 것.
  • 프레임워크 : 특정한 부류의 소프트웨어에 재사용성을 부여하여 개발할 수 있도록 만들어주는 관련 클래스들의 집합. 프레임워크의 재정의란 프레임워크에 정의한 클래스를 상속받아 특정 응용프로그램을 지원하는 서브클래스를 정의하는 것을 의미. 이러한 재정의 과정을 통해 새로운 응용프로그램을 만들 수 있음. 프레임워크는 응용프로그램에 뼈대를 제공. 클래스와 객체들의 분할, 전체 구조, 클래스와 객체들간의 상호작용, 객체와 클래스 조합 방법, 제어 흐름에 대해 미리 정의.

 

디자인 패턴 vs 프레임워크

  1. 디자인 패턴이 프레임워크보다 더 추상적
  2. 디자인 패턴은 프레임워크에 비해서 소규모의 아키텍처 요소
  3. 디자인 패턴은 프레임워크에 비해 덜 특수화 되어 있음

 

디자인 패턴을 고르는 방법

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

 

각 디자인 패턴을 통한 효과를 볼 수 있는 부분

 

+ Recent posts