Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to support multiple construction signatures in a factory design?

I'm working with the following (simplified) factory design to create objects of some inheritance hierarchy, shouldn't be anything special:

// class to create
class Class
{
public:
  Class(Type type, Foo foo);
};

// Simple creator class.
// Used in practice to do some runtime checks about whether or not construction is allowed.
class Creator
{
public:
  Class* create( Type type, Foo foo ) const
  {
    return new Class( type, foo );
  }
};

class Factory
{
public:
  Factory
  {
    // fill object creator map on construction
    _map[ "name" ] = new Creator<Class>;
  }

  Class* create( const std::string& name, Type type, Foo foo )
  {
    // fowards to map entry
    return _map[name]->create( type, foo );
  }

private:
 std::map<std::string, Creator*> _map;
}

// client code
int main()
{
  Factory f;
  factory.create(name, type, foo);
}

Now I run into problems once I want to create subclasses which have a different constructor signature because the factory imposes a fixed signature on the entire inheritance hierarchy. I.e. for the following class I have no way of specifying the new 3rd parameter via the factory construction without imposing this extended signature on all other class of my hierarchy again.

class ExtClass : public Class
{
public:
  Class(Type type, Foo foo, NewMember nm)
    : Class(type, foo),
      _nm(nm)

private:
  NewMember _nm;
};

Is there a way to make this work with my current design without making pricinpal changes? I'm thinking of using templates or bind objects to make varying argument calls possible. Or would you in this case suggest a different solution than the factory design?

like image 604
user1709708 Avatar asked Nov 09 '22 18:11

user1709708


1 Answers

This answer is different enough to my first solution and it includes what you might consider "principal changes" that I have made it a separate answer:

In my opinion, it is superior to my earlier solution, but it depends what your exact requirements are. The features here are:

  • Creator id is unique.
  • CreateObject supports implicit conversion of parameters.

The same limitation that the constructors must take const& parameters exists. It might not matter, but this solution only requires C++11. It would, of course, be a bit simpler with the new C++17 tuple features.

#include <boost/functional/factory.hpp>
#include <boost/function.hpp>
#include <boost/variant.hpp>

#include <map>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
// Just for debugging.
#include <iostream>
#include <typeinfo>
#include <cxxabi.h>

// Tuple manipulation.

template <typename Signature>
struct signature_impl;

template <typename ReturnType, typename... Args>
struct signature_impl<ReturnType(Args...)>
{
    using return_type = ReturnType;
    using param_types = std::tuple<Args...>;
};

template <typename T>
using signature_t = signature_impl<T>;


template <std::size_t... Ints>
struct indices {};

template <std::size_t N, std::size_t... Ints>
struct build_indices : build_indices<N-1, N-1, Ints...> {};

template <std::size_t... Ints>
struct build_indices<0, Ints...> : indices<Ints...> {};

template <typename Tuple>
using make_tuple_indices = build_indices<std::tuple_size<typename std::remove_reference<Tuple>::type>::value>;

// The multiple-signature factory.
template <class AbstractProduct, typename IdentifierType, typename... ProductCreators>
class multifactory
{
    using functions = boost::variant<boost::function<ProductCreators>...>;

    std::map<IdentifierType, functions> associations_;

    template <typename Signature>
    struct dispatch_foo
    {
        template <typename CreateArgs, std::size_t... Indices>
        typename std::enable_if<std::is_convertible<CreateArgs, typename signature_t<Signature>::param_types>::value, AbstractProduct>::type
        static apply(boost::function<Signature> const &f, CreateArgs && t, indices<Indices...>)
        {
            return f(std::get<Indices>(std::forward<CreateArgs>(t))...);
        }

        template <typename CreateArgs, std::size_t... Indices>
        typename std::enable_if<!std::is_convertible<CreateArgs, typename signature_t<Signature>::param_types>::value, AbstractProduct>::type
        static apply(boost::function<Signature> const &, CreateArgs &&, indices<Indices...>)
        {
            return nullptr;
        }
    };

    template <typename... CreateArguments>
    struct dispatcher : boost::static_visitor<AbstractProduct>
    {
        std::tuple<CreateArguments...> args;

        dispatcher(CreateArguments const&... args) : args{std::forward_as_tuple(args...)} {}

        template <typename Signature>
        AbstractProduct operator()(boost::function<Signature> const &f) const
        {
            int status;
            std::cout << "visitor: " << abi::__cxa_demangle(typeid(Signature).name(), nullptr, 0, &status) << "\n";
            return dispatch_foo<Signature>::apply(f, args, make_tuple_indices<std::tuple<CreateArguments...>>{});
        }
    };

public:
    template <typename ProductCreator>
    bool Register(IdentifierType id, ProductCreator &&creator) {
        return associations_.emplace(id, std::forward<ProductCreator>(creator)).second;
    }

    bool Unregister(const IdentifierType& id) {
        return associations_.erase(id) == 1;
    }

    template <typename... Arguments>
    AbstractProduct CreateObject(const IdentifierType& id, Arguments const& ... args) {
        auto i = associations_.find(id);
        if (i != associations_.end()) {
            dispatcher<Arguments...> impl(args...);
            return boost::apply_visitor(impl, i->second);
        }
        throw std::runtime_error("Creator not found.");
    }
};


struct Arity {
    virtual ~Arity() = default;
};

struct Nullary : Arity {};

struct Unary : Arity {
    Unary() {} // Also has nullary ctor.
    Unary(int) {}
};


int main(void)
{
    multifactory<Arity*, int, Arity*(), Arity*(const int&)> factory;
    factory.Register(0, boost::function<Arity*()>( boost::factory<Nullary*>() ));
    factory.Register(1, boost::function<Arity*(const int&)>(boost::factory<Unary*>()) );
    auto a = factory.CreateObject(0);
    assert(a);
    assert(typeid(*a) == typeid(Nullary));
    auto b = factory.CreateObject(1, 2);
    assert(b);
    assert(typeid(*b) == typeid(Unary));
}
like image 158
Jeremy W. Murphy Avatar answered Nov 14 '22 22:11

Jeremy W. Murphy