NLP Blog

파이썬 디자인 패턴 - 구조디자인패턴 - 컴포지트 패턴

|

2-3. 컴포지트 패턴

  • 컴포지트(composite) 패턴은 계층구조에서 어떤 객체가 다른 객체를 포함하는지(계층구조의 일부로) 여부와 상관없이 구조 내 모든 객체의 동일한 처리 방법을 제공하기 위해 고안된 패턴
  • 이러한 객체를 조합체(composite object) 라고 부르기도 한다.
  • 고전적인 접근법에서는 조합체 내의 각 객체 및 객체 컬렉션이 모두 동일한 기반 클래스를 가진다.
  • 조합 및 비조합 객체는 모두 같은 핵심 메서드 를 공유한다.
  • 하지만 조합 객체는 자식 객체를 추가, 제거, 순회하기 위한 메서드를 추가로 포함

  • 이 패턴은 Inkscape 같은 그래픽 프로그램에서 객체의 그룹을 설정하거나 해제하는 기능을 지원하는 데 흔히 사용된다.
    • 이러한 경우 이 패턴은 매우 유용하다. 왜냐하면 그룹을 설정하거나 해제하기 위해 사용자가 구성 요소를 선택할 때 선택 요소 중 일부는 단일 항목 (ex: 사각형)이지만 나머지는 조합된 것 (ex: 여러 다른 도형을 조합한 얼굴 이미지)일 수도 있기 때문이다.

  • 실례를 살펴보기 위해 비조합 항목과 일부 조합 항목을 생성하고 이 모두를 출력하는 main() 함수를 살펴보자
  • 다음은 stationary1.py에 있는 코드이며 실행결과는 아래와 같다

stationary main

result

  • SimpleItem에는 이름과 가격이 있다.
  • CompositeItem은 이름과 임의 개수의 SimpleItem이나 CompositeItem을 포함하고 있다.
  • 따라서 조합 항목은 무제한으로 중첩될 수 있다.
  • 조합 항목의 가격은 포함된 항목 가격의 총합이다.

  • 이 예제에서 연필 세트는 연필, 자, 지우개로 구성된다.
    • 포장된 연필 세트는
      • 먼저 포장 상자를 만들고
      • 그 안에 연필 세트를 넣고
      • 추가로 연필하나를 넣는다.
  • 여기서는 컴포지트 패턴을 구현하는 두 가지 방법을 살펴보겠다.
    • 하나는 전통적인 접근법
    • 다른 하나는 조합 및 비조합 객체를 모두 표현하는 단일 클래스를 활용하는 방법

2-3-1. 전통적인 조합/비조합 계층구조

  • 전통적인 접근법은 모든 종류의 항목에 조합 여부에 상관없이 공통의 추상 기반 클래스를 제공
  • 조합체에 대해서는 추가적인 추상 기반 클래스를 제공하는 것
  • 클래스 구조는 아래와 같다

class structure


  • 기반 클래스인 AbstractItem 부터 살펴보자

AbstractItem

  • 이 클래스에서 파생될 하위 클래스가 자신이 복합 객체인지 여부를 알려주게 하고싶다.
  • 또한 모든 하위 클래스가 순회 가능하게 만들 것이다.
    • 이때 기본 동작은 빈 시퀀스에 대한 반복자를 돌려주는 것이다.
  • AbstractItem 클래스에는 추상 메서드나 프로퍼티가 적어도 하나는 포함돼 있기 때문에 AbstractItem 클래스의 객체는 생성할 수 없다

SimpleItem class

  • 위 코드는 비조합 항목에 대한 SimpleItem 클래스다.
    • nameprice 애트리뷰트가 있다.
  • SimpleItemAbstractItem을 상속하므로 모든 추상 애트리뷰트 메서드를 재구현해야 한다.
    • composite 애트리뷰트만 구현하면 된다.
    • AbstractItem__iter__() 메서드는 추상메서드가 아니므로 여기서 재구현 할 필요가 없고, 기반 클래스의 메서드를 그대로 가져와 빈 시퀀스에 대한 이터레이터를 반환하면 된다.
  • SimpleItem은 비조합 객체지만 SimpleItem이나 CompositeItem 모두 같은 방식으로 다룰 수 있으므로 (적어도 순회할 때는) 이러한 방법이 적절하다.
    • ex: 이러한 객체를 섞어서 itertools.chain() 함수에 전달할 수 있다.
  • print() 메서드는 조합 및 비조합 객체 항목을 출력한다.
    • 포함된 항목을 계층구조 내에서의 단계에 따라 들여쓰기 한다.

