2010-11-14 10 views
8

Sé qué es el Patrón de visitante y cómo usarlo; esta pregunta no es un duplicado de este one.C++: dudas sobre el patrón de visitante


Tengo una biblioteca donde pongo la mayor parte del código reutilizable que escribo y que me enlace a la mayoría de mis proyectos.

A menudo necesito agregar funciones a algunas clases, pero sin agregar estas nuevas características a la biblioteca. Permítanme utilizar un ejemplo real:

En este lib Tengo una clase Shape, heredado por CircleShape, PolygonShape y CompositeShape.

Ahora estoy desarrollando una aplicación gráfica donde tengo que hacer que estos Shape, pero no quiero poner una función virtual en la clase renderShape núcleo, ya que algunos de mis proyectos que utilizan Shape no hacen ningún el renderizado y otros proyectos gráficos podrían usar diferentes motores de renderizado (estoy usando Qt para este proyecto, pero para un juego usaría OpenGL, así la función render necesitará diferentes implementaciones).

La forma más famoso de hacerlo es utilizando el patrón del visitante, por supuesto, pero esto hace estallar unas pocas dudas en mi mente:

Cualquier clase de cualquier biblioteca podría ser necesario extender como mi Shape hace. La mayoría de las bibliotecas públicas (sobre todas ellas) no brindan ningún soporte para Patrón de visitante; ¿por qué? ¿por qué debería?

Visitor Pattern es una forma de simular Double Dispatching en C++. No es nativo en C++, y requiere una implementación explícita, lo que hace que la interfaz de clase sea más compleja: no creo que la función applyVisitor esté en el mismo nivel que las funciones de mi clase, veo esto como romper la abstracción.

Explicitly up-casting Shape con dynamic_cast es más caro, pero a mí me parece una solución más limpia.


Entonces, ¿qué debo hacer? ¿Implementando Double Dispatching en todas mis clases de biblioteca? ¿Qué sucede si la biblioteca que proporciona Shape no es mía, pero se encuentra alguna biblioteca de GPL en Internet?

+2

El patrón de visitante no es una forma de obtener múltiples de despacho. Necesita despacho múltiple y, por lo tanto, una técnica para emular el despacho múltiple generalmente se enseña a los programadores de C++ como parte del patrón de visitante. – AProgrammer

+0

¿Necesita despacho múltiple? Quizás el patrón de NVI satisface sus necesidades. Parece que un diseño de NVI podría ayudar pero requiere un procesamiento no virtual y métodos de tipo doRender virtual privado. Entonces, tal vez una clase abstracta entre forma y círculo de formas llamada shaperenderengine. ShapeCircle podría entonces derivar de forma o de shaperenderengine ..... – Eric

Respuesta

13

Primero: "Patrón de visitantes es una forma de simular Doble Despachando en C++ ". Esto es, erm, no del todo correcto. En realidad, el envío doble es una forma de despacho múltiple, que es una forma de simular (los métodos múltiples que faltan) en C++.


si las operaciones en una jerarquía de clases, deben ejecutarse mediante añadiendo funciones virtuales o por visitantes añadiendo está determinada por las probabilidades de la adición de clases frente a la adición de las operaciones:

  • Si el número de clases sigue cambiando más rápidamente t El número de operaciones, usa las funciones virtuales. Eso es porque agregar una clase requiere modificar a todos los visitantes.
  • Si el número de clases es relativamente estable en comparación con el número de operaciones, visitantes uso. Eso es porque agregar una función virtual requiere cambiar todas las clases en la jerarquía.

Sí, muchas bibliotecas no vienen con una interfaz de visitante.
Cuando solo estamos mirando el razonamiento anterior, esto sería correcto si el número de clases cambia a menudo. Es decir, si se lanza una biblioteca con frecuencia, con nuevas clases agregadas constantemente, entonces proporcionar una interfaz de visitante no tendría mucho sentido, porque cada vez que una nueva versión trae nuevas clases, todos los que usan la biblioteca deben adaptar a todos sus visitantes. . Entonces, si solo miramos el razonamiento anterior, una interfaz de visitante parecería ser útil si el número de clases en la jerarquía de clases de una biblioteca rara vez o nunca cambia.

Sin embargo, con las bibliotecas de terceros hay otro aspecto: normalmente, los usuarios no pueden cambiar las clases en la biblioteca.Es decir, si tienen que añadir una operación, la única forma en que puede hacerlo es mediante la adición de un visitante - si la biblioteca ofrece los ganchos para que se conectan a ella.
Así que si está escribiendo una biblioteca y siente que los usuarios deberían poder agregarle operaciones, entonces necesita proporcionarles una forma de conectar sus visitantes a su lib.

+0

¿Entonces crees que debería agregar algo de apoyo para los visitantes de mi biblioteca? ¿Podría hablarme de cualquier biblioteca que use el patrón de visitante para dejarme inspirarme? El único que conozco es Boost :: Variant, pero ese caso es bastante diferente de un uso "normal" como el mío. – peoro

