jeudi 7 septembre 2017

How to reduce library coupling with the factory pattern?

A low level (core) library, Library A, has the following base class:

class Shape
{
public:
    virtual void draw() const = 0;
};

A high level (application) library, Library B, contains the derived classes, for example:

class Circle : public Shape
{
public:
    void draw() const
    {
        std::cout << "Circle" << std::endl;
    }
};

A mid level (GUI) library, Library C, needs to instantiate derived classes, for example:

std::shared_ptr<Shape> ShapeEditor::create()
{
    if (someCondition)
    {
        return std::make_shared<Circle>();
    }
}

However, this is not possible due to the relationships between libraries. Library C is aware of (links with) Library A, but not Library B and therefore cannot instantiate derived class types.

To work around this, the factory pattern is deployed. Library A contains a simple ShapeFactory:

class ShapeFactory
{
public:
    typedef std::function<std::shared_ptr<Shape>()> ShapeBuilder;

    static std::shared_ptr<Shape> create(const std::string &name)
    {
        auto it = registry().find(name);
        if (it != registry().end())
        {
            return (it->second)();
        }

        return nullptr;
    }

    static std::map<std::string, ShapeBuilder>& registry()
    {
      static std::map<std::string, ShapeBuilder> impl;
      return impl;
    }
};

template<typename T>
struct ShapeFactoryRegister
{
    ShapeFactoryRegister(std::string name)
    {
      ShapeFactory::registry()[name] = []() {return std::make_shared<T>();};
      std::cout << "Registering class '" << name << "'\n";
    }
};

Derived classes in Library B self-register themselves with the factory via a static ShapeFactoryRegister member variable:

class Circle : public Shape
{
public:
    void draw() const
    {
        std::cout << "Circle" << std::endl;
    }

private:
    static ShapeFactoryRegister<Circle> AddToFactory_;
};

ShapeFactoryRegister<Circle> Circle::AddToFactory_("Circle");

The function from Library C then instantiates derived classes via the factory:

std::shared_ptr<Shape> ShapeEditor::create()
{
    if (someCondition)
    {
        return ShapeFactory::create("Circle"); // Coupling between Library B and Library C
    }
}

The use of a hard coded string as a handle to the derived types effectively couples Library B and Library C together. This method is no better than simply moving the derived classes into Library B.

The questions I have on this scenario is:

  1. What are alternatives to using a string as a handle to derived classes?
  2. How do I further decouple Library B and Library C?
  3. What are alternatives to the factory pattern to solve this problem?
  4. Is the factory design pattern the right hammer for this problem?

This problem has had me stumped for a while. Many thanks!

Aucun commentaire:

Enregistrer un commentaire