Patrón de diseño para comportamiento polimórfico al tiempo que permite la separación de la biblioteca


10

Digamos que tengo una jerarquía de Itemclases: Rectangle, Circle, Triangle. Quiero poder dibujarlos, así que mi primera posibilidad es agregar un Draw()método virtual a cada uno:

class Item {
public:
   virtual ~Item();
   virtual void Draw() =0; 
};

Sin embargo, quiero dividir la funcionalidad de dibujo en una biblioteca Draw separada, mientras que la biblioteca Core contiene solo las representaciones básicas. Hay un par de posibilidades en las que puedo pensar:

1 - A DrawManagerque toma una lista de Itemsy tiene que usar dynamic_cast<>para determinar qué hacer:

class DrawManager {
  void draw(ItemList& items) {
    FOREACH(Item* item, items) {
       if (dynamic_cast<Rectangle*>(item)) {
          drawRectangle();
       } else if (dynamic_cast<Circle*>(item)) {
          drawCircle();
       } ....
    }
  }
};

Esto no es ideal, ya que se basa en RTTI y obliga a una clase a conocer todos los elementos de la jerarquía.

2 - El otro enfoque es diferir la responsabilidad del dibujo a una ItemDrawerjerarquía ( RectangleDrawer, etc.):

class Item {
   virtual Drawer* GetDrawer() =0;
}

class Rectangle : public Item {
public:
   virtual Drawer* GetDrawer() {return new RectangleDrawer(this); }
}

Esto logra la separación de preocupaciones entre la representación base de los Artículos y el código para dibujar. Sin embargo, el problema es que las clases de ítems dependen de las clases de dibujo.

¿Cómo puedo separar este código de dibujo en una biblioteca separada? ¿Es la solución para que los artículos devuelvan una clase de fábrica de alguna descripción? Sin embargo, ¿cómo se puede definir esto para que la biblioteca Core no dependa de la biblioteca Draw?

Respuestas:


3

Echa un vistazo al patrón de visitante .

Su intención es separar un algoritmo (en su dibujo de caso) de una estructura de objeto en la que opera (elementos).

Entonces, un ejemplo simple para su problema sería:

class Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};

class Rectangle : public Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};

class Circle : public Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};


class Visitable
{
public:
    virtual void accept(Rectangle& r);
    virtual void accept(Circle& c);
};

class Drawer : public Visitable
{
public:
    void accept(Rectangle& r)
    {
        // draw rectangle
    }   
    void accept(Circle& c)
    {
        // draw circle
    }
};


int main()
{
    Item* i1 = new Circle;
    Item* i2 = new Rectangle;

    Drawer d;
    i1->visit(d); // Draw circle
    i2->visit(d); // Draw rectangle

    return 1;
}

Interesante: nunca había pensado en usar un visitante con sobrecarga para lograr el polimorfismo. Sin embargo, ¿se sobrecargaría correctamente en tiempo de ejecución?
the_mandrill

Sí, se sobrecargaría correctamente. Ver respuesta editada
hotpotato

Puedo ver que esto funciona. Sin embargo, es una pena que cada clase derivada tenga que implementar el visit()método.
the_mandrill 01 de

¡Esto me llevó a buscar un poco más y ahora he encontrado la implementación del patrón de visitante de Loki que parece ser exactamente lo que estoy buscando! Estaba familiarizado con el patrón de visitante en términos de nodos visitantes en los árboles, pero no había pensado en él de una manera más abstracta.
the_mandrill

2

La división que describe, en dos jerarquías paralelas, es esencialmente el patrón del puente .

Le preocupa que haga que la abstracción refinada (Rectángulo, etc.) dependa de la implementación (RectangleDrawer), pero no estoy seguro de cómo podría evitarlo por completo.

Ciertamente, puede proporcionar una fábrica abstracta para romper esa dependencia, pero aún necesita alguna forma de decirle a la fábrica qué subclase crear, y esa subclase aún depende de la implementación refinada específica. Es decir, incluso si Rectangle no depende de RectangleDrawer, todavía necesita una forma de decirle a la fábrica que construya uno, y RectangleDrawer sí mismo depende de Rectangle.

También vale la pena preguntar si esta es una abstracción útil. ¿Dibujar un rectángulo es tan difícil que vale la pena ponerlo en una clase diferente, o realmente estás tratando de abstraer la biblioteca de gráficos?


Pensé que había visto ese patrón antes de tener las jerarquías paralelas, gracias por pensar en eso. Sin embargo, quiero abstraer la biblioteca de gráficos. No quiero que la biblioteca principal tenga que depender de la biblioteca de gráficos. Digamos que la aplicación principal administra objetos de forma y la biblioteca para mostrarlos es un complemento. Sin embargo, tenga en cuenta que esto realmente no es una aplicación para dibujar rectángulos, solo lo estoy usando como una metáfora.
the_mandrill 01 de

Lo que estoy pensando es que RectangleDrawer, CircleDraweretc. , puede no ser la forma más útil de abstraer la biblioteca de gráficos. Si, en cambio, expone un conjunto de primitivas a través de una interfaz abstracta regular, aún así rompe la dependencia.
Inútil


0

La razón por la que tiene un problema es porque los objetos no deben dibujarse solos. Dibujar algo correctamente depende de mil millones de piezas de estado y un Rectángulo no tendrá ninguna de esas piezas. Debe tener un objeto gráfico central que represente el estado del núcleo, como backbuffer / profundidad buffer / shaders / etc, y luego darle un método Draw ().


Estoy de acuerdo en que los objetos no deben dibujarse a sí mismos, de ahí el deseo de mover esa habilidad a una clase diferente para lograr una separación de preocupaciones. Supongamos que estas clases eran Dogo Cat: el objeto responsable de dibujarlas es mucho más complejo que dibujar un rectángulo.
the_mandrill
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.