AbstractCompositeItem class

  • 이 클래스는 CompositeItem의 기반 클래스 역할을 하며, 조합 객체에 대한 추가, 제거 ,순회 기능을 제공
  • AbstractCompositeItem의 인스턴스를 직접 만들 수는 없다.
    • 추상 애트리뷰트인 composite()를 상속했지만 구현하지는 않았기 때문
  • add() 메서드는 하나 이상의 항목(SimpleItemCompositeItem 모두 가능)을 받아 이것들을 조합객체의 자식 목록에 추가한다.
    • first 인자를 생략하고 *items만 사용할 수는 없다.
    • 이렇게 만들경우 빈 리스트를 추가할 수 있게 되어, 사용자 코드에 논리오류가 남을 여기가 있기 때문
    • 순환 참조를 막기위한, 즉 조합 항목에 자기 자신을 추가하는 등의 문제를 막기 위한 조치는 여기서 취하지 않았다.
  • remove() 메서드는 항목을 제거할 때 한 번에 하나만 제거하는 간단한 접근법을 취함
    • 물론 제거한 항목이 조합 항목이라면, 해당 항목을 제거하는 것은 그 자식 또는 자손 항목을 모두 제거하는 셈이 된다.
  • __iter__() 특수 메서드를 재구현 하면 조합 객체의 자식 항목을 for루프, (리스트) 내장 구문, 제너레이터 등에서 순회 할 수 있다.
    • 대부분의 경우 메서드 본문을 for item in self.children: yield item 이라고 쓸 수도 있지만 self.children은 시퀀스(리스트)이기 때문에 그러한 경우에 대한 내장함수 iter()를 활용할 수 있다.

CompositeItem clas

  • 이 클래스는 실제 조합 항목을 나타낸 것이다.
    • 자신의 name 애트리뷰트를 가지고 있지만 조합(자식 항목의 추가, 제거, 순회등)과 관련된 모든 처리 코드는 기반 클래스에 있다.
    • 기반 클래스의 추상 애트리뷰트인 composite()를 구현했기 때문에 CompositeItem의 인스턴스를 생성할 수 있다.
  • price() 는 읽기전용 애트리뷰트이다.
    • 조합 항목의 가격을 모든 자식 항목 가격의 합계로 누적해서 계산하기 위해 내장함수 sum()의 인자로 제너레이터 표현식을 전달
    • 이때 자식 항목에 조합 항목이 있다면, 마찬가지로 그 아래 자식 가격의 합계를 재귀적으로 누적
    • for item in self 표현식은 실제로는 iter(self)를 호출해서 self에 대한 이터레이터를 얻는다.
      • 그 결과로 __iter__() 특수 메서드를 호출하며, 이 메서드는 self.children에 대한 이터레이터를 반환
  • print() 메서드의 첫 부분은 SimpleItem.print() 메서드와 같다
    • child에 대한 처리를 추가함

stationary1.py structure

  • 위 예제의 SimpleItemCompositeItem은 모두 대부분의 사례에서 그대로 활용할 수 있다.
    • 하지만 더 계층구조를 다듬을 필요가 있다면, 이 클래스나 해당 추상 기반 클래스를 상속한 클래스를 활용할 수도 있다.
  • 여기서 AbstractItem, SimpleIterm, AbstractCompositeItem, CompositeItem 클래스는 모두 완벽하게 잘 동작
    • 그러나 필요이상으로 고드가 길고, 비조합 클래스에는 없는 add()remove() 같은 메서드 가 조합클래스에 있기 때문에, 단일 인터페이스를 사용하는 것도 아니다.
  • 이를 해결하기 위한 방법을 다음 절에서 소개


