Extender ambos lados de un patrón de visitante / puente

votos
3

Digamos que tengo una jerarquía de clases, usemos los Shapeejemplos clásicos :

abstract class Shape
Circle : Shape
Square : Shape

Tengo una segunda jerarquía de clases de renderizador que manejan la representación de formas de diferentes maneras:

abstract class ShapeRenderer
HtmlShapeRenderer : ShapeRenderer
WindowsFormsShapeRenderer : ShapeRenderer

Permitir que estos varíen de forma independiente implicaría tradicionalmente el uso del patrón Bridge. Permitir que las acciones de renderización se extiendan sin modificar las Shapeclases implicaría tradicionalmente el patrón Visitor.

Sin embargo, ambos se enfocan exclusivamente en extender el lado de la implementación y no el lado de la abstracción. Digamos que quería agregar una nueva Shape, digamos Triangle, quiero poder admitir la renderización Triangletambién. Dado que tanto el patrón Visitor como el Bridge se basan en aplanar la jerarquía de abstracción en un conjunto de métodos, por ejemplo:

public abstract class ShapeRenderer
{
     public abstract void RenderCircle(Circle c);
     public abstract void RenderSquare(Square s);
}

La única forma de extender la Shapejerarquía es modificar el código de la ShapeRendererclase base , que es un cambio radical.

Jon, para aclarar: el uso de Bridge o Visitor permite a los clientes proporcionar implementaciones alternativas de renderizado, pero les exige conocer todas las Formas potenciales. Lo que me gustaría hacer es permitir que los clientes también puedan extender la Shapeclase y requerirles que proporcionen una implementación de representación para su nueva clase. De esta manera, el código existente puede funcionar con cualquier tipo de Shape, sin preocuparse por los detalles de su representación.

¿Existe una solución común para este tipo de problema utilizable en C #?

Publicado el 09/12/2008 a las 18:00
fuente por usuario
En otros idiomas...                            


4 respuestas

votos
2

¿Qué tal el patrón de estrategia ? Donde la estrategia es una referencia a la implementación de RenderEngine. Cuando desea agregar una nueva forma, crea una nueva implementación de los motores de representación que conocen la nueva implementación de Shape e implementa la función de representación correspondiente. Usted agrega una función virtual a Shape que actúa como una función auxiliar para seleccionar la función de representación de forma correcta, es decir, los objetos Circle llaman a la función renderCircle (), etc.

En C ++, podría verse algo así como:

class Triangle : public Shape
{
  public:
      Triangle( const RenderEngine& whichRenderEngine );
      void render( void ) { renderStrategy->renderTriangle( *this );

  private:
      RenderEngine* renderStrategy;
};

class TriangleRender : HTMLShapeRender
{
   public:
      // if inheriting from concrete class, all other rendering functions 
      // already exist... otherwise re-implement them here.

      void renderTriangle( const Triangle& t ) { /* impl */ }
};

HTMLRenderer r; // doesn't know about Triangles.
Circle c( &r );
c.render();

Square s( &r );
s.render();

// Now we add Triangle
TriangleRenderer tr;
Triangle t( &tr );
t.render();

Square s2( &tr );  // tr still knows how to render squares... 
s2.render();
Respondida el 10/12/2008 a las 04:50
fuente por usuario

votos
2

Creo que debería ser un cambio radical. Si agrega una forma, los renderizadores existentes claramente no serán capaces de hacer frente; deberán cambiarse.

Puede cambiar ShapeRenderer para agregar RenderTriangle () como un método virtual (no abstracto) que simplemente registra el hecho de que no se puede procesar de forma adecuada, y luego arreglar los procesadores uno a la vez, pero fundamentalmente no va a ser capaz de renderizar el nuevo tipo sin más código.

¿Qué tipo de cambio sin interrupción estás realmente esperando lograr?

Respondida el 09/12/2008 a las 18:17
fuente por usuario

votos
1

Mi solución aquí sería usar una Abstract Factory, en cuyo caso cargaría un diccionario de ShapeRenderers por tipo, donde tipo es una subclase de Shape y permitiría a la fábrica proporcionar el ShapeRenderer requerido para cada Shape (y posiblemente plataforma, por ejemplo, Windows, Web, iPhone).

Hacerlo de esta manera significa que agregar una nueva forma requeriría solo un cambio en un almacén de configuración para que la fábrica sepa qué representadores mapear con qué formas y un nuevo ensamblaje que contenga las implementaciones concretas.

Respondida el 09/12/2008 a las 18:58
fuente por usuario

votos
1

Diseñar a una interfaz, no a una implementación.

Hola, hoy puedo usar la misma respuesta dos veces (creo que es discutible que Renderer sea una implementación) ...

No estoy seguro de ir con la clase ShapeRenderer. ¿Qué pasa con un IRenderHTML, IRenderWindows que se implementan mediante las clases de formas?

Obtienes extensibilidad con las Formas así como con las Renderizaciones.

Creo que puede ser mejor OO para decir que tu círculo se rinde, que pasar el círculo a una clase de utilidad para renderizar. Puede agregar fácilmente nuevas formas y nuevas representaciones permitiendo que las formas realicen la representación ellos mismos.

Respondida el 09/12/2008 a las 18:22
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more