Pregunta de novato sobre el patrón de diseño del decorador

18

Estaba leyendo un artículo de programación y mencionaba el patrón Decorador. He estado programando durante un tiempo pero sin ningún tipo de educación o capacitación formal, pero estoy tratando de aprender sobre los patrones estándar y demás.

Entonces busqué el Decorador y encontré un artículo de Wikipedia sobre él. Ahora entiendo el concepto del patrón Decorador, pero este pasaje me confundió un poco:

Como ejemplo, considere una ventana en un sistema de ventanas. Para permitir el desplazamiento del contenido de la ventana, podemos agregarle barras de desplazamiento horizontales o verticales, según corresponda. Suponga que las ventanas están representadas por instancias de la clase Window, y suponga que esta clase no tiene funcionalidad para agregar barras de desplazamiento. Podríamos crear una subclase ScrollingWindow que los proporcione, o podríamos crear un ScrollingWindowDecorator que agregue esta funcionalidad a los objetos de Windows existentes. En este punto, cualquier solución estaría bien.

Ahora supongamos que también deseamos la posibilidad de agregar bordes a nuestras ventanas. Nuevamente, nuestra clase de ventana original no tiene soporte. La subclase ScrollingWindow ahora plantea un problema, porque efectivamente ha creado un nuevo tipo de ventana. Si deseamos agregar soporte de borde a todas las ventanas, debemos crear subclases WindowWithBorder y ScrollingWindowWithBorder. Obviamente, este problema empeora con cada nueva característica que se agrega. Para la solución de decorador, simplemente creamos un nuevo BorderedWindowDecorator; en tiempo de ejecución, podemos decorar ventanas existentes con ScrollingWindowDecorator o BorderedWindowDecorator o ambos, según lo creamos conveniente.

OK, cuando dicen agregar bordes a todas las ventanas, ¿por qué no simplemente agregar funcionalidad a la clase de ventana original para permitir la opción? A mi modo de ver, la subclasificación es solo para agregar una funcionalidad específica a una clase o anular un método de clase. Si necesito agregar funcionalidad a todos los objetos existentes, ¿por qué no modificaría la superclase para hacerlo?

Había otra línea en el artículo:

El patrón decorador es una alternativa a la subclase. La subclasificación agrega comportamiento en tiempo de compilación, y el cambio afecta a todas las instancias de la clase original; la decoración puede proporcionar un nuevo comportamiento en tiempo de ejecución para objetos individuales.

No entiendo donde dicen "... el cambio afecta a todas las instancias de la clase original": ¿cómo cambia la subclase la clase principal? ¿No es ese el objetivo de la subclase?

Asumiré que el artículo, como muchos Wiki, simplemente no está escrito claramente. Puedo ver la utilidad del Decorador en esa última línea: "... proporcionar un nuevo comportamiento en tiempo de ejecución para objetos individuales".

Sin haber leído sobre este patrón, si tuviera que cambiar el comportamiento en tiempo de ejecución para objetos individuales, probablemente habría incorporado algunos métodos en la superclase o subclase para habilitar / deshabilitar dicho comportamiento. Por favor, ayúdame a comprender realmente la utilidad del Decorador, y ¿por qué mi pensamiento de novato es erróneo?

Jim
fuente
aprecio la edición, Walter ... para tu información, usé "chicos" no para excluir a las mujeres, sino como un saludo informal.
Jim
La edición solo sería eliminar el saludo en general. No es un protocolo SO / SE estándar usar uno en las preguntas [no se preocupe, no creemos que sea grosero saltar directamente a la pregunta]
Farrell
¡Genial, gracias! ¡era solo que lo etiquetaba como "eliminar el género" y odiaría a cualquiera que piense que soy misógino o algo así! :)
Jim
Los uso mucho para evitar esas cosas procesales legales llamadas "servicios": medium.com/@wrong.about/…
Zapadlo

Respuestas:

13

El patrón decorador es uno que favorece la composición sobre la herencia [otro paradigma de POO que es útil conocer]

El principal beneficio del patrón decorador: la subclasificación es permitir más opciones de mezcla y combinación. Si tiene, por ejemplo, 10 comportamientos diferentes que puede tener una ventana, entonces esto significa, con subclases, que necesita crear cada combinación diferente, que también inevitablemente incluirá una gran cantidad de reutilización de código.
Sin embargo, ¿qué sucede cuando decides agregar un nuevo comportamiento?