2-3-2. (비)조합 객체를 위한 단일 클래스

  • 앞에 있는 네 개의 클래스 (추상 클래스 둘, 구상 클래스 둘)에는 해줘야 할게 많음
  • 그리고 조합 클래스에서만 add()remove() 메서드를 제공하기 때문에 완변한 공통 인터페이스도 제공하지 않는다.
  • 약간의 추가 비용(비조합 항목마다 빈 리스트 애트리뷰트 하나(children list), 조합 항목마다 float값 하나(price attribute))을 감수 할 수 있다면 조합 및 비조합 항목을 표현하는 단일 클래스를 활용할 수 있다.
    • 이렇게 하면 인터페이스가 완벽히 동일해진다는 이점을 얻을 수 있는데, 조합 항목이 아닌 어떤 항목이든 add()remove() 메서드를 호출해 예상한 결과를 얻을 수 있기 때문

  • 이번 절에서는 다른 클래스를 필요로 하지 않는, 조합 및 비조합 항목 모두를 위한 새로운 Item 클래스를 만든다
  • 인용할 코드는 stationery2.py 이다

Item __init__

  • __init__() 메서드의 인자가 다소 복잡해 보인다.
    • 항목을 생성하는데 Item()을 직접 호출할 것이 아니기 때문에 괜찮다
  • 각 항목에는 name이 필요하다. 각 항목은 price가 붙어 있으며, 기본값(0.00)을 갖는다
    • 또한 모든 항목은 0개 이상의 자식 항목(*items)를 가질 수 있으며, 이 값은 self.children에 저장된다.
      • 이 때 비조합 항목은 빈 리스트를 갖는다.

create compose

  • 클래스 객체를 호출해서 항목을 생성하는 대신 더 깔끔하게 인자를 취해 Item을 반환하는 두 종류의 팩터리 클래스 메서드를 제공
    • SimpleItem("Ruler", 1.60) or CompositeItem("Pencil Set", pencil, ruler, eraser)
    • Item.create("Ruler", 1.60) 이나 Item.compose("Pencil Set", pencil, ruler, eraser)로 사용
  • Item()을 직접 사용 가능 (__init__())

make methods

  • 클래스 메서드와 같은 역할을 하는 두 팩터리 함수를 제공
  • 이런 함수는 모듈을 활용할 때 편리
    • ex: Item클래스가 Item.py 모듈에 있다면
      • Item.Item.create("Ruler", 1.60)
      • Item.make_item("Ruler", 1.60)

composite

  • 이 프로퍼티는 앞의 것과는 다른데, 어떤 항목이 조합 항목이거나 조합 항목이 아닐 수도 있기 때문
    • Item 클래스에서 조합 항목은 self.children 리스트가 비어 있지 않은 것이다.

add method

  • add() 메서드를 이전과는 약간 다르게 더 효율적인 방식으로 작성
    • itertools.chain() : 임의 개수의 iterable 을 받아, 함수에 전달된 모든 iterable을 연결한 것과 같은 하나의 iterable을 반환
  • 이 메서드는 조합 여부에 관계없이 어떤 항목에서도 호출 가능
    • 비조합 항목에서 이 함수를 호출하면 해당 항목이 조합 항목이 된다.
  • 비조합 항목을 조합 항목으로 변경할 때의 미묘한 부작용이 존재
    • 항목 자기 자신의 가격이 감춰진다는 점
    • 조합 항목의 가격은 자식 항목 가격의 합계이기 때문
    • 물론 설계 관점에 따라 다른 결정(자신의 가격값을 유지하는 것과 같은)도 가능

remove method

  • 조합 항목의 마지막 자식 항목을 삭제하면 해당 항목은 이제 비조합 항목이 된다.
  • 그런데 조금 미묘한 부분은 이제 이 항목의 가격은 자식 항목 가격(이제는 존재하지 않는) 의 합계가 아닌 비공개 self.__price 애트리뷰트의 값이 된다는 점
    • 이에 대응하기 위해 __init__() 메서드에서 모든 항목에 가격 기본값을 설정

