Страницы

Поиск по вопросам

суббота, 11 января 2020 г.

Запретить создание экземпляра класса. Создание фабрики C++

#cpp #абстрактная_фабрика


Пишу для себя гуишку. Есть главный class GUI который занимается управлением всеми
элементами (class Widget), создание этих элементов должно происходить только через
этот GUI, для регистрации.

Думал что-то вроде такого:

1) Абстрактный класс виджетов:

class Widget {
public:
    /* ... */
    virtual ~Widget();

protected:
    friend class GUI;

    Widget();

    /* ... */
private:

};


2) Класс GUI:

class GUI {
public:
    /* ... */

    template
    WidgetType* create(AbstractContainer* parent = nullptr) {
        static_assert(std::is_base_of::value, "Error...");

        std::shared_ptr widget = std::make_shared();
        widget->m_id = m_currentWidgetID;
        m_currentWidgetID++;

        WidgetType* result = reinterpret_cast(widget.get());

        if (parent != nullptr) {
            m_centralWidget->addWidget(widget);
        }
        else {
            parent->addWidget(widget);
        }

        return result;
    }

private:
    std::shared_ptr m_centralWidget;

    /* ... */
};


Сама проблема в спецификации friend class GUI, которая не распространяется на классы
наследники, из чего следует, что мне надо писать для каждого наследника friend class
GUI. Это необходимо, т.к. конструкторы спрятаны в protected, чтобы можно было запретить
создание экземпляра класса виджета напрямую Widget(), сделав это только через фабрику
GUI::create().

Без friend class GUI я не знаю как дать доступ к protected зоне класса элемента,
где лежит конструктор.

Сам вопрос:
Как выйти из ситуации и сделать правильно эту фабрику?
    


Ответы

Ответ 1



Тут классический случай. Ваш класс GUI реализован так, что знает об имплементации класс Widget, если вы хотите чтоб наследники тоже могли получить прямой доступ до Widget, то надо выделить приватныу часть интерфейса и публичную. (Это очень странное решение, разрешать всем наследникам лезть в имплементацию Widget, обычно таких классов должно быть ограниченное количество и все они тесно связаны с имплементаций Widget) // WidgetPrivate.h namespace details { class WidgetPrivate { public: virtual void privateMethod1() = 0; virtual int& accessToPrivateInt() = 0; ... }; } // Widget.h class Widget : protected details::WidgetPrivate { friend class GUI; public: void somePublicMethod(); int getInt(); //... protected: WidgetPrivate& privateInterface(); // <- никто, кроме GUI и наследников не дотянется до этого метода void privateMethod1() override; int& accessToPrivateInt() override; }; // Widget.cpp WidgetPrivate& Widget::privateInterface() { return *this; } Теперь, те кто должен иметь доступ к потрохам Widget используют метод privateInterface, такие включения и точки использования очень легко искать по grep privateInterface * class GUI { protected: static WidgetPrivate& widgetPrivateInterface(Widget& w) { return w->privateInterface(); } } class GUIDrived : public GUI { void x() { widgetPrivateInterface(*widget).accessToPrivateInt() = 5; } private: Widget * widget; } То все наследники будут иметь доступ до приватного интерфейса.

Ответ 2



У меня была примерно такая же проблема года 2 назад. Единственное (но не самое лучшее) решение, которое я нашел, заключалось в том, что бы в конструктор, в нашем случае, класса Widget, передавать параметр-пустышку какого нибудь другого класса, у которого в друзьях будет класс GUI. Таким образом, что бы создать экземпляр класса Widget, нужно будет создать экземпляр класса-пустышки. А это можно будет сделать только из фабрики. Вот примерная реализация: class GUI; class Stub { Stub() {} friend GUI; }; class Widget { public: /* ... */ virtual ~Widget(); protected: friend class GUI; Widget(Stub st); /* ... */ private: }; class SomeRealWidget: public Widget { public: SomeRealWidget(Stub st): Widget(st) { /* ... */ } }; class GUI { public: /* ... */ template WidgetType* create(AbstractContainer* parent = nullptr) { static_assert(std::is_base_of::value, "Error..."); Stub st; std::shared_ptr widget = std::make_shared(st); widget->m_id = m_currentWidgetID; m_currentWidgetID++; WidgetType* result = reinterpret_cast(widget.get()); if (parent != nullptr) { m_centralWidget->addWidget(widget); } else { parent->addWidget(widget); } return result; } private: std::shared_ptr m_centralWidget; /* ... */ }; К сожалению, другого варианта я не нашел. Будет здорово, если в класса Stub у вас получится впихнуть что-нибудь общее для всех виджетов, что нужно будет для их инициализации, тогда такой вариант будет почти оправдан.

Комментариев нет:

Отправить комментарий