데코레이터 디자인 패턴에 대한 초보자 질문


18

나는 프로그래밍 기사를 읽고 있었고 Decorator 패턴을 언급했다. 나는 잠시 동안 프로그래밍을 해왔지만 공식적인 교육이나 훈련이 없었지만 표준 패턴에 대해 배우려고합니다.

그래서 Decorator를 찾아서 Wikipedia 기사 를 찾았 습니다. 나는 지금 Decorator 패턴의 개념을 이해하고 있지만,이 구절에 약간 혼란 스러웠다.

예를 들어, 윈도우 시스템의 윈도우를 고려하십시오. 창의 내용을 스크롤하기 위해 가로 또는 세로 스크롤 막대를 적절하게 추가 할 수 있습니다. 윈도우가 Window 클래스의 인스턴스로 표시되고이 클래스에 스크롤 막대를 추가 할 수있는 기능이 없다고 가정합니다. 이를 제공하는 서브 클래스 ScrollingWindow를 만들거나 기존 Window 객체에이 기능을 추가하는 ScrollingWindowDecorator를 만들 수 있습니다. 이 시점에서 어느 쪽의 솔루션이든 괜찮을 것입니다.

이제 창에 테두리를 추가하는 기능을 원한다고 가정 해 봅시다. 다시, 우리의 원래 Window 클래스는 지원하지 않습니다. ScrollingWindow 서브 클래스는 이제 새로운 종류의 창을 효과적으로 만들었 기 때문에 문제를 일으 킵니다. 모든 창에 테두리 지원을 추가하려면 하위 클래스 WindowWithBorder 및 ScrollingWindowWithBorder를 만들어야합니다. 분명히,이 문제는 모든 새로운 기능이 추가 될수록 악화됩니다. 데코레이터 솔루션의 경우 새로운 BorderedWindowDecorator를 만들면됩니다. 런타임에 ScrollingWindowDecorator 또는 BorderedWindowDecorator 또는 둘 다를 사용하여 기존 창을 장식 할 수 있습니다.

모든 창에 테두리를 추가 할 때 옵션을 허용하기 위해 기능을 원래 Window 클래스에 추가하는 것이 어떻습니까? 내가 보는 방식으로, 서브 클래 싱은 클래스에 특정 기능을 추가하거나 클래스 메소드를 재정의하기위한 것입니다. 기존의 모든 객체에 기능을 추가해야한다면 왜 슈퍼 클래스를 수정하지 않겠습니까?

이 기사에는 다른 줄이 있습니다.

데코레이터 패턴은 서브 클래 싱의 대안입니다. 서브 클래 싱은 컴파일 타임에 동작을 추가하고 변경 사항은 원래 클래스의 모든 인스턴스에 영향을줍니다. 장식은 개별 객체에 대해 런타임에 새로운 동작을 제공 할 수 있습니다.

"... 변경은 원본 클래스의 모든 인스턴스에 영향을 미칩니다"라고 말하는 곳을 얻지 못합니다. 서브 클래 싱은 어떻게 부모 클래스를 변경합니까? 이것이 서브 클래 싱의 요점이 아닌가?

많은 Wiki와 마찬가지로 기사가 명확하게 작성되지 않았다고 가정합니다. 마지막 줄에서 데코레이터의 유용성을 볼 수 있습니다. "... 개별 개체에 대해 런타임시 새로운 동작을 제공합니다."

이 패턴에 대해 읽지 않고 개별 객체에 대해 런타임에 동작을 변경해야하는 경우 해당 동작을 활성화 / 비활성화하기 위해 수퍼 클래스 또는 서브 클래스에 일부 메소드를 빌드했을 것입니다. 데코레이터의 유용성을 정말로 이해하도록 도와주십시오. 왜 초보자 생각에 결함이 있습니까?


편집 해 주셔서 감사합니다. Walter ... fyi, 저는 "남자"를 사용하여 여자를 배제하지 않고 비공식적 인 인사로 사용했습니다.
Jim

