设计模式的灵活性问题:工厂方法

3
我最近和一位同事讨论了工厂方法设计模式。其中一个基本的方法是,静态方法(例如来自工厂类的方法)应该隐藏所创建类的复杂创建逻辑。
class IObject {
    //Interface
};

class A :public IObject {

};


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

问题在于,在我们的情况下,工厂类将始终仅返回类型为A的显式对象。 我的诚实意见是,静态工厂方法的int参数是无用的,增加了不必要的复杂性。
我认为,如果只有一个实现,就没有必要强制返回IObject类型的接口指针。我认为可以简化事情:
class A {
};


class Factory {
    static A createA() {
        //All logic goes here
    }
};
  • 我的大学认为设计模式需要按照字面意思应用,也许将来会有变化,因此使用他的版本将简化未来的工作(因为代码更灵活)。
  • 我更倾向于 KISS 原则和诚实,为了可能不存在的功能而使代码变得灵活可能会带来问题。

至于 GOF 所说的:

本书中的设计模式是定制的通信对象和类的描述,旨在解决特定上下文中的一般设计问题。

答案似乎表明,设计模式更多地是解决问题的描述,而不是规定

那么你们对此有何看法(针对这个特定情况)?


2
在这种情况下,我必须同意你的观点,即YAGNI。除非有迹象表明您将在不久的将来受益于或需要其他设计,否则我会像你说的那样KISS。 - Tom
2
个人而言,我会直接在 A 类中使用静态方法并将构造函数设置为私有 :) - Antoine Morrier
3
您的“简化”版本并不是一个工厂,我会说它只是一个原始的构建器。“问题在于,在我们的情况下,工厂类将始终仅返回类型A的明确对象”-这里存在一个问题。工厂类的客户端不应该知道并依赖于正在创建的对象的实际类型。 - user7860670
KISS 可以被解释为“如果你实际上不需要它,就不要引入工厂。”;-) - Scheff's Cat
2
“模式书”中许多人忽略的一个重要事情是你引用的一部分:“在特定上下文中解决通用设计问题”。由于您没有遇到工厂模式旨在解决的设计问题(似乎也没有遇到相关上下文),因此实际上没有什么可以应用它。现在和可预见的未来承担更多工作,只是因为它可能简化某些可能不会发生的事情,这并不是一个好的时间利用方式。(您本质上是在承担债务,并支付可能永远不会收到的利息。也就是说,您正在借钱赌博。) - molbdnilo
2个回答

3

工厂模式不仅可以轻松地创建对象,而无需指定将要创建的对象的确切类,而且还可以灵活地创建新的继承对象,而不更改任何基本代码(正如您的标题中所述)。因此,我必须赞同您的朋友 :)

例如,假设这是您的游戏代码:

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

如何在不更改上述任何代码的情况下插入新的超级剑?这就是工厂模式派上用场的地方。
以下是一些实际示例,其中工厂模式将发挥作用:
1)软件中插件功能的设计:最初,您不知道将集成哪些插件。您不想为每个新插件更改现有代码,是吗?
2)多个硬件支持在软件中的集成:需要能够无缝地将新硬件集成到代码中。
正如VTT在评论中指出的那样,您的工厂代码不正确,它只是方便创建已定义的对象,但它无法处理对象z,除非修改工厂中的现有代码。

我只能部分地同意:如果实现一个模式,那么就要正确地实现它!到目前为止还好。然而,问题似乎暗示我们根本不需要这个模式;如果我们事先知道我们实际上不会从中获益,因为永远不会有任何其他替代方案来替换A,那么为什么要费心去使用模式并承担多态性的开销呢? - Aconcagua
@Aconcagua:同意。楼主简单的使用情况并不足以应用该模式。我更关注该模式在实际场景中的灵活性方面。 - seccpur

1
您提出了两个问题:
  • 您如何实现某些东西的观点?
  • 工厂还是非工厂?
第二个问题的答案很简单。这不是一个工厂。里面没有任何东西。工厂(或更好地说抽象工厂)通常用于晚期实例化某个类(使用工厂方法)。并且该类型可能在运行时某个时刻已知。想象一下,您将读取一个文件,并根据读取行中的参数创建具有不同参数的不同类。
这可以使用抽象工厂和工厂方法来完成。
请参见下面非常简单的工厂类以及其中添加的一些演示代码。
#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;
}

回到你的第一个问题。如何实现某件事情。5个人会给出25种不同的答案。尝试使用你非常熟悉并且符合要求的方法。如果你只有一个应用程序,很少会被重复使用,那么就简单地实现你的简单解决方案。
如果你正在开发一个库,在大型项目中工作,或者是一个大团队,那么可能需要使用更正式的模式。然后你应该遵循它们。因为许多聪明的人付出了很多努力来提出好的解决方案(设计模式)。
所以,抱歉,没有具体的答案。只有意见。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接