How to deal with designers of different lengths with Factory design pattern?

I want to have a class that creates different types of objects based on the string I pass. From my research, this best describes the Factory design pattern. I have success, but I ran into a design problem: I don’t know how to create objects with different length constructors.

Take, for example, an abstract parent class called Pet. Of it 3 children: fish, cat and dog. They all inherit the weight and color from Pet, so this happens in their constructors. But the fish may want a few fins and booleans as to whether it is salted fish. This is a constructor with 4 parameters. The cat would like to get the number of legs. These are 3 parameters. A dog can have parameters for its legs, breed and whether it plays well with other dogs in 5 ways.

In C ++, I understand that there is no reflection, so the most common practice is to simply declare a line map for function pointers, where the function pointer points to a function that looks something like this: / p>

    template<typename T> Pet* createObject(int weight, std::string color) {return new T(weight, color);}

Again, I'm not sure how I will add more parameters to the call without affecting the calls to the constructors of other objects.

I can think of two workarounds: create new functions to accept different parameters or create default parameters for constructors above a certain size.

Workaround 1 seems excessive depending on how many different sizes of parameters I have.

Workaround 2 apparently ignores the entire constructor point, since I will have to assign data after the constructor is called.

Are there any better workarounds?

+4
source share
5 answers

You can use variable templates and perfect forwarding.

template<typename T, typename... Args>
Pet* createObject(Args&&... args) {
    return new T(std::forward<Args>(args)...);
}

, , , , T*. , , . shared_ptr unique_ptr. factory: make_shared make_unique ( ++ 14). , ++ 11, shared_ptr make_shared Boost.

, , , . , , , , , , , . factory. , ++ ( , ++ 11) . , , , Pet. , - , . factory :

typedef std::function<std::shared_ptr<Pet>(const std::string& name)> PetFactory;

- , Dog ( ).

PetFactory petFactory =
        [](const std::string& name) {
            return std::make_shared<Dog>(name, 12, "brown", 23.5);
        }

, factory:

std::shared_ptr<Pet> pet = petFactory("Pet Name");
+2

, ( ..)

#include <map>
#include <string>

// definition of pet hierarcy
class pet_t
{
public:
    virtual ~pet_t(void) {}
};

class frog_t : public pet_t
{
public:
    frog_t(int) {}
    static frog_t *builder(int n) { return new frog_t(n); }
};

class dog_t : public pet_t
{
public:
    dog_t(const char *, int) {}
    static dog_t *builder(const char *n, int p) { return new dog_t(n, p); }
};
// the per builder function type
typedef pet_t *(*pet_builder_t)(...);
// the map containing per builders: it indexed by per type name
std::map<std::string, pet_builder_t> registry;
void build_factory(void)
{
    registry["frog"] = reinterpret_cast<pet_builder_t>(&frog_t::builder);
    registry["dog"] = reinterpret_cast<pet_builder_t>(&dog_t::builder);
}
// the actual factory function
template <class ...Ts>
pet_t *factory(std::string name, Ts&&...ts)
{
    pet_builder_t builder = registry[name];
    // assume there is something in the map
    return builder(std::forward<Ts>(ts)...);
}

int main(int argc, char *argv[])
{
    build_factory();
    dog_t  *dog  = dynamic_cast<dog_t  *>(factory(std::string("dog"), std::string("pluto"), 3));
    frog_t *frog = dynamic_cast<frog_t *>(factory(std::string("frog"), 7));
}

, , .

0

, factory: Fish, .

factory , , , . , factory , , : factory , .

, : factory.

, factory, , "" . , - .

0

, ! factory, . , , TMP.

:

/* Pet and derived classes omitted */
/*    Registrar pattern omitted    */

struct PetFactory {
    using PetCtr = std::unique_ptr<Pet> (*)(std::istream &);

    static auto make(std::istream &stream) {
        std::string str;
        stream >> str;
        return ctrMap().at(str)(stream);
    }

    using PetCtrMap = std::map<std::string, PetCtr>;
    static PetCtrMap &ctrMap();
};

template <class T, class... Args, std::size_t... Idx>
auto streamCtr_(std::istream &stream, std::index_sequence<Idx...> const &) {

    std::tuple<Args...> args;

    using unpack = int[];
    unpack{0, (void(stream >> std::get<Idx>(args)), 0)...};

    return std::make_unique<T>(std::move(std::get<Idx>(args))...);
}

template <class T, class... Args>
auto streamCtr(std::istream &stream) {
    return std::unique_ptr<Pet>(streamCtr_<T, Args...>(
        stream,
        std::index_sequence_for<Args...>{}
    ));
}

int main() {
    PetFactory::make("fish 1 silver 5 true");
    PetFactory::make("cat 4 tabby 9");
    PetFactory::make("dog 17 white husky playful");
}

:

I'm a 1kg silver fish with 5 fins, living in salty water.
I'm a 4kg tabby cat with 9 lives.
I'm a 17kg white husky and I'm playful.

, Coliru. !

0

; , , , . , boost::any, .

using MyArgs = unordered_map<string, boost::any>;

class Fish {
  Fish(MyArgs args) {
    int num_fins = boost::any_cast<int>(args.at("num_fins"));
  }

, factory :

unique_ptr<Pet> factory(string animal_name, MyArgs args) {
  auto func = factory_map.at(animal_name);
  return func(args);
}

Edit: I should also note that if you make a mistake, then it is your MyArgs that has no argument or has the wrong type, an exception will be thrown. This way you can get a nice clear error and even handle it, as opposed to getting UB.

-1
source

Source: https://habr.com/ru/post/1598388/


All Articles