편집은 일반적으로 인사말을 제거하는 것입니다. 질문에 하나를 사용하는 것은 표준 SO / SE 프로토콜이 아닙니다. [걱정하지 마십시오. 질문으로 바로 넘어가는 것은 무례하다고 생각하지 않습니다.]
Farrell

멋지다, 고마워! 그것은 단지 "성별 제거"라고 태그를 붙였고, 내가 잘못된 생각이나 무언가라고 생각하는 사람을 싫어할 것입니다! :)
Jim

나는 "서비스"라는 법적 절차를 피하기 위해 그것들을 많이 사용합니다 : medium.com/@wrong.about/…
Zapadlo

답변:


13

데코레이터 패턴은 상속보다 구성을 선호하는 패턴입니다 [배우는 데 유용한 또 다른 OOP 패러다임]

데코레이터 패턴의 주요 장점은 서브 클래 싱보다 더 많은 믹스 앤 매치 옵션을 허용한다는 것입니다. 예를 들어, 윈도우가 가질 수있는 10 가지의 다른 행동이 있다면, 이는 서브 클래 싱과 함께 모든 다른 조합을 생성해야하며, 이는 많은 코드 재사용을 필연적으로 포함합니다.
그러나 새로운 행동을 추가하기로 결정하면 어떻게됩니까?