__iter__ method

  • 이 메서드는 조합 항목이라면 자식에 대한 이터레이터를, 비조합 항목이라면 빈 시퀀스를 반환

property

  • price 프라퍼티는 조합 항목이든(자식 항목 가격의 합계), 비조합 항목이든(항목가격) 모두 잘 동작해야 한다.

print method

  • 마찬가지로, print() 메서드도 조합이나 비조합 항목에 대해 모두 잘 동작 해야 한다.
  • 앞 절의 CompositeItem.print() 메서드와 동일
  • 비조합 항목을 방문할 때 빈 시퀀스에 대한 이터레이터를 반환하기 때문에 항목의 자식을 방문할 때 무한 재귀 호출이 일어나지 않는다.

Comment  Read more

파이썬 디자인 패턴 - 구조디자인패턴 - 브리지 패턴

|

2-2. 브리지 패턴

  • 브리지 패턴은 추상화(인터페이스 또는 알고리즘)와 구체적인 구현을 별도로 구분해야 하는 상황에서 활용
  • 브리지 패턴을 활용하지 않은 접근법
    • 하나 이상의 추상 기반 클래스를 만들고
    • 각 기반 클래스마다 둘 이상의 구체적인 구현 클래스를 제공
  • 브리지 패턴을 활용 하는 경우
    • 두 개의 독립적인 클래스 계층을 둔다
      • 기능(인터페이스와 고수준 알고리즘)을 정의하는 “추상” 클래스 계층
      • 추상적 기능이 결국 실행할 구현 코드를 제공하는 “구상” 클래스 계층
    • 추상클래스는 구상클래스의 인스턴스 가운데 하나와 합성
      • 이 인스턴스는 추상적 인터페이스와 구체적 기능사이의 다리 역할을 한다.
  • 앞에서 살펴본 어댑터 패턴에서 HtmlRenderer클래스는 브리지 패턴을 활용했다고도 할 수 있다. 그리기 기능을 제공하기 위해 HtmlWriter를 합성했기 깨문

  • 이번 예제로는 측정 알고리즘을 활용해 막대그래프를 그리는 클래스를 생성하되, 실제 그래프를 그리는 기능은 다른 클래스가 담당하게 한다고 하자.

barchart1.py BarCharter class

  • BarCharter클래스에 막대 그래프를 그리는 알고리즘을 구현하되 (render() 메서드에), 그리기 자체는 이를 위해 구현한 객체에 의존
  • 이 객체는 특별한 막대 그래프 그리기 인터페이스를 따라야 한다.
    • 이 인터페이스에 필요한 메서드로는 initialize(int, int), draw_caption(str), draw_bar(str, int), finalize()가 있다

  • 이전 절과 마찬가지로 isinstance() 테스트를 활용해 전달된 renderer 객체가 우리가 필요로 하는 인터페이스를 지원하는지 확인하는 동시에 그리기 클래스가 어떤 특별한 기반 클래스를 상속하는지는 않아도 되게 할 것
  • 이번 인터페이스 검사 클래스는 앞에서 처럼 10줄 정도의 클래스를 생성하는 대신 단 두줄의 코드를 생성

BarRenderer

  • 위 코드는 abc 모듈과 함께 동작하는데 필요한 메타클래스를 가진 BarRenderer 클래스를 생성
  • 이 클래스는 Qtrac.has_method() 함수에 전달
  • 이 함수는 클래스 데코레이터를 반환
  • 데코레이터는 __subclasshook__() 클래스 메서드를 클래스에 추가
  • 이 새로운 메서드는 BarRendererisinstance() 호출의 인자로 전달될 때마다 지정된 메서드가 있는지 검사 (클래스 데코레이터는 2장 4절 에서 설명)