Con el decorador, solo agrega una nueva clase que describe este comportamiento, y eso es todo: el patrón le permite colocarlo de manera efectiva sin ninguna modificación en el resto del código.
Con subclases, tienes una pesadilla en tus manos.
Una pregunta que hizo fue "¿cómo la subclase cambia la clase principal?" No es que cambie la clase principal; cuando dice una instancia, significa cualquier objeto que haya 'instanciado' [si está utilizando Java o C #, por ejemplo, mediante el newcomando]. A lo que se refiere es que, cuando agrega estos cambios a una clase, no tiene otra opción para que ese cambio esté allí, incluso si realmente no lo necesita.

Alternativamente, puede poner toda su funcionalidad en una sola clase con activada / desactivada a través de indicadores ... pero esto termina con una sola clase que se hace más y más grande a medida que su proyecto crece.
No es inusual comenzar su proyecto de esta manera, y refactorizar en un patrón de decorador una vez que alcance una masa crítica efectiva.

Un punto interesante que debe hacerse: puede agregar la misma funcionalidad varias veces; por lo que podría, por ejemplo, tener una ventana con el doble, triple o cualquier cantidad o bordes que necesite.

El punto principal del patrón es habilitar los cambios en el tiempo de ejecución: es posible que no sepa cómo desea que se vea la ventana hasta que se ejecute el programa, y ​​esto le permite modificarla fácilmente. Por supuesto, esto se puede hacer a través de la subclase, pero no tan bien.
Y, por último, permite agregar funcionalidad a las clases que quizás no pueda editar, por ejemplo, en clases selladas / finales, o las que se proporcionan desde otras API

Farrell
fuente
2
Gracias, Farrell, cuando dices "Alternativamente, puedes poner toda su funcionalidad en una sola clase con activada / desactivada mediante banderas ... pero esto termina con una sola clase que se hace más y más grande a medida que tu proyecto crece". eso me hizo clic para mí. Creo que parte del problema es que nunca he trabajado en una clase que fuera tan grande que el decorador tuviera sentido para mí. Pensando en grande, definitivamente puedo ver los beneficios ... ¡gracias!
Jim
Además, la parte sobre las clases selladas tiene mucho sentido ... ¡gracias!
Jim
3

Considere las posibilidades de agregar barras de desplazamiento y bordes con subclases. Si quieres todas las posibilidades, obtienes cuatro clases (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)

Ahora, en WindowWithScrollBarAndBorder.draw(), la ventana se dibuja dos veces, y la segunda vez puede o no sobrescribir la barra de desplazamiento ya dibujada, dependiendo de la implementación. Por lo tanto, su código derivado está estrechamente vinculado a la implementación de otra clase, y debe preocuparse por eso cada vez que modifique el Windowcomportamiento de la clase. Una solución sería copiar y pegar el código de las superclases a las clases derivadas y ajustarlo a las necesidades de las clases derivadas, pero cada cambio en una superclase debe volver a pegarse y ajustarse nuevamente, por lo que nuevamente las clases derivadas son estrechamente acoplado a la clase base (a través de la necesidad de copiar-pegar-ajustar). Otro problema es que si necesita otra propiedad que una ventana puede tener o no, debe duplicar cada clase:

class Window(object):
    ...

class WindowWithGimmick(Window):
    ...

class WindowWithScrollBar(Window):
    ...

class WindowWithBorder(Window):
    ...

class WindowWithScrollBarAndBorder(Window):
    ...

class WindowWithScrollBarAndGimmick(Window):
    ...

class WindowWithBorderAndGimmick(Window):
    ...

class WindowWithScrollBarAndBorderAndGimmick(Window):
    ...

Eso significa para un conjunto de características mutuamente independientes f con | f | siendo la cantidad de características, debe definir 2 ** | f | clases, por ejemplo. Si tiene 10 características, obtendrá 1024 clases estrechamente ajustadas. Si usa el Patrón de decorador, cada característica tiene su propia clase independiente y poco acoplada y solo tiene 1 + | f | clases (eso es 11 para el ejemplo anterior).

Pillmuncher
fuente
Mira, mi pensamiento no sería seguir agregando clases, sería agregar la funcionalidad a la (sub) clase original. así que en la clase Window (objeto): tienes scrollBar = true, border = false, etc. La respuesta Farrell me muestra dónde puede salir mal, sin embargo, solo conduce a clases hinchadas y tal ... gracias por la respuesta, +1!
Jim
bueno, ¡+1 una vez que tenga el representante para hacerlo!
Jim
2

No soy un experto en este patrón en particular, pero tal como lo veo, el patrón Decorador se puede aplicar a clases que es posible que no tenga la capacidad de modificar o subclasificar (puede que no sean su código y estén selladas, por ejemplo ) En su ejemplo, ¿qué pasa si no escribió la clase Window pero la está consumiendo? Siempre que la clase Window tenga una interfaz y usted programe contra esa interfaz, su Decorador puede usar la misma interfaz pero extender la funcionalidad.

El ejemplo que mencionas en realidad está cubierto aquí bastante claramente:

La extensión de la funcionalidad de un objeto se puede hacer de forma estática (en tiempo de compilación) mediante el uso de la herencia; sin embargo, puede ser necesario extender la funcionalidad de un objeto dinámicamente (en tiempo de ejecución) a medida que se usa un objeto.

Considere el ejemplo típico de una ventana gráfica. Para ampliar la funcionalidad de la ventana gráfica, por ejemplo, agregando un marco a la ventana, se requeriría extender la clase de ventana para crear una clase FramedWindow. Para crear una ventana enmarcada es necesario crear un objeto de la clase FramedWindow. Sin embargo, sería imposible comenzar con una ventana simple y ampliar su funcionalidad en tiempo de ejecución para convertirse en una ventana enmarcada.

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

Dan Diplo
fuente