Вопрос об абстрактных фабриках и инъекциях - PullRequest
6 голосов
/ 03 августа 2010

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

В основном я пишу пользовательский интерфейс, и в моем пользовательском интерфейсе есть узлы, которые можно выбрать. Когда узел выбран, пользовательский интерфейс заканчивается базовым классом абстрактного узла «INode». Из этого я получаю фабрику, выполняя node-> getFactory (), и из этого я могу создать соответствующие диалоги или представления для этого узла, потому что правильная фабрика возвращается конкретным узлом (например, factory-> createAddDialog (), factory- > createView (узел) и т. д.).

Мой вопрос касается попыток найти наилучший способ для этой фабрики попасть в узел.

До сих пор я думал о 3 путях:

1) Введите правильную фабрику при создании узла:

AreaNode *node = new AreaNode(new AreaNodeFactory());

Итак, определение AreaNode:

AreaNode : public INode
{
    AreaNode(INodeAbstractFactory *injectedFactory)
    {
        m_injectedFactory = injectedFactory;
    }

    INodeAbstractFactory* getFactory()
    {
        return m_injectedFactory;
    }

    INodeAbstractFactory* m_injectedFactory;
};

2) Введите более общую фабрику и дайте узлу получить фабрику от этой фабрики:

AreaNode : public INode
{
    AreaNode(IFactory *injectedFactory)
    {
        m_injectedFactory = injectedFactory;
    }

    INodeAbstractFactory* getFactory()
    {
        return m_injectedFactory->getAreaNodeFactory();
    }

    IFactory* m_injectedFactory;
}

3) Просто создайте конкретную фабрику (хотя это устраняет возможности использования разных фабрик для одного и того же узла, возможно, для тестирования или для последующих изменений):

AreaNode : public INode
{
    INodeAbstractFactory* getFactory()
    {
        return new AreaNodeFactory();
    }
}

Текущие мысли по поводу этих опций:

Вариант 1. Может быть немного случайным - мне нужно убедиться, что я всегда даю правильную фабрику для этого типа, или, возможно, я мог бы просто использовать другую фабрику, чтобы ввести правильную фабрику для меня.

Вариант 2: заставляет узел достаточно знать о реализации абстрактной фабрики, чтобы иметь возможность вызывать getAreaNodeFactory, что может быть не так уж и плохо. По крайней мере, это помогает гарантировать, что всегда будет выбрана правильная / та же самая фабрика (при условии, что более общая фабрика реализована правильно).

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

Есть мысли по этому поводу?

Спасибо.

EDIT: Извините, пропущены объявления переменных в исходной записи, исправлено.

EDIT: Другая проблема, связанная с вариантом 2, заключается в том, что мне нужно реализовать getFactory в каждом типе узла. По крайней мере, с опцией 1 базовый класс может просто каждый раз возвращать абстрактный фабричный класс внедрения.

Ответы [ 3 ]

2 голосов
/ 03 августа 2010

Как насчет

class FactoryBase { /* interface */ }
template <typename NodeType> class Factory : public FactoryBase {
  // Default implementation.
}
// Add appropriate specializations for all relevant nodetypes, if needed.
template <typename NodeType> inline Factory<NodeType> getFactory(NodeType*) {
  return Factory<NodeType>( );
}

class AreaNode : public Node {
  FactoryBase getFactory() { return getFactory(this); }
};

Вывод аргумента шаблона будет гарантировать, что тип фабрики получен из this.Это должно предотвратить ручные ошибки там.Но в целом, я бы вообще не стал публично разоблачать фабрику.Просто добавьте следующие биты в базовый класс:

class Node {
public:
  std::Auto_ptr<View> createView( ) {
    this->getFactory()->createView(this);
  } // etc.
private:
   virtual FactoryBase* getFactory() = 0;
};
2 голосов
/ 03 августа 2010

Как насчет.

template<typename Factory> 
class AreaNode : public INode
{
public:
      virtual ~AreaNode(){}

      AreaNode() : pFactory_(new Factory())
      {         
      }

      const shared_ptr<IFactory>& GetFactory()
      {
          return pFactory_;
      } 
private:          
      shared_ptr<IFactory> pFactory_;
};

EDIT:

Или в зависимости от вашего контекста.

template<typename Factory> 
class Node
{
public:
      virtual ~Node(){}

      Node() : pFactory_(new Factory())
      {         
      }

      const shared_ptr<IFactory>& GetFactory()
      {
          return pFactory_;
      } 
private:          
      shared_ptr<IFactory> pFactory_;
};

class AreaNode : public Node<AreaNodeFactory>
{
     // Implementation
};

// OR

typedef Node<AreaNodeFactory> AreaNode;
1 голос
/ 04 августа 2010

Зависит от назначения «узловых» объектов

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

class INode //well, it's not "interface" in clear sense
{
public:
     void setFactory(Factory* f) { this->f = f; }
     Factory* getFactory() const { return f; }
private:
     Factory* f;
};

Теперь функциональность бетонных узлов и бетонных заводов сделана ортогональной друг другу и может быть свободно объединена любым способом.

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

Также рассмотрим это (не очень ООП):

typedef boost::function0<Factory*> Node;
std::map<UIItem*,Node> nodes;
nodes[area_item1] = &getAreaFactory;
nodes[area_item2] = &getAreaFactory;
nodes[region_item1] = &getRegionFactory;
...
void OnSelect(UIItem* i)
{
    ... 
    View* v = nodes[i]()->createView();
    ...
}

Возможно, это подходит всем вашим потребностям)

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