Gabriel Grigoras
Gabriel Grigoras

Reputation: 151

Design pattern flexibility issue Factory Method

I've been having a recent discussion with a colleague regarding the Factory Method design pattern. One basic approach is that the static method (from the factory class for example )should hide the complex creation logic of the created class:

class IObject {
    //Interface
};

class A :public IObject {

};


class Factory {
    static IObject * create(int type) {
        //All logic goes here
    }
};

The issue is that in our case the factory class will always only return an explicit object of type A. My honest opinion was the the int argument of the static factory method is useless and ads unnecessary complexity.

Also returning an Interface pointer of type IObject is also forced in my opinion. There is no need to return a pointer of base class if there is and always will be only ONE implementation. I think things can be simplified :

class A {
};


class Factory {
    static A createA() {
        //All logic goes here
    }
};

As far as what GOF has to say:

The design patterns in this book are descriptions of communicating objects and classes that are customized to solve a general design problem in a particular context.

The answer seems to indicate that design patterns are more a description on how to solve some things and not a prescription.

So what do you guys think (for this particular case)?

Upvotes: 3

Views: 484

Answers (2)

ObliteratedJillo
ObliteratedJillo

Reputation: 5166

Factory Pattern is not only about ease of creating objects, without having to specify the exact class of the object that will be created, but it is also about flexibility (as in your title ) of creating new inherited objects, without changing any of the base code. So, I have to agree with your friend rather :)

For example, lets say this is your game code:

Sword.cpp
ShortSword.cpp
LongSword.cpp
GameEngine.cpp

How to insert a new super sword without changing any of the above codes? That's where Factory Pattern codes handy.

Here's some practical examples where Factory Pattern will shine :

1) Design of plugin features in software: Initially you have no idea what sorts of plugins will be integrated. You don't want to change your existing code for every new plugin, do you?

2) Integration of multiple hardware support in software: Need ability to seamlessly integrate newer hardware in code.

As VTT has pointed out in the comment, your factory code is not correct, it just facilitate creation of already defined objects, but it can't handle object z without modifying existing code in your factory.

Upvotes: 3

A M
A M

Reputation: 15267

Your are raising 2 questions:

  • What is your opinion how to implement something?
  • Factory or not Factory?

The answer to the 2nd question is easy. This is not a factory. Nothing in it. A factory (or better a abstarct factory) is mostly used to instantiate some class late (with a factory method). And the type is maybe known at runtime somewhen. Imaging you would read a file, and depending on the parameters in the read lines create different classes with different parameters.

This can be done with an abstract factory and a factoy method.

Please see the below very simple factory class with some demo code added to it

#include <iostream>
#include <map>
#include <utility>
#include <any>


// Some demo classes ----------------------------------------------------------------------------------
struct Base {
    Base(int d) : data(d) {};
    virtual ~Base() { std::cout << "Destructor Base\n"; }
    virtual void print() { std::cout << "Print Base\n"; }
    int data{};
};
struct Child1 : public Base {
    Child1(int d, std::string s) : Base(d) { std::cout << "Constructor Child1 " << d << " " << s << "\n"; }
    virtual ~Child1() { std::cout << "Destructor Child1\n"; }
    virtual void print() { std::cout << "Print Child1: " << data << "\n"; }
};
struct Child2 : public Base {
    Child2(int d, char c, long l) : Base(d) { std::cout << "Constructor Child2 " << d << " " << c << " " << l << "\n"; }
    virtual ~Child2() { std::cout << "Destructor Child2\n"; }
    virtual void print() { std::cout << "Print Child2: " << data << "\n"; }
};
struct Child3 : public Base {
    Child3(int d, long l, char c, std::string s) : Base(d) { std::cout << "Constructor Child3 " << d << " " << l << " " << c << " " << s << "\n"; }
    virtual ~Child3() { std::cout << "Destructor Child3\n"; }
    virtual void print() { std::cout << "Print Child3: " << data << "\n"; }
};



using UPTRB = std::unique_ptr<Base>;


template <class Child, typename ...Args>
UPTRB createClass(Args...args) { return std::make_unique<Child>(args...); }

// The Factory ----------------------------------------------------------------------------------------
template <class Key, class Object>
class Factory
{
    std::map<Key, std::any> selector;
public:
    Factory() : selector() {}
    Factory(std::initializer_list<std::pair<const Key, std::any>> il) : selector(il) {}

    template<typename Function>
    void add(Key key, Function&& someFunction) { selector[key] = std::any(someFunction); };

    template <typename ... Args>
    Object create(Key key, Args ... args) {
        if (selector.find(key) != selector.end()) {
            return std::any_cast<std::add_pointer_t<Object(Args ...)>>(selector[key])(args...);
        }
        else return nullptr;
    }
};

int main()
{
    Factory<int, UPTRB> factory{
        {1, createClass<Child1, int, std::string>},
        {2, createClass<Child2, int, char, long>}
    };
    factory.add(3, createClass<Child3, int, long, char, std::string>);


    // Some test values
    std::string s1(" Hello1 "); std::string s3(" Hello3 ");
    int i = 1;  const int ci = 1;   int& ri = i;    const int& cri = i;   int&& rri = 1;

    UPTRB b1 = factory.create(1, 1, s1);
    UPTRB b2 = factory.create(2, 2, '2', 2L);
    UPTRB b3 = factory.create(3, 3, 3L, '3', s3);

    b1->print();
    b2->print();
    b3->print();
    b1 = factory.create(2, 4, '4', 4L);
    b1->print();
    return 0;
}

Back to your first question. How something should be implemented. 5 people will give 25 different answers. Try to use the method that you are very familiar with and which fullfills the requirements. If you have a single application with a low chance for reusability, implement simply your easy solution.

If you develop a library, work in big project a big team, then maybe use more formal patterns. And then you should follow them. Because many smart people made a lot of effort to come up with good solutions (Design patterns).

So, sorry, no concrete answer. Just opinions.

Upvotes: 1

Related Questions