2011-09-07 3 views
6

Estoy trabajando en una aplicación basada en diapositivas en C++. Cada diapositiva tiene una colección de diapositivas-artículos que se pueden incluir elementos como subtítulo, botón, rectángulo, etc.¿Cómo debo diseñar un conjunto de clases relacionadas donde solo algunas de ellas admiten una determinada operación?

Sólo algunos de estos artículos de apoyo relleno, mientras que otros no lo hacen .

¿Cuál es la mejor manera de implementar el relleno para los elementos de la diapositiva en este caso? Aquí hay dos maneras de que pensé:

  1. crear una interfaz Fillable e implementar esta interfaz para los elementos deslizantes que soportan llenar, manteniendo todas las propiedades relacionadas con rellenar el interfaz. Al iterar sobre la lista de elementos de la diapositiva, dígale que los cambie a en Fillable, y si tiene éxito, realice la operación relacionada con el llenado.

  2. Haz una clase fill. Convierta un puntero fill en una clase de elemento deslizante, asigne el objeto fill al puntero fill para aquellos objetos que admitan relleno y, para el resto, manténgalo nulo. Asigne una función GetFill, que devolverá el fill para los artículos si existe; de ​​lo contrario, devuelve NULL.

¿Cuál es el mejor enfoque para esto? Estoy interesado en el rendimiento y la mantenibilidad.

Respuesta

3

Haría una combinación de ambos. Haga su interfaz Fillable y haga que sea el tipo de devolución para su método GetFill. Esto es mejor que el enfoque de lanzamiento dinámico. El uso del lanzamiento dinámico para realizar consultas para la interfaz requiere que el objeto del elemento de la diapositiva real implemente la interfaz si es para admitirlo. Sin embargo, con un método de acceso como GetFill, tiene la opción de proporcionar una referencia/puntero a algún otro objeto que implemente la interfaz.También puede devolver this si este objeto implementa la interfaz. Esta flexibilidad puede ayudar a evitar la saturación de clases y promover la creación de objetos componentes reutilizables que puedan ser compartidos por múltiples clases.

Editar: Este enfoque también funciona muy bien con el patrón de objeto nulo. En lugar de devolver un puntero nulo para los objetos que no admiten Fillable, puede devolver un objeto simple no operativo que implementa la interfaz. Entonces no tiene que preocuparse siempre de buscar punteros nulos en el código del cliente.

0

Crear una SlideItem clase base:

class SlideItem { 
    public: 
     virtual ~SlideItem(); 
     virtual void fill() = 0; 
}; 

Después, realice una implementación vacía para aquellos que no se puede llenar:

class Button : public SlideItem { 
    public: 
     void fill() { } 
}; 

Y una implementación adecuada de relleno para los demás:

class Rectangle : public SlideItem { 
    public: 
     void fill() { /* ... fill stuff ... */ } 
}; 

y poner todos ellos dentro de un recipiente .. si quiere llenarlos Sólo tiene que llamar a todo el mundo ... fácil de m aintain .. y a quién le importa el rendimiento :)


Si realmente necesita un código rápido, su primera solución es ciertamente buena. Pero si lo haces así, asegúrate de no tener que lanzarlo cada vez que quieras llenarlo. Equípelos una vez y coloque los punteros en un recipiente rellenable. Luego itere sobre este contenedor rellenable si tiene que llenar.

Por otra parte, en mi humilde opinión, pone mucho esfuerzo en esto, sin una ganancia de rendimiento razonable. (por supuesto que no conozco su solicitud, podría estar justificado ... pero por lo general no)

+2

Esto se conoce como una interfaz gruesa, y es un antipatrón. –

+0

@Armen Tsirunyan: Ok, bien. Si hay un antipatrón debe haber el patrón apropiado para esto también. Sin duda me lo puedes explicar :) Ah, y si tu solución contiene el doble de clases (adaptadores o lo que sea) no me interesa ... – duedl0r

+0

@ duedl0r: me temo que he leído mal el párrafo entero, lo siento. –

0

Parece que lo que estás buscando está cerca del Capability Pattern. Tu # 2 está cerca de este patrón. Esto es lo que haría:

Haga una clase de relleno. Haga que el puntero de relleno sea parte de la clase de elemento deslizante, asigne el objeto de relleno para llenar el puntero solo para aquellos objetos que admitan relleno, para el resto de ellos, manténgalo nulo. Cree una función GetCapability (Capability.Fill), que devolverá el relleno para los elementos si existe; de ​​lo contrario, devuelve NULL. Si algunos de sus objetos ya implementan una interfaz Rellenable, puede devolver el lanzamiento de objeto a un puntero Rellenable.

+0

Lamentablemente, una vez más, tiene una clase base de grasa ... Esto es bastante desafortunado. –

0

Considere almacenar Variante elementos, como boost::variant.

Puede definir un boost::variant<Fillable*,Item*> (debe usar punteros inteligentes si tiene la propiedad), y luego tener una lista de esas variantes para iterar.

+0

Su clase de grasa se convierte en la variante ... No me gusta este enfoque, especialmente si se espera que crezca la cantidad de interfaces de relleno. Lo que particularmente me desagrada es que usualmente, tienes 'Fillable' heredando' Item'. Tendrás que defender este mejor :) –

+0

@Alexandre C: La clase en sí misma nunca fue manipulada, y por lo tanto, los clientes que no requieren la propiedad 'Fillable' nunca ven el método' fill'. La clase "gorda" es por lo tanto local para el uso de la interfaz "gorda", ¡y todo está bien! (y usted también es tan rápido como la clase "gorda"). Además, el mecanismo puede crecer localmente sin requerir que 'Item 'implemente una interfaz de visitante completa. –

0

La respuesta es depende.

No veo el punto de tener que saturar su interfaz base con fill/get_fillable_instance/... si no se supone que todos los objetos manejan el relleno. No obstante, usted puede conseguir lejos con sólo

struct slide_object 
{ 
    virtual void fill() {} // default is to do nothing 
}; 

pero depende de si usted piensa fill debería aparecer en la clase abstracta objeto de diapositiva. Sin embargo, raramente debería, a menos que no se pueda llenar es excepcional.

La fundición dinámica puede ser correcta en el caso de que necesite proporcionar dos clases distintas de objetos (y no más de dos), algunos de ellos pueden llenarse y el otro no tiene nada que ver con la capacidad de llenado. En este caso, tiene sentido tener dos sub-jerarquías y usar fundición dinámica donde lo necesite.

He utilizado este enfoque con éxito en algunos casos y es simple y fácil de mantener, siempre que la lógica de envío no esté dispersa (es decir, hay solo uno o dos lugares donde se realiza el reparto dinámico).

Si se espera que tenga un comportamiento más relleno como, a continuación, dynamic_cast es una mala elección, ya que dará lugar a

if (auto* p = dynamic_cast<fillable*>(x)) 
    ... 
else if (auto* p = dynamic_cast<quxable*>(x)) 
    ... 

que es malo. Si va a necesitar esto, implemente un patrón de Visitante.

0

Sugiero usar una interfaz para las formas, con un método que devuelve un relleno. Por ejemplo:

class IFiller { 
public: 
    virtual void Fill() = 0; 

protected: 
    IFiller() {} 
    virtual ~IFiller() {} 
}; 

class IShape { 
public: 
    virtual IFiller* GetFiller() = 0; 

protected: 
    IShape() {} 
    virtual ~IShape() {} 
}; 

class NullFiller : public IFiller { 
public: 
    void Fill() { /* Do nothing */ } 
}; 

class Text : public IShape { 
public: 
    IFiller* GetFiller() { return new NullFiller(); } 
}; 

class Rectangle; 
class RectangleFiller : public IFiller { 
public: 
    RectangleFiller(Rectangle* rectangle) { _rectangle = rectangle; } 
    ~RectangleFiller() {} 

    void Fill() { /* Fill rectangle space */ } 

private: 
    Rectangle* _rectangle; 
}; 

class Rectangle : IShape { 
public: 
    IFiller* GetFiller() { return new RectangleFiller(this); } 
}; 

encuentro este método más fácil de mantener y extender, si bien no introduce grandes problemas de rendimiento.

Cuestiones relacionadas