Проблема с использованием абстрактной фабрики - PullRequest
6 голосов
/ 02 августа 2010

Я использую абстрактную фабрику для создания компонентов пользовательского интерфейса, таких как диалоги.Используемая абстрактная фабрика возвращается из выбранного в настоящий момент универсального «INode», который является базовым классом для нескольких различных типов узлов.Так, например, если я хочу добавить новый узел того же типа, что и выбранный узел, сценарий выглядит примерно так:

(обратите внимание, это полупсевдокод)

Пользователь щелкает узел, и узел сохраняется для последующего использования:

void onTreeNodeSelected(INode *node)
{
    selectedNode = node;
}

Пользователь нажимает кнопку «добавить» в интерфейсе пользователя:

void onAddClicked()
{
    IFactory *factory = selectedNode->getFactory();
    Dialog *dialog = factory->createAddDialog(parentWidget);
    dialog->show();
}

Что все выглядит нормально.Проблема возникает, когда я хочу отредактировать выбранный узел:

void onEditClicked()
{
    IFactory *factory = selectedNode->getFactory();
    Dialog *dialog = factory->createEditDialog(selectedNode, parentWidget);
    dialog->show();
}

О, дорогой ... Я передаю объект INode.В какой-то момент мне придется понизить это значение до правильного типа узла, чтобы диалоговое окно могло использовать его правильно.

Я изучил исходный код "PostgreSQL Admin 3", и они делают что-то похожее наэтот.Они обошли его, сделав что-то вроде этого:

FooObjectFactoryClass::createDialog(IObject *object)
{
    FooObjectDialog *dialog = new FooObjectDialog((FooObject*)object);
}

Йек .. брось!

Единственный способ, которым я могу обдумать это и при этом использовать свои фабрики, - это ввести узелсам в фабрику, прежде чем он будет возвращен:

FooNode : INode
{
    FooNodeFactory* FooNode::getFactory()
    {
        fooNodeFactory->setFooNode(this);
        return fooNodeFactory;
    }
}

Итак, мое событие редактирования может сделать это:

void onEditClicked()
{
    IFactory *factory = selectedNode->getFactory();
    Dialog *dialog = factory->createEditDialog(parentWidget);
    dialog->show();
}

И он будет использовать вставленный узел для контекста.

Полагаю, что если нет внедренного кода, createEditDialog мог бы утверждать ложь или что-то в этом роде.

Есть мысли?

Спасибо!

Ответы [ 5 ]

3 голосов
/ 02 августа 2010

Распространенным решением является «двойная диспетчеризация», когда вы вызываете виртуальную функцию для одного объекта, которая, в свою очередь, вызывает виртуальную функцию для другого, передавая this, который теперь имеет правильный статический тип. Таким образом, в вашем случае фабрика может содержать функции «создания» для различных типов диалогов:

class IFactory
{
public:
    ....
    virtual Dialog* createEditDialog(ThisNode*, IWidget*);
    virtual Dialog* createEditDialog(ThatNode*, IWidget*);
    virtual Dialog* createEditDialog(TheOtherNode*, IWidget*);
    ....
};

тогда каждый тип узла имеет виртуальный createEditDialog, который отправляет правильную фабричную функцию:

class INode
{
public:
    ....
    virtual Dialog* createEditDialog(IWidget* parent) = 0;
    ....
};

class ThisNode : public INode
{
public:
    ....
    virtual Dialog* ThisNode::createEditDialog(IWidget* parent)
    {
        return getFactory()->createEditDialog(this, parent);
    }
    ....
};

Тогда вы можете создать правильный диалог как

void onEditClicked()
{
    Dialog *dialog = selectedNode->createEditDialog(parentWidget);
    dialog->show();
}
1 голос
/ 02 августа 2010

На мой взгляд, использование приведений в стиле C (хотя стиль C ++ предпочтительнее) вполне приемлемо, если ваш код правильно прокомментирован.

Я не большой поклонник DI (* 1003)* внедрение зависимостей ), потому что это затрудняет отслеживание некоторого кода, и в вашем случае я бы предпочел взглянуть на dynamic_cast<>() или что-то еще, чем пытаться следовать внедренному коду для нескольких исходных файлов.

0 голосов
/ 02 августа 2010

Я думаю, что приведение внутри createEditDialog в этом случае неплохо, даже если вы откажетесь от проверок времени компиляции. Если тип узла не изменяется во время выполнения, вы можете использовать шаблоны вместо абстрактного INode -класса.

В противном случае, я бы также подумал о вашем предложенном решении. Однако я бы переименовал метод в что-то вроде «getSelectedNodeDialogFactory» (я знаю, длинное имя), чтобы было ясно, что возвращаемая фабрика специфична для этого узла. Существуют ли другие диалоги, которые должны знать конкретный тип объекта INode? Возможно, createAddDialog нужен родительский или предшествующий узел? Все они могут быть в классе factory-with-selected-node.

0 голосов
/ 02 августа 2010

Ваш подход внедрения узла в фабрику - это, как правило, шаблон, который я нашел полезным, но часто бывают случаи, когда у вас нет ссылки на целевой объект при создании фабрики, как здесь , Так что в этом случае это может работать нормально, и это проще, чем решение подобных проблем в общем случае.

В более общем случае вам нужно работать с понятием интерфейсов и установить механизм, с помощью которого ваш объект INode может публиковать, какие интерфейсы он поддерживает, и предоставлять доступ к этим интерфейсам для клиентов. Выполнение этого полностью динамически приводит к COM-подобному подходу, который требует динамической регистрации и приведения. Но вы также можете сделать это статически типизированным способом, если у вас есть относительно стабильный набор интерфейсов, которые вы хотите показать, и можете позволить редактировать интерфейс INode, когда вам нужно добавить новый интерфейс компонента.

Итак, это пример того, как сделать простой статически типизированный подход:

struct INode
{
     virtual INodeSize* getNodeSizeInterface() = 0;

     virtual INodeProperties* getNodePropertiesInterface() = 0;

     virtual INodeColor* getNodeColorInterface() = 0;

     ... // etc
}

Теперь каждая INode реализация может возвращать некоторые или все эти интерфейсы компонентов (она просто возвращает NULL, если не реализует их). Затем ваши диалоги оперируют интерфейсами компонентов, чтобы выполнять свою работу, вместо того, чтобы пытаться выяснить, какая фактическая реализация INode была передана. Это сделает намного более гибкое отображение между диалогами и реализациями узлов. Диалог может быстро определить, есть ли у него «совместимый» объект INode, проверив, что он возвращает действительный объект для каждого интерфейса, в котором заинтересован диалог.

0 голосов
/ 02 августа 2010

Я бы предложил две вещи.

Во-первых: в кастинге нет ничего плохого. Если вы хотите быть в безопасности, вы можете использовать RTTI (type_id stuff) или некоторые виртуальные функции в классе INode, которые могут возвращать некоторую информацию, которая сообщит вам, безопасно ли приведение.

Второе: вы можете проверить, что нужно для функции createEditDialog, и поместить их в виртуальные функции либо в INode, либо в унаследованный класс, который будет соответствовать ожидаемому типу createDialog.

В общем, я не вижу ничего действительно плохого в проблеме, которую вы описываете, и трудно дать больше предложений, не видя весь код, что, я полагаю, неосуществимо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...