Qtrac hasmethod

  • Qtrac.py 모듈의 has_methods() 함수는 필요한 메서드를 포착해 클래스 데코레이터 함수를 생성한 다름 이 함수를 반환
  • 데코레이터 자체는 __subclasshook__() 함수를 생성한 다음, 이를 내장 함수인 classmethod()를 통해 기바느 클래스에 클래스 메서드로 추가한다.
  • __subclasshook__()함수는 본질적으로 어댑터 패턴에서 논의한 바와 같다
  • 다만 이번에는 기반클래스를 하드코딩하는 대신 데코레이션 대상 클래스(Base)를 사용하고, 하드코딩한 메서드 이름 집합 대신 데코레이터에 전달된 메서드 이름들(methods)을 사용한다

  • 제네릭 추상 기반 클래스를 상속하는 식으로 동일한 메서드 검사 기능을 수행하는 것도 가능

barchart3.py BarRenderer

  • Qtrac.py에 있는 Qtrac.Requirer 클래스는 @has_methods 클래스 데코레이터를 통해 동일한 검사 기능을 수행하는 추상 기반 클래스다.

qtrac requirer


barchart1.py main

  • main() 함수에서는 출력할 데이터를 성정한 다음 각각 다른 구현 방법을 활용하는 두 종류의 막대 그래프 그리기 객체를 생성한 이후
  • 이를 활용해 막대 그래프를 그린다.

  • text barchart

text barchart

  • image barchart

imagebarchart

  • 막대그래프 그리기 인터페이스 및 클래스

class interface


TextBarRenderer

  • 이 클래스에서는 텍스트로 sys.stdout에 출력하는 막대 그래프 그리기 인터페이스를 구현 한다.
  • 참고로 이 클래스에서 finalize() 메서드는 아무것도 하지 않는다. 하지만 막대 그래프 그리기 인터페이스를 만족하려면 반드시 있어야 한다.


  • barchart1.py에서 ImageBarRenderer 클래스는 cyImage 모듈을 활용한다…….

