Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Question about abstract factories and injection

This is similar to one of my other questions, but different enough I think to warrant a new question.

Basically I am writing a user interface, and my user interface has nodes that can be selected. When a node is selected, the user interface ends up with an abstract node base class "INode". From this I get a factory by doing node->getFactory(), and from this I can create the appropiate dialogs or views for that node because the correct factory gets returned by the concrete node (e.g. factory->createAddDialog(), factory->createView(node), etc.).

My question is about trying to find the best way for that factory to get into the node in the first place.

So far I've thought of 3 ways:

1) Inject the correct factory when I create the node:

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

So the definition of AreaNode is:

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

    INodeAbstractFactory* getFactory()
    {
        return m_injectedFactory;
    }

    INodeAbstractFactory* m_injectedFactory;
};

2) Inject a more general factory and allow the node to get the factory from that factory:

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

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

    IFactory* m_injectedFactory;
}

3) Just create the concrete factory (though this removes the scope for using different factories for the same node perhaps for testing or for later changes):

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

Current thoughts on these options:

Option 1: Could be a little haphazard - I'd have to make sure I always give it the correct factory for that type, or maybe I could just use another factory to inject the correct factory for me.

Option 2: Forces the node to know about the abstract factory implementation enough to be able to call getAreaNodeFactory, which may not be such a bad thing. It at least helps ensure the correct/same factory will always be fetched (assuming the more general factory is implemented properly).

Option 3: This feels little restrictive as I won't be able to swap the class out, and I'm not keen on the node having to know about the concrete implementation of the factory - though in this case it might not be too much of an issue (famous last words!).

Any thoughts on this?

Thanks.

EDIT: Sorry missed out the variable declarations in the origin post, corrected.

EDIT: Another problem with option 2 is I have to implement "getFactory" in every node type. At least with option 1 the base class can just return the inject abstract factory class every time..

like image 477
Mark Avatar asked Aug 03 '10 13:08

Mark


2 Answers

How about

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); }
};

Template argument deduction will ensure that the factory type is derived from this. This should prevent manual mistakes there. But in general, I wouldn't bother with publicly exposing a factory at all. Just add the following bits to the base class:

class Node {
public:
  std::Auto_ptr<View> createView( ) {
    this->getFactory()->createView(this);
  } // etc.
private:
   virtual FactoryBase* getFactory() = 0;
};
like image 144
MSalters Avatar answered Oct 31 '22 00:10

MSalters


How about.

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:

Or depending on your context.

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;
like image 34
ronag Avatar answered Oct 30 '22 23:10

ronag