GOF의 DesignPattern

2장 사례 연구: 문서 편집기 설계

춤추는수달 2023. 2. 25. 07:41

이 장에서는 Lexi라는 문서 편집 프로그램의 설계 문제를 디자인 패턴을 사용하여 해결하는 방법을 설명한다. 여기서 보여준 설계 문제는 다음 7가지 이다.

  1. 문서 구조
  2. 서식 설정
  3. 사용자 인터페이스 꾸미기
  4. 여러 룩앤필 표준 지원
  5. 여러 윈도우 시스템 지원
  6. 사용자 조작
  7. 철자 검사와 붙임표 처리

이제 각 문제들에 대한 설명과 어떻게 해결했는 지에 대해 알아보자.


1. 문서 구조

문서 편집 프로그램은 편집할 문서에 대한 정보를 구조화할 필요가 있습니다. 쪽, 행, 글자 ,표 등 모든 그려지는 요소들을 글리프(Glyph) 라는 클래스의 서브클래스로 만듭니다. 그리고 재귀적 합성을 통해 글리프 안에 글리프가 들어가는 구조로 만듭니다. 이는 복합체 패턴에 해당합니다.

2. 서식 설정

여기서 말하는 서식이란 대표적으로 문서의 줄을 나누는 것을 말합니다. 이런 서식 설정에는 여러 알고리즘이 존재합니다. 그리고 이런 여러 알고리즘을 사용자의 목적에 따라 다르게 적용시키고 싶습니다. 그리고 알고리즘을 문서 구조와는 독립되게 하고싶습니다. 이럴 때 서식 알고리즘을 캡슐화하여 전략 패턴을 사용합니다.  Glyph의 서브클래스인 Composition(Context)클래스에서 Compositor(Strategey)클래스의 Compose()를 호출하도록 합니다. 그리고 Compositior클래스의 서브클래스에서는 다양한 서식 알고리즘을 구현합니다.

3. 사용자 인터페이스 꾸미기

여기서 사용자 인터페이스 꾸미기란 문서의 테두리, 스크롤 바 같은 것을 말합니다. 이러한 장식 요소들은 기존의 코드를 변경하지 않고도 추가될 수 있어야 합니다. 또한 장식 요소가 포함된 글리프와 그렇지 않은 글리프가 코드에서 똑같이 다루어져야합니다. 이럴 때, 투명한 포함 개념을 적용시킬 수 있습니다.

Border나 Scroller 클래스가 상속받는 MonoGlyph를 만듭니다. 이 클래스는 Glyph를 상속받습니다. 그리고 Border 클래스의 인스턴스가 다른 Glyph들을 포함하도록 합니다. 이 Border 클래스로 다른 글리프들을 장식하는 것입니다. 이 때 Border 클래스도 Glyph인터페이스를 구현했기 때문에 다른 글리프들과 똑같이 취급됩니다. 다만 Draw()를 호출했을 때, 이 Border 인스턴스가 포함하는 다른 글리프 인스턴스들의 Draw()를 호출하게 됩니다. 이는 장식자 패턴을 사용한 것입니다.

4. 여러 룩앤필 표준 지원

여기서 룩앤필이란 사용자와 상호작용할 수 있는 버튼, 메뉴 등과 같은 위젯을 의미합니다. 그런데 이 룩앤필이란 것들은 플랫폼 마다 제공하는 사용자 인터페이스 스타일 지침을 따라야합니다. 다시 말하면, 플랫폼마다 다른 룩앤필 객체들을 생성해야 합니다. 그런데 C++에서 런타임에 생성자를 통해 구현하기는 힘듭니다. 이럴 때 플랫폼에 알맞은 인스턴스를 생성하기 위해 추상 팩토리 패턴을 이용합니다. 

추상 팩토리 클래스, 팩토리 클래스, 추상 제품 클래스, 제품 클래스가 필요합니다. 런타임에 플랫폼을 확인해 추상 팩토리 클래스 인스턴스를 특정 팩토리 클래스 인스턴스로 초기화 하고, CreateProduct() 함수를 호출하면 이 추상 팩토리 객체는 제품 객체를 생성합니다. 그리고 이 제품 객체를 추상 제품 클래스 변수로 받아서 사용할 수 있습니다.

5. 여러 윈도우 시스템 지원

윈도우 시스템도 (위의 룩앤필과 비슷하게) 플랫폼에 따라 다른 방식으로 제공합니다. 그리고 우리는 하나의 프로그램, 하나의 실행 파일로 여러 플랫폼을 지원할 수 있어야합니다. 

