重读设计模式——工厂方法

抽象工厂#

使用接口的多态特性来创建一系列的对象,而不是指定具体的类。

class AbstractProductA {
public:
    virtual ~AbstractProductA() = default;

    virtual void eat() = 0;
};

class AbstractProductB {
public:
    virtual ~AbstractProductB() = default;

    virtual void use() = 0;
};

class AbstractFactory {
public:
    virtual ~AbstractFactory() = default;

public:
    virtual std::shared_ptr<AbstractProductA> createProductA() = 0;

    virtual std::shared_ptr<AbstractProductB> createProductB() = 0;
};

void client(AbstractFactory& factory) {
    auto product_a = factory.createProductA();
    product_a->eat();
    auto product_b = factory.createProductB();
    product_b->use();
}

现在,假设我们准备建造一个工厂,工厂会生产两个产品A和B,产品A是食品,有一个eat的方法,产品B是玩具,有一个play方法。
客户将会使用从工厂中拿到这两个产品,并消费掉。

class ProductA1 : public AbstractProductA {
    void eat() override {
        cout << "yummy" << endl;
    }
};

class ProductA2 : public AbstractProductA {
    void eat() override {
        cout << "awful" << endl;
    }
};

class ProductB1 : public AbstractProductB {
public:
    void use() override {
        cout << "just so so" << std::endl;
    }
};

class ProductB2 : public AbstractProductB {
public:
    void use() override {
        cout << "very cool" << std::endl;
    }
};

class ConcreteFactoryA : public AbstractFactory {
public:
    ~ConcreteFactoryA() = default;

public:
    std::shared_ptr<AbstractProductA> createProductA() override {
        cout << "productA made in factoryA" << endl;
        return std::make_shared<ProductA1>();
    }

    std::shared_ptr<AbstractProductB> createProductB() override {
        cout << "productB made in factoryA" << endl;
        return std::make_shared<ProductB2>();
    }
};

class ConcreteFactoryB : public AbstractFactory {
public:
    std::shared_ptr<AbstractProductA> createProductA() override {
        cout << "productA made in factoryB" << endl;
        return std::make_shared<ProductA2>();
    }

    std::shared_ptr<AbstractProductB> createProductB() override {
        cout << "productB made in factoryB" << endl;
        return std::make_shared<ProductB1>();
    }
};

现在我们实现了两个工厂,工厂A生产食品A1和玩具B2,工厂B生产食品A2和B1。我们使用这两个工厂来生产出产品。

 int main() {
    ConcreteFactoryA factory_a;
    client(factory_a);

    ConcreteFactoryB factory_b;
    client(factory_b);

    return 0;
}

打印如下:

productA made in factoryA
yummy
productB made in factoryA
very cool
productA made in factoryB
awful
productB made in factoryB
just so so

这样就通过工厂方法来获得了不同的产品。
从终端输出的结果来看,工厂B生产的产品似乎不是那么好,于是我们决定升级这个工厂,生产出更好的产品:

class ProductA3 : public AbstractProductA {
    void eat() override {
        cout << "delicious" << endl;
    }
};

class ProductB3 : public AbstractProductB {
    void play() override {
        cout << "super cool" << endl;
    }
};

class ConcreteFactoryC : public ConcreteFactoryB {
    std::shared_ptr<AbstractProductA> createProductA() override {
        cout << "productA made in factoryC" << endl;
        return std::make_shared<ProductA3>();
    }

    std::shared_ptr<AbstractProductB> createProductB() override {
        cout << "productB made in factoryC" << endl;
        return std::make_shared<ProductB3>();
    }
};

升级完成,工厂C派生自工厂B,并且生产新的产品A3和B3。使用一下这个工厂生产的产品:

int main() {
    ConcreteFactoryC factory_c;
    client(factory_c);

    return 0;
}

结果如下:

productA made in factoryC
delicious
productB made in factoryC
super cool

总结#

  • 通过工厂模式,将客户和具体的实现分离开来,客户只需要一个抽象接口就可以获得想要的实例,而不用关心具体的实现。
  • 工厂模式使得一系列的产品可以很容易的替换,比如需要生产产品A3和B3时,只需要再派生出一个工厂类即可,而无需对现有的代码进行任何的更改。
  • 同样,这有利于产品的一致性。当一个系列的对象被设计为一起工作时,一个应用一次只能使用一个系列中的对象,一定程度地避免了奇葩代码的出现。