데코레이터를 사용하면이 동작을 설명하는 새 클래스를 추가하기 만하면됩니다. 패턴을 사용하면 나머지 코드를 수정하지 않고도이 패턴을 효과적으로 제거 할 수 있습니다.
하위 클래스를 사용하면 악몽이 생길 수 있습니다.
당신이 한 질문은 "하위 클래스가 어떻게 부모 클래스를 바꾸는가?"였습니다. 부모 클래스를 변경하는 것이 아닙니다. 인스턴스라고 할 때 '인스턴트 화'한 모든 객체를 의미합니다 (예 : new명령 을 사용하여 Java 또는 C #을 사용하는 경우 ). 이 변경 사항을 클래스에 추가하면 실제로 필요하지 않더라도 해당 변경 사항을 선택할 수 없습니다.

또는 플래그를 통해 켜고 끄는 모든 기능을 단일 클래스에 넣을 수 있지만 프로젝트가 커질수록 점점 커지는 단일 클래스로 끝납니다.
이런 식으로 프로젝트를 시작하고 효과적인 임계 질량에 도달하면 데코레이터 패턴으로 리팩터링하는 것은 드문 일이 아닙니다.

흥미로운 점 : 동일한 기능을 여러 번 추가 할 수 있습니다. 예를 들어, 필요에 따라 이중, 삼중 또는 임의의 양 또는 경계가있는 창을 가질 수 있습니다.

패턴의 주요 요점은 런타임 변경을 가능하게하는 것입니다. 프로그램이 실행될 때까지 창의 모양을 알지 못할 수 있으므로이를 쉽게 수정할 수 있습니다. 물론, 이것은 서브 클래 싱을 통해 수행 될 수 있지만 훌륭하지는 않습니다.
마지막으로, 봉인 / 최종 클래스 또는 다른 API에서 제공 한 클래스와 같이 편집 할 수없는 클래스에 기능을 추가 할 수 있습니다.


2
감사합니다. Farrell – "대안 적으로, 모든 기능을 단일 클래스에 넣을 수 있습니다. 플래그를 통해 켜거나 끌 수 있습니다. 그러나 프로젝트가 커질수록 점점 커지는 단일 클래스로 끝납니다." 그것은 나를 위해 클릭하게했다. 나는이 문제의 일부가 데코레이터가 나에게 의미가있는 수업을 한 적이 없다고 생각합니다. 크게 생각하면 이점을 분명히 볼 수 있습니다 ... 감사합니다!
Jim

또한 봉인 클래스에 대한 부분은 많은 의미가 있습니다 ... 감사합니다!
Jim

3

서브 클래 싱으로 스크롤바 및 테두리를 추가 할 수있는 가능성을 고려하십시오. 모든 가능성을 원한다면 네 가지 수업 (Python)을 얻습니다.

class Window(object):
    def draw(self):
        "do actual drawing of window"

class WindowWithScrollBar(Window):
    def draw(self):
        Window.draw(self)
        "do actual drawing of scrollbar"

class WindowWithBorder(Window):
    def draw(self):
        Window.draw(self)
        "do actual drawing of border"

class WindowWithScrollBarAndBorder(Window):
    def draw(self):
        WindowWithScrollBar.draw(self)
        WindowWithBorder.draw(self)

이제에서 WindowWithScrollBarAndBorder.draw()창은 두 번 그려지고 두 번째는 구현에 따라 이미 그려진 스크롤 막대를 덮어 쓰거나 덮어 쓰지 않을 수 있습니다. 따라서 파생 코드는 다른 클래스의 구현과 밀접하게 연결되어 있으므로 Window클래스 동작 을 변경할 때마다주의해야합니다 . 해결책은 파생 클래스의 코드를 수퍼 클래스에서 복사하여 붙여 넣은 다음 파생 클래스 요구에 맞게 조정하는 것이지만 수퍼 클래스의 모든 변경 사항을 다시 복사하여 붙여 넣어야하므로 파생 클래스가 다시 (복사-붙여 넣기 조정의 필요성을 통해) 기본 클래스에 단단히 연결됩니다. 또 다른 문제는 창에 있거나 없을 수도있는 또 다른 속성이 필요한 경우 모든 클래스를 두 배로 늘려야한다는 것입니다.

class Window(object):
    ...

class WindowWithGimmick(Window):
    ...

class WindowWithScrollBar(Window):
    ...

class WindowWithBorder(Window):
    ...

class WindowWithScrollBarAndBorder(Window):
    ...

class WindowWithScrollBarAndGimmick(Window):
    ...

class WindowWithBorderAndGimmick(Window):
    ...

class WindowWithScrollBarAndBorderAndGimmick(Window):
    ...

이는 | f |와 상호 독립된 기능 f의 집합을 의미합니다. 기능의 개수이므로 2 ** | f | 예를 들어 수업 10 개의 기능이 있다면 1024 개의 엄밀한 클래스가 제공됩니다. 데코레이터 패턴을 사용하면 모든 기능이 독립적이고 느슨하게 결합 된 클래스를 갖게되며 1 + | f | 클래스 (위 예제의 경우 11입니다).


내 생각은 클래스를 계속 추가하는 것이 아니라 원래의 하위 클래스에 funtionality를 추가하는 것입니다. 그래서 Window (object) 클래스에서 : scrollBar = true, border = false 등이 있습니다 ... Farrell은 잘못 될 수있는 곳을 보여줍니다. +1!
Jim

글쎄, 내가 그렇게 할 담당자가 있으면 +1!
Jim

2

나는이 특정 패턴에 대한 전문가는 아니지만 그것을 보았을 때 Decorator 패턴은 수정하거나 서브 클래스 할 수없는 클래스에 적용될 수 있습니다 (예 : 코드가 아니고 봉인되어있을 수 있습니다) ). 예제에서 Window 클래스를 작성하지 않고 소비하고 있다면 어떻게해야합니까? Window 클래스에 인터페이스가 있고 해당 인터페이스에 대해 프로그래밍하는 경우 Decorator는 동일한 인터페이스를 사용할 수 있지만 기능을 확장 할 수 있습니다.

언급 한 예는 실제로 여기에서 매우 깔끔하게 설명됩니다.

상속을 사용하여 객체의 기능 확장을 컴파일 타임에 정적으로 수행 할 수 있지만 객체가 사용될 때 객체의 기능을 동적으로 (런타임에) 확장해야 할 수도 있습니다.

그래픽 창의 전형적인 예를 고려하십시오. 예를 들어 창에 프레임을 추가하여 그래픽 창의 기능을 확장하려면 Windowd 클래스를 확장하여 FramedWindow 클래스를 만들어야합니다. 프레임이있는 창을 만들려면 FramedWindow 클래스의 객체를 만들어야합니다. 그러나 일반 창으로 시작하여 런타임에 기능을 확장하여 프레임 창으로 만드는 것은 불가능합니다.

http://www.oodesign.com/decorator-pattern.html

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.