그러나 위의 룩앤필 예 처럼 추상 팩토리 클래스를 사용하기는 힘듭니다. 왜냐하면 윈도우 시스템은 이미 플랫폼마다 다른 방식으로 여러 클래스 계층에 둘러쌓여 있기 때문에 하나의 추상 제품 클래스로 묶일 수 없기 때문입니다. 여기선 가교 패턴을 이용합니다. 

윈도우 시스템을 사용하는 응용 프로그램의 입장과, 윈도우 시스템별 내부 구현을 분리하는 것입니다. 응용 프로그램에서 사용할 Window 클래스를 정의하고, 여러 플랫폼 별 윈도우 시스템 구현을 추상화할 WindowImp 클래스를 정의합니다. 그리고 Window 클래스에서 WindowImp 클래스를 포함해 사용하는 형태입니다. 이렇게 하면 WIndow 클래스는 플랫폼에 종속적이지 않게 됩니다. 그리고 WindowImp 클래스를 상속받는 플랫폼별 클래스들을 만들고 필요한 기능들을 플랫폼별 윈도우 시스템에 맞추어 구현하는 것입니다.

6. 사용자 조작

여기서는 사용자가 하는 행동(요청)들과 조작 인터페이스의 종속성을 해결합니다. 예를 들면 메뉴를 클릭해서 문서를 저장할 수도 있지만, 단축키를 통해서도 문서를 저장할 수 있도록 합니다. 여기서는 커맨드 패턴을 사용합니다.

주요 개념은 요청의 캡슐화 입니다. Command 클래스를 만들고, 이 클래스를 상속받는 각 요청 별 클래스를 만듭니다. 그리고 Command 클래스에는 Execute() 함수가 있고 각 서브클래스에서 요청에 해당되는 기능들을 구현합니다. 그리고 사용자 인터페이스에 해당하는 Glyph의 서브 클래스(e.g MenuItem)에서 Command 객체를 포함하고 사용하는 형태입니다. 

이렇게 요청을 캡슐화 함으로써 행동 취소 기능을 만들기 아주 편해집니다. Command 객체들을 리스트에 저장해 놓고 취소하면 최근 저장된 Command 객체의 취소 함수를 호출하는 식입니다.

7. 철자 검사와 붙임표 처리

철자 검사나 붙임표 뿐만 아니라 각종 문서 분석 작업(단어 갯수 세기 등)들도 포함합니다. 이를 위해서는 1번 설계문제에서 만든 문서 구조에 담긴 정보에 접근하는 방법과, 정보를 분석하는 방법을 생각해야 합니다.

정보에 접근(순회)하는 방법은 문서 구조와는 분리되어야 합니다. 또한 정보 분석에는 여러 가지 순회 방법이 필요하므로, 여러 가지 순회 방법을 선택할 수 있어야 합니다. 여기선 반복자 패턴을 이용합니다. iterator 클래스를 만들고 이 클래스를 상속받는 여러 순회 방법에 대한 서브 클래스들을 정의합니다. 그리고 우리가 만든 문서 구조를 각 순회방법에 맞게 순회할 수 있도록 Iterator 서브클래스의 내부를 구현합니다. 이것은 반복자 패턴 입니다.

이번엔 정보를 분석하는 방법에 대해 알아봅시다. 정보 분석은 정보 순회 방법과는 분리되어야합니다. 또한 다양한 분석 방법으로 확장할 수 있어야 합니다. 우선 Visitor 클래스를 정의하고, 이 클래스를 상속받는 다양한 분석 방법에 해당하는 서브 클래스들도 만듭니다. 이 클래스들은 visit() 함수에 자신의 분석 방법을 구현합니다. 그리고 Glyph의 서브클래스에서 넘겨받은 visitor 클래스의 visit()를 호출함으로써, Glyph가 어떤 종류인지 확인할 필요 없이 원하는 분석을 진행할 수 있게 합니다. 이는 방문자 패턴 입니다.

'GOF의 DesignPattern' 카테고리의 다른 글

장식자 패턴  (0) 2023.09.09
3장 팩토리 메서드 패턴  (0) 2023.03.02
3장 빌더 패턴  (0) 2023.03.01
3장. 생성 패턴 & 추상 팩토리 패턴  (0) 2023.02.27
1장 서론.  (0) 2023.02.07