上述的工厂有个明显的缺点:

工厂难以扩展去支持新的种类,例如,如果有一天希望给当前的工厂添加生产新产品C的功能的话,就需要从抽象基类开始,将所有的工厂类都添加生产产品C的接口。
后面会有解决方法,这里先按下不表。

工厂单例#

对于具体的工厂,或是其他什么需要保证全局都只有一份的对象,相比使用全局变量,单例总是更好的方式。
简单的做法,可以将类的构造隐藏起来,然后通过一个静态接口向外提供服务。

class Factory {
public:
    static std::shared_ptr<Factory>& instance() {
        static std::shared_ptr<Factory> instance = std::shared_ptr<Factory>(new Factory);
        return instance;
    }

    Factory(const Factory&) = delete;
    Factory& operator=(const Factory&) = delete;

protected:
    Factory() = default;
    
public:
    virtual void whoami() {
        cout << "factory" << endl;
    }
};

像这样,只有通过instance方法才可以获取到Factory的唯一实例,构造函数通过protected关键字隐藏起来。

简单场景下,这样的做法完全可行,不过如果类具有派生关系,或者我们只是希望获得一系列工厂中的一个,上述做法就不太适用了。
例如在上述Factory的基础上,又派生了两个工厂:

class SonFactory : public Factory {
public:
    static std::shared_ptr<SonFactory>& instance() {
        static std::shared_ptr<SonFactory> instance = std::shared_ptr<SonFactory>(new SonFactory);
        return instance;
    }
    
    SonFactory(const SonFactory&) = delete;
    SonFactory& operator=(const SonFactory&) = delete;

protected:
    SonFactory() = default;

public:
    void whoami() override {
        cout << "son factory" << endl;
    }
};

class ChildFactory : public SonFactory {
public:
    static std::shared_ptr<ChildFactory>& instance() {
        static std::shared_ptr<ChildFactory> instance = std::shared_ptr<ChildFactory>(new ChildFactory);
        return instance;
    }
    
    ChildFactory(const ChildFactory&) = delete;
    ChildFactory& operator=(const ChildFactory&) = delete;

protected:
    ChildFactory() = default;

public:
    void whoami() {
        cout << "child factory" << endl;
    }
};

这样做的话,每个工厂都会有一个单实例,事实上,我们希望派生出的工厂和基类工厂划为统一的系列对外提供服务,一次只需要其中的某一个工厂的实例。
于是可以再封装一层,通过一个工厂单例来统一进行调度,并且采用一种注册的方式来获取工厂的信息,而不用侵入任何工厂类的内部来获取工厂是否可用的信息。
修改一下上述的工厂类,所有的工厂类都将继承工厂单例类:

class FactorySingleton {
public:
    FactorySingleton() = default;

public:
    template<typename T>
    static void registerSelf(std::string name) {
        m_registry.insert(std::make_pair(name, std::shared_ptr<T>(new T)));
    }

    static std::shared_ptr<FactorySingleton> instance(std::string name) {
        if(m_registry.find(name) == m_registry.end()) {
            return nullptr;
        }
        return m_registry.at(name);
    }

private:
    inline static std::map<std::string, std::shared_ptr<FactorySingleton>> m_registry{};

public:
    virtual void whoami() = 0;
};

class Factory : public FactorySingleton {
public:
    Factory(const Factory&) = delete;
    Factory& operator=(const Factory&) = delete;

protected:
    Factory() = default;
    
public:
    void whoami() override {
        cout << "factory" << endl;
    }

    friend class FactorySingleton;
};

class SonFactory : public Factory {
public:
    SonFactory(const SonFactory&) = delete;
    SonFactory& operator=(const SonFactory&) = delete;

protected:
    SonFactory() = default;

public:
    void whoami() override {
        cout << "son factory" << endl;
    }

    friend class FactorySingleton;
};

class ChildFactory : public SonFactory {
public:
    ChildFactory(const ChildFactory&) = delete;
    ChildFactory& operator=(const ChildFactory&) = delete;

protected:
    ChildFactory() = default;

public:
    void whoami() {
        cout << "child factory" << endl;
    }

    friend class FactorySingleton;
};

这里我们新建了一个工厂单例类,并让所有的工厂都派生自这个类,当需要使用到某个工厂时,就调用registerSelf将自己注册进去。

所有派生类都须将基类FactorySinglton声明为友元类,这样才能使用new来构造出对应的派生类对象,因为派生类对象的构造函数都声明为了私有,外部无法获取到。

所有的工厂都可以通过工厂单例类的instance方法来获得:

int main() {
    Factory::registerSelf<Factory>("factory");
    SonFactory::registerSelf<SonFactory>("sonfactory");
    ChildFactory::registerSelf<ChildFactory>("childfactory");

    FactorySingleton::instance("factory")->whoami();
    FactorySingleton::instance("sonfactory")->whoami();
    FactorySingleton::instance("childfactory")->whoami();

    return 0;
}

这个工厂单例类似于抽象工厂基类的作用,只是添加了调用子工厂单例的功能。

工厂方法#

工厂方法可以看作是稍微简化的抽象工厂。工厂方法适用于这样的场景:工厂基类当前并不确定要生产什么样的产品,可能不同时刻需要不同的产品,于是将具体的生成过程推迟到子类中去完成。需要一个新的产品时,就派生一个新的工厂子类。

class BaseProduct {
public:
    virtual void eat() = 0;
    virtual void drink() = 0;
    virtual void play() = 0;
};

class BaseFactory {
public:
    virtual BaseProduct* createProduct() = 0;
};

struct ProductA : public BaseProduct {
    void eat() override {
        cout << "product a eat\n";
    }

    void drink() override {
        cout << "product a drink\n";
    }

    void play() override {
        cout << "product a play\n";
    }
};

class FactoryA : public BaseFactory {
    BaseProduct* createProduct() override {
        return new ProductA;
    }
};

struct ProductB : public BaseProduct {
    void eat() override {
        cout << "product b eat\n";
    }

    void drink() override {
        cout << "product b drink\n";
    }

    void play() override {
        cout << "product b play\n";
    }
};

class FactoryB : public BaseFactory {
    BaseProduct* createProduct() override {
        return new ProductB;
    }
};

产品a和产品b会有不同的表现形式,根据需要选择合适的产品。

int main() {
    BaseFactory* ptr = new FactoryA;

    BaseProduct* p_a = ptr->createProduct();
    p_a->eat();         // product a eat
    p_a->drink();       // product a drink
    p_a->play();        // product a play
    delete ptr;

    ptr = new FactoryB;
    BaseProduct* p_b = ptr->createProduct();
    p_b->eat();         // product b eat
    p_b->drink();       // product b drink
    p_b->play();        // product b play
    
    return 0;
}

原型#

前面提到,抽象工厂存在着难以扩展的缺点,如果要加入新的接口,势必要将所有的子类全部修改。原型可以一定程度弥补这个缺点。
基本的思路是这样:无论是产品A还是产品B,终究都是产品,于是我们只使用一个接口来生产产品(感觉像是回到了工厂方法)。不同在于,这次我们将在产品类上做文章,要求产品类需要提供一个clone接口来返回自身,然后通过标识或其他方法来决定工厂生产哪种产品。

class ProductA : public AbstractProduct {
public:
    ProductA* clone() {
        return new ProductA;
    }
};

class ProductB : public AbstractProduct {
public:
    ProductB* clone() {
        return new ProductB;
    }
};

class ConcreteFactory : public AbstractFactory {
public:
    ~ConcreteFactory() = default;

public:
    AbstractProduct* createProduct(std::string tag) override {
        AbstractProduct* ptr;
        if(tag == "A") {
            ptr = ProductA().clone();
        }
        else if(tag == "B") {
            ptr = ProductB().clone();
        }
        return ptr;
    }
};

这样,又增加了一些接口的灵活性。

适用性#

  • 如果系统应该独立于产品的创建、构成和表示时,也就是工厂创建时并不或者无法关心具体生产什么样的产品时。
  • 实例化的类要在运行期才确定时,例如动态加载。
  • 当类的实例需要有多种状态的组合时,建立相应数目的状态原型然后再克隆会比每次手动实例化更方便一些。

作者:cwtxx

出处:https://www.cnblogs.com/cwtxx/p/18718231

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   cwtxx  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示