Padrão de design para comportamento polimórfico, permitindo a separação da biblioteca

10

Digamos que eu tenho uma hierarquia de Itemclasses: Rectangle, Circle, Triangle. Quero poder desenhá-los, então minha primeira possibilidade é adicionar um Draw()método virtual a cada um:

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

No entanto, desejo dividir a funcionalidade de desenho em uma biblioteca Draw separada, enquanto a biblioteca Core contém apenas as representações básicas. Há algumas possibilidades em que posso pensar:

1 - A DrawManagerque pega uma lista de se Itemprecisa usar dynamic_cast<>para descobrir o que fazer:

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

Isso não é ideal, pois depende do RTTI e força uma classe a estar ciente de todos os itens na hierarquia.

2 - A outra abordagem é adiar a atribuição de responsabilidades a uma ItemDrawerhierarquia ( RectangleDrawer, etc):

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

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

Isso alcança a separação de preocupações entre a representação básica dos itens e o código para o desenho. O problema, porém, é que as classes de itens dependem das classes de desenho.

Como posso separar esse código de desenho em uma biblioteca separada? A solução para os Itens retornar uma classe de fábrica com alguma descrição? No entanto, como isso pode ser definido para que a biblioteca Core não dependa da biblioteca Draw?

the_mandrill
fonte

Respostas:

3

Dê uma olhada no padrão de visitantes .

Sua intenção é separar um algoritmo (no desenho do seu caso) de uma estrutura de objeto na qual ele opera (itens).

Portanto, um exemplo simples para o seu problema seria:

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;
}
batata quente
fonte
Interessante - nunca pensei em usar um visitante com sobrecarga para obter polimorfismo. Isso sobrecarregaria corretamente em tempo de execução?
#
Sim, isso sobrecarregaria corretamente. Veja resposta editada
hotpotato
Eu posso ver que isso funciona. É uma pena que cada classe derivada tenha que implementar o visit()método.
the_mandrill
Isso me levou a pesquisar um pouco mais e agora encontrei a implementação do padrão Visitor de Loki, que parece ser exatamente o que estou procurando! Eu estava familiarizado com o padrão de visitantes em termos de visitar nós nas árvores, mas não tinha pensado nisso de uma maneira mais abstrata.
the_mandrill
2

A divisão que você descreve - em duas hierarquias paralelas - é essencialmente o padrão da ponte .

Você se preocupa que isso torne a abstração refinada (Rectangle etc.) dependente da implementação (RectangleDrawer), mas não tenho certeza de como você poderia evitar isso completamente.

Você certamente pode fornecer uma fábrica abstrata para quebrar essa dependência, mas ainda precisa de alguma maneira de dizer à fábrica qual subclasse criar e essa subclasse ainda depende da implementação refinada específica. Ou seja, mesmo que o Rectangle não dependa do RectangleDrawer, ele ainda precisará de uma maneira de instruir a fábrica a criar um, e o próprio RectangleDrawer ainda depende do Rectangle.

Também vale a pena perguntar se isso é uma abstração útil. Desenhar um retângulo é tão difícil que vale a pena colocar em uma classe diferente ou você está realmente tentando abstrair a biblioteca de gráficos?

Sem utilidade
fonte
Eu pensei que já tinha visto esse padrão antes de ter hierarquias paralelas - obrigado por me concentrar nisso. No entanto, estou querendo abstrair a biblioteca de gráficos. Não quero que a biblioteca principal dependa da biblioteca de gráficos. Digamos que o aplicativo principal gerencia objetos de forma e a biblioteca para exibi-los é um complemento. Lembre-se, porém, de que isso realmente não é um aplicativo para desenhar retângulos - estou apenas usando-o como uma metáfora.
the_mandrill
O que eu estou pensando é que RectangleDrawer, CircleDraweretc. pode não ser a forma mais útil para abstrair a biblioteca de gráficos. Se, em vez disso, você expor um conjunto de primitivas por meio de uma interface abstrata regular, você ainda interrompe a dependência.
inútil das
1

Veja o valor da semântica e do polimorfismo baseado em conceitos .

Este trabalho mudou a maneira como encaro o polimorfismo. Usamos esse sistema com grande efeito em situações repetidas.

Bill Door
fonte
Isso parece fascinante - parece que está indo na direção que eu estou querendo
the_mandrill
11
O link está quebrado. É por isso que as respostas que são essencialmente apenas de link são desencorajadas.
ajb
0

A razão pela qual você está tendo um problema é porque os objetos não devem se desenhar. Desenhar algo corretamente depende de um bilhão de peças de estado e um retângulo não terá nenhuma dessas peças. Você deve ter um objeto gráfico central que represente o estado central como backbuffer / depth buffer / shaders / etc e, em seguida, forneça um método Draw ().

DeadMG
fonte
Concordo que os objetos não devem se desenhar, daí o desejo de mudar essa habilidade para uma classe diferente, a fim de obter uma separação de preocupações. Suponha que essas classes sejam Dogou Cat- o objeto responsável por desenhá-las é muito mais complexo do que desenhar um retângulo.
the_mandrill