ImgaeBarRenderer1

  • Image 모듈은 픽셀 하나를 알파(투명도), 빨강, 초록, 파랑의 네가지 색상 요소로 인코딩한 부호없는 32비트 정수로 표현한다.
  • 이 모듈은 색 이름을 받아 그에 해당하는 부호 없는 정수값을 반환하는 Image.color_for_name() 메서드를 제공
    • 이 때 색 이름으로는 X11의 rgb.txt에 있는 이름 (ex: “sienna”) 이나 HTML 스타일 이름 (ex: “#A0522D”) 중 하나를 쓸 수 있다.
  • 위 코드에서는 막대 그래프의 막대에 쓰일 색 리스트를 만들었다.

ImageBarRenderer __init__

  • 이 메서드를 통해 사용자가 원하는 대로 몇가지 값을 설정할 수 있으며, 이에 따라 막대 그래프의 모양이 바뀐다.

ImageBarRenderer initialize

  • 이 메서드와 이후 메서드들은 막대 그래프 그리기 인터페이스의 일부이므로 반드시 있어야 한다. (initialize(), draw_caption(), draw_bar(), finalize())
  • initialize() 메서드에서는 크기가 막대 개수, 폭과 최대 높이에 비례하고, 기본색은 흰색인 새 이미지를 생성한다.
  • self.index 변수는 0부터 시작해 몇 번째 막대를 그려야 할지 파악하는데 활용

ImgaeBarRenderer draw_caption

  • Image 모듈은 텍스트 그리기를 지원하지 않으므로 caption 값은 이미지 파일명으로 사용

ImgaeBarRenderer draw_bar

  • 이 메서드는 사용할 색을 COLORS에서 선책한다
  • 다음으로 현재 (self.index) 막대의 좌표(왼쪽 상단과 오른쪽 하단)를 계산하고 self.image 인스턴스(Image.Image 타입의)에게 지정된 좌표에 사각형을 color의 색으로 채워서 그리도록 요청
  • 그다음 인덱스를 하나 증가시켜 다음막대를 그리게 한다.

ImageBarRenderer finialize

  • 여기서는 간단히 이미지를 저장하고 그 사실을 사용자에게 콘솔 메시지로 알려준다.

  • 분명 TextBarRendererImageBarRenderer의 구현 방법은 서로 극단적으로 다르다
  • 하지만 두 클래스 모두 브리지를 활용해 BarCharter 클래스에 실제 막대 그래프 그리기 기능을 제공할 수 있다.

Comment  Read more

파이썬 디자인 패턴 - 구조디자인패턴 - 어댑터 패턴

|

2장.구조 디자인 패턴

  • 구조 디자인 패턴은 주로 어떻게 기존 객체를 조합해 새로운 더 큰 객체를 만드는가에 대한 패턴
  • 세가지 주요 주제
    • 인터페이스 맞추기
    • 기능 추가
    • 객체 컬렉션 추가

2-1. 어댑터 패턴

  • 어댑터 패턴은 인터페이스를 맞춰서 한 객체가 호환되지 않은 인터페이스를 가진 다른 객체를 활용 할 수 있게하되, 어떤 클래스도 변경하지 않는 기법
  • 원래 의도한 상황이 아닌 곳에서 변경 불가능한 클래스를 활용하고 싶은 때 유용

제목, 본문, 그리기용 인스턴스를 가지고 페이지를 그려주는 간단한 Page 클래스가 있다고 해보자

Page class

  • Page 클래스는 그리기용 클래스(renderer)가 페이지 그리기 인터페이스로 header(str), paragraph(str), footer()세 메서드를 제공하는 한, 실제 클래스가 무엇인지는 알지도 못하고, 신경 쓰지도 않는다.
  • 이때 전달된 그리기 객체가 Renderer인스턴스임을 보장하고 싶다
    • 간단하지만 약간 문제가 있는 해결책 : assert isinstance(renderer, Renderer)
      • 문제점 1. 더 명확한 TypeError 대신 AssersionError가 발생
      • 문제점 2. 프로그램을 -O(optimize, 최적화) 옵션을 주고 실행하면 assert 구문이 무시되므로 render() 메서드에서 AttributeError오류가 발생
    • 위 코드의 if not isinstance() 구문은 올바른 TypeError를 발생 시키고, -O 옵션과도 상관없이 동작함

  • 위 방법의 문제점 하나는 모든 그리기 객체를 Renderer 클래스의 하위 클래스로 만들어야만 할 것 같다는 점이다. C++ 이라면 분명히 그렇다.
  • 그러나 파이썬의 abc(Abstract Base Class, 추상기반클래스) 모듈이 대안이 될 수 있다.
    • abc는 추상 기반 클래스가 제공하는 인터페이스 점검 기능에 duck typing의 유연함을 결합함
    • 이는 특정한 인터페이스를 만족한다는 것 (즉, 특정 API를 제공)이 보장되지만 특정 기반 클래스의 하위 클래스일 필요는 없는 객체를 생성할 수 있다는 의미다

Renderer class

  • 여기서는 Renderer 클래스의 __subclasshook__() 특수 메서드를 재구현했다.
    • 이 메서드는 내장 함수인 isinstance() 가 활용하며, 첫 번째 인자로 전달된 객체가 두 번째 인자로 전달된 클래스(또는 클래스 튜플 가운데 어느 하나)의 상위 클래스인지 판단한다.
    • collections.ChainMap() 클래스를 활용 (파이썬 3 코드)
    • __subclasshook__() 특수 메서드는 호출 대상 클래스가 Renderer 인지 검사하면서 시작하고, 만약 그렇지 않다면 NotImplemented를 반환
      • 이는 Renderer의 하위 클래스가 __subclasshook__행위를 상속하지 않는다는 의미
      • 이렇게 하는 이유는 추상 기반 클래스를 상속하는 목적은 행위를 상속하는데 있지 않고, 조건을 덧붙여 인터페이스를 더 세분화 하는데 있기 때문
      • 원한다면 하위 클래스에서 정의한 __subclasshook__() 안에서 Renderer.__subclasshook__()을 명시적으로 호출해서 Renderer의 행위를 상속 할 수 있다
    • 이 함수가 True나 False를 반환하면 추상 기반 클래스 쪽에서 코드 실행이 멈추고 bool 값을 반환한다.
    • 만약 NotImplemented를 반환하면 일반적인 상속 기능(하위 클래스, 명시적으로 등록된 클래스의 하위 클래스, 하위 클래스의 하위 클래스 등)이 동작한다.
    • if문의 조건이 만족되면 Subclass 가 상속한 모든 클래스(자기 자신도 포함)를 __mro__() 특수 메서드를 통해 순회해 각 클래스의 비공개 딕셔너리 (__dict__)에 접근한다.
      • __mor__()? : mro는 Method Resolution Order의 약자로 어떤 개게의 메서드를 호출하면 이 순서에 따라 차례대로 메서드가 존재하는지를 검사한다.
    • 이 for문을 시퀀스풀기 (* 연산자)를 활용해 즉시 풀어서 dictionary로 이뤄진 튜플(tuple)을 collections.ChainMap() 함수에 전달
      • 이 함수는 임의 개수의 맵Map(dict나 그와 비슷한)을 인자로 받아 모든 맵을 한친 것 가튼 단일 맵 뷰를 반환
      • 검사 대상 메서드의 이름으로 구성된 튜플을 생성
      • 모든 메서드가 attributes 맵에 있는지 검사
        • attributes 맵의 키는 Subclass 또는 모든 Superclass에 있는 모든 메서드나 애트리뷰트의 이름
      • 모든 메서드가 맵에 있다면 True를 반환
    • 참고로 이 함수에서는 하위 클래스(또는 기반 클래스 가운데 아무거나)에 필요한 메서드와 같은 이름의 애트리뷰트가 있는지만 검사
      • 그래서 메서드 대신 애트리뷰트가 매칭 될 수도 있다.
      • 메서드만 확실하게 매칭하고 싶다면 method in attribuesand callable(method)를 추가해 검사 (이렇게까지 할 필욘없다)
  • 인터페이스 검사를 제공하기 위해 __subclasshook__()를 포함한 클래스를 생성하는 것은 매우 유용
  • 하지만 기반 클래스와 지원 메서드만 차이 나는데도 십여 줄의 코드를 작성해야 한다는 것은 피해야 할 코드 중복으로 볼 수 있다

TextRenderer class

  • 위 코드는 페이지 그리기 인터페이스를 지원하는 간단한 클래스이다
  • header() 메서드는 지정된 폭만큼 중앙에 제목을 출력하고, 다음 줄에는 제목 아래에 = 글자를 출력
  • paragraph() 메서드는 파이썬 표준 라이브러리의 texwrap 모듈을 활용해 지정된 줄 폭에 맞춰 줄바꿈한 단락을 출력
  • footer() 메서드는 아무것도 하지 않지만 인터페이스의 일부이므로 반드시 있어야한다.

HtmlWriter class

  • HtmlWriter 클래스에서는 간단한 HTML 페이지를 출력
  • 이 클래스에 header(), footer()메서드가 있긴 하지만, 페이지 그리기 인터페이스에 약속된 행동과는 다르게 동작 (html방식으로)
  • 따라서 TextRenderer와는 달리 Page 인스턴스에 페이지 출력을 위해 HtmlWriter를 직접 전달할 수 없다.

  • 해결책 중 하나는 HtmlWriter를 상속한 클래스에 페이지 그리기 인터페이스 메서드를 추가하는 것
    • 하지만 이 방법은 다소 취약한 방식
    • 결과 클래스에 HtmlWriter의 메서드와 페이지 그리기 인터페이스의 메서드가 섞여있기 때문
  • 더 나은 해결책은 어댑터를 만드는 것
    • 어댑터는 우리가 사용해야 하는 클래스를 내부에 가지고 있는 클래스
    • 필요한 인터페이스를 제공하고 내부의 클래스와 외부 인터페이스 사이를 중계하는 데 필요한 작업을 처리

page renderer adapter


htmlrenderer class

  • 어댑터 클래스의 코드다
  • 이 클래스의 객체를 생성할 때 HtmlWriter 타입의 htmlWriter를 얻고, 이에 대한 페이지 그리기 인터페이스 메서드를 제공
  • 실제 작업은 합성된 HtmlWriter 객체에서 위임하므로, HtmlRenderer 클래스는 기존 HtmlWriter 클래스에 새로운 인터페이스를 제공하는 wrapper에 불과하다

main method

  • 실제 Page 클래스 인스턴스를 각 그리기 객체를 가지고 생성하는 코드
  • TextRenderer에는 줄당 22자의 폭을 주었다.
  • HtmlRenderer가 사용하는 HtmlWriter에는 결과를 출력할 인자로 열린 파일 객체 전달

Comment  Read more

파이썬 디자인 패턴 - 생성디자인패턴 - 싱글턴 패턴 + 파이썬 생성디자인 패턴 정리

|

1-5. 싱글턴 패턴


  • 싱글턴 패턴은 프로그램 전체에서 어떤 클래스의 인스턴스가 오직 하나만 필요할 때 활용
  • 일부 긱체 지향 언어에서는 싱글턴 생성이 매우 복잡하지만 파이썬은 굉장히 쉽다.
  • 가장 쉬운 방법은 비공개 변수에 저장되어 공개된 함수를 통해서만 접근할 수 있는 전역 상태를 지닌 모듈을 생성하는 것

currnecy rate.py

  • 환율에 대한 dict (key=통화 이름, value=환율)을 반환하는 함수가 필요
  • 이 함수를 여러 번 호출할 수도 있겠지만, 대부분의 경우 이 값을 단 한번만(호출할 때마다가 아니고) 가져오고자 한다
  • 싱글턴 패턴을 활용
    • get.rates dict를 get() 메서드의 attribute로 생성했는데, 이는 비공개 attribute에 해당한다.
    • 공개함수 get()은 처흠 호출되면(또는 refresh=True이면) 환율을 다운로드 한다.
    • 그렇지 않은 경우, 가장 최근에 다운로드한 환율 dict를 반환
    • 클래스를 만들 필요가 없이 싱글턴 데이터값을 갖게 된 셈이다.

파이썬 생성 디자인 패턴의 정리

  • 모든 디자인 패턴은 파이썬으로 간단히 구현이 가능
  • 싱글턴 패턴은 모듈을 활용해 직접적으로 구현 가능
  • 프로토타입 패턴은 클래스 객체에 대한 동적 접근을 허용하는 파이썬의 특성상 필요하지 않음 (copy모듈을 통해 구현 가능)
  • 파이썬에서 가장 유용한 생성 디자인 패턴
    • 팩터리 메서드 패턴
    • 빌더 패턴

Comment  Read more

파이썬 디자인 패턴 - 생성디자인패턴 - 프로토타입 패턴

|

1-4.프로토타입 패턴


  • 프로토타입 패턴은 원래 객체를 복제한 새로운 객체를 만들고, 그 복제본을 변경해 사용하는 패턴이다
  • 일단! 파이썬에서의 새로운 객체를 생성하는 방법을 살펴보자

making point class

  • 위의 Point class가 있을 경우 새로운 객체를 만드는 방법은 7가지가 있다.
    1. Point 클래스 객체를 생성자를 활용해 생성 (정적생성)
    2. eval() 을 사용해 클래스 이름을 매개변수로 전달 (동적생성)
    3. gettattr() 을 사용해 클래스 이름을 매개변수로 전달 (동적생성)
    4. globals() 을 사용해 클래스 이름을 매개변수로 전달 (동적생성) - point3 과 동일한 방식
    5. point5는 클래스 객체와 필요한 인자를 받는 제네릭 함수를 통해 생성
    6. 고전적인 프로토타입 접근법을 생성
      • 먼저 copy.deepcopy()를 사용해 기존 객체를 복제
      • 복제된 객체를 초기화하거나 애트리뷰트를 재설정
    7. 파이썬은 어떤 객체의 클래스 객체 (__class__)에 접근할 수 있다
      • 기존 객체를 복제한 다음 복제본을 수정하는 대신
      • 클래스 객체를 활용해 바로 새로운 객체를 만들 수 있다.

Comment  Read more