+0

@ peoro: No lo sé, ya que no conozco tu lib. Francamente, no puedo pensar en una sola lib de C++ que llegue con los visitantes.Estoy de acuerdo en que 'boost :: variant' no es su visitante cotidiano corriente. Lamento ser de tan poca ayuda. – sbi

0

Esto no se parece a un caso para el patrón Visitor.

Sugeriría que tenga una clase RenderableShape que agregue un objeto Shape, luego cree subclases para cada forma. RenderableShape tendría un método virtual render.

Si desea admitir varios motores de representación, puede tener una clase base RenderContext que abstraiga operaciones de dibujo, con subclases para cada motor de representación, cada subclase implementando las operaciones de dibujo en términos de su motor de representación. A continuación, tiene RenderableShape::render tome un RenderContext como argumento y dibuje con su API abstraída.

+0

Algunas funciones dentro de mi biblioteca devuelven punteros/referencias a 'Shape'. Por ejemplo, el 'CompositeShape' del que hablé tiene un conjunto de' Shape * 'que puede ser de cualquier tipo. Entonces, incluso si guardo en una clase contenedora ('RenderableShape') la' Forma 'asignada en el código de la aplicación, no podría manejar la 'Forma *' devuelta por las funciones de la biblioteca. – peoro

+0

¿No puedes simplemente envolver la 'Forma' devuelta? –

+0

Sí, ¿pero envolverlo en qué? No sé si es CircleShape o PolygonShape ... Necesitaría algún tipo de up-casting, pero ¿por qué molestarse con el wrapper? Yo siempre podría usarlo – peoro

0

Así que hay una clase xxxShape, que de alguna manera contiene información que "impulsa" la representación. Para Círculos que pueden ser centro, radio, para Cuadrados algunas coordenadas de esquina o algo así. Tal vez algunas otras cosas sobre rellenos y colores.

No desea/no puede actualizar esas clases para agregar la lógica de representación real, y creo que sus razones para no hacerlo son válidas/inevitables.

Pero, presumiblemente, tiene suficientes métodos de acceso público en las clases para que pueda obtener la información de "conducción", de lo contrario está condenado.

Así, en cuyo caso, ¿por qué se puede envolver no sólo estos elementos:

CircleRenderer hasA Cicle, knows how to render Circles 

y así sucesivamente. Ahora usa el patrón Visitor en las clases de Renderer.

+0

Algunas funciones dentro de mi biblioteca devuelven punteros/referencias a 'Shape'. Por ejemplo, el 'CompositeShape' del que hablé tiene un conjunto de' Shape * 'que puede ser de cualquier tipo. Entonces, incluso si guardo en una clase contenedora la 'Forma' asignada en el código de la aplicación, no podría manejar la 'Forma *' devuelta por las funciones de la biblioteca. – peoro

0
a muchas soluciones posibles

No, pero se puede hacer esto, por ejemplo: Comenzar una nueva jerarquía, lo que hace Shapes en una específica Context:

// contracts: 

class RenderingContext { 
public: virtual void DrawLine(const Point&, const Point&) = 0; 
    // and so on... 
}; 

class ShapeRenderer { 
public: virtual void Render(RenderingContext&) = 0; 
}; 

// implementations: 

class RectangleRenderer : public ShapeRenderer { 
Rectangle& mR; 

public: 
virtual void Render(RenderingContext& pContext) { 
    pContext.DrawLine(mR.GetLeftLower(), mR.GetRightLower()); 
    // and so on... 
} 

RectangleRenderer(Rectangle& pR) : mR(pR) {} 
}; 
+0

Algunas funciones dentro de mi biblioteca devuelven punteros/referencias a 'Shape'. Por ejemplo, el 'CompositeShape' del que hablé tiene un conjunto de' Shape * 'que puede ser de cualquier tipo. Entonces, incluso si guardo en una clase contenedora la 'Forma' asignada en el código de la aplicación, no podría manejar la 'Forma *' devuelta por las funciones de la biblioteca. – peoro

0

Entiendo absolutamente lo que ha dicho y comparto las mismas preocupaciones. El problema es que el patrón de visitante no está muy claramente definido y la solución original es engañosa, en mi humilde opinión. Es por eso que hay tantas variaciones de este patrón.

En particular, creo que la implementación correcta debería ser compatible con el código heredado, quiero decir: un binario ha perdido el código fuente en absoluto, ¿no? Esto es lo que dice la definición: nunca debería haber tenido que cambiar las estructuras de datos originales.

No me gustan las implementaciones con visitA, visitB, visitWhatever, acceptA, acceptB, acceptWhatever. Esto es absolutamente incorrecto, en mi humilde opinión.

Si tienes la oportunidad, echa un vistazo a an article I've written about this.

Es Java, pero puede portar a C++ fácilmente si lo encuentra útil para sus propósitos.

espero que ayude

Saludos

Cuestiones relacionadas