Построение правильной архитектуры

Наткнулся на затык при проектировании архитектуры. Имею абстрактный класс Model и двух наследников - Model1 и Model2. Model1 обладает членом типа int(a), а Model2 - const char(b)*. Также имеется абстрактный класс Render и два наследника - Render1 и Render2. Render предоставляет чистую виртуальную функцию - член draw, которая принимает тип *Model**. Теперь же, в классе Render1 в методе draw я выполняю некоторые действия, и мне нужен доступ к члену a, класса Model1. В свою очередь тоже самое требуется в классе Render2 доступ к члену b класса Model2. По задумке, в класс Render2 не может быть передан класс Model1 и наоборот. Как поступить в такой ситуации? Базовый код, использующий полиморфизм, приведён ниже:

class Model
{
public:
    virtual void init() = 0;
};

class Model1 : public Model
{
    int a;
public:
    void init() override
    {
        a = 48;
    }
};

class Model2 : public Model
{
    const char* b;
public:
    void init() override
    {
        b = "test";
    }
};

class Render
{
public:
    virtual void draw(Model* m) = 0;
};

class Render1: public Render
{
public:
    void draw(Model* m) override
    {
        // здесь сделать что - то с a
    }
};

class Render2 : public Render
{
public:
    void draw(Model* m) override
    {
        // здесь сделать что - то с b
    }
};

P.S.: я понимаю, что Render'ы не должны знать, кто в них приходит(какой экземпляр класса), но ничего лучшего никак не могу придумать. Посоветуйте, как в таком случае стоит поступить. Полиморфизм и наследование использовал потому что классы слишком общие. P.S.S.: про dynamic_cast знаю, но это тоже рушит архитектуру, я пытаюсь избежать этого

UPD1: При использовании визитора я бы хотел разделить возможности наследников, так как в случае использования одного визитора подключаются много библиотек. А хотелось бы в определенном хейдере определенную библиотеку. Иными словами, можно ли сделать вместо этого:

class Foo;
class Bar;

class Visitor 
{
public:
  virtual void visit(Foo &ref) = 0;
  virtual void visit(Bar &ref) = 0;

  virtual ~Visitor() = default;
};

class GetType : public Visitor 
{
public:
  std::string value;

public:
  void visit(Foo &ref) override 
  {
    value = "Foo";
  }
  void visit(Bar &ref) override 
  {
    value = "Bar";
  }

};

Вот так:

class Foo;
class Bar;

class Visitor
{
public:
    virtual void visit(Foo& ref) = 0;
    virtual void visit(Bar& ref) = 0;

    virtual ~Visitor() = default;
};

class GetTypeFoo : public Visitor
{
public:
    std::string value;

public:
    void visit(Foo& ref) override
    {
        value = "Foo";
    }
    void visit(Bar& ref) override
    {
        // empty
    }

};

class GetTypeBar : public Visitor
{
public:
    std::string value;

public:
    void visit(Foo& ref) override
    {
        // empty
    }
    void visit(Bar& ref) override
    {
        value = "Bar";
    }

};

Ну и вызываться, соответственно, будет нужный метод в нужный момент.


Ответы (1 шт):

Автор решения: Andrej Levkovitch

В вашем случае отлично подойдет такой паттерн программирования как visitor. Тобишь создаете виртуальную функцию draw в базовом классе Model в которую передаете объект Render. Так вы избавитесь от проблемы определения типов, так как объект априори знает о том, какой у него тип.

В итоге у вас будет два базовых типа: один для visitor-ов, второй для объектов, принимающих визиторы. Количество производных (от базового класса) визиторов не ограничено, но следует помнить, что в случае добавления нового класса принимающего визитор вы должны обеспечить поддержку нового класса всеми визиторами! Это создает некоторую сложность, но если часть (или все крому одного) визиторов не должна что-либо делать с объектом нового типа, то для упрощение имплементации можно определить в базовом классе visitor все функции не как абстрактные, а с пустой реализацией.

→ Ссылка