05. 工厂方法模式

一、工厂方法模式

  简单工厂模式虽然简单,但存在一个很严重的问题:当系统中需要引入新产品时,由于静态工厂方法通过所传入参数的不同来创建不同的产品,这必定要修改工厂类的源代码,将违背开闭原则。此外,在简单工厂模式中,所有的产品都由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,包含了大量的 if…else… 代码,具体产品与工厂类之间的耦合度高,严重影响了系统的灵活性和扩展性。如何实现增加新产品而不影响已有代码?工厂方法模式为此应运而生。

  在 工厂方法模式 中,不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构。

  工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它在父类中定义一个创建对象的方法,但允许子类决定具体要实例化哪个类。这种模式将类的实例化操作延迟到子类中完成,即由子类来决定究竟应该实例化哪一个类。在工厂方法模式中,创建对象的接口被定义在父类中,而具体的实现则交由子类来完成。这样,客户端只需要知道具体工厂的名称,而无需了解产品的具体创建过程,从而实现了客户端与产品实现之间的解耦。

  工厂方法模式的主要角色:

  • 抽象工厂(Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
  • 具体工厂(Concrete Factory):主要是实现抽象工厂的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特征和功能。
  • 具体产品(Concrete Product):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂一一对应。

  总的来说,工厂方法模式通过定义一个用于创建对象的接口,让子类决定实例化哪一个类,从而实现了客户端与产品实现之间的解耦,提高了系统的灵活性和可扩展性。

二、C++实现工厂方法模式

工厂方法模式

  将所有产品类公共的代码移至抽象产品类,并在抽象产品类中声明一些抽象方法,以供不同的具体产品类来实现。

// 抽象披萨类
class Pizza
{
private:
    std::string name;

public:

    Pizza(void);
    Pizza(std::string name);

    virtual void prepare(void) = 0;                                             // 准备原材料,这里不同披萨准备原材料不同
    void bake(void);                                                            // 烘烤,这里不同披萨烘烤方式相同
    void cut(void);                                                             // 切块,这里不同披萨切块方式相同
    void box(void);                                                             // 装盒,这里不同披萨装盒方式相同

    std::string getName();
    void setName(std::string name);
};
Pizza::Pizza(void) {}
Pizza::Pizza(std::string name) : name(name) {}

void Pizza::bake(void)
{
    std::cout << name + "烘烤" << std::endl;
}

void Pizza::cut(void)
{
    std::cout << name + "切块" << std::endl;
}

void Pizza::box(void)
{
    std::cout << name + "装盒" << std::endl;
}

std::string Pizza::getName(void)
{
    return name;
}

void Pizza::setName(std::string name)
{
    this->name = name;
}

  在具体产品类中实现了抽象产品类中声明的抽象业务方法,不同的具体产品类可以提供不同的实现。

// 奶酪披萨
class CheesePizza : public Pizza
{
public:
    using Pizza::Pizza;

    void prepare(void) override;
};
void CheesePizza::prepare(void)
{
    std::cout << "准备" + getName() + "的原材料" << std::endl;
    std::cout << "准备奶酪" << std::endl;
}
// 水果披萨
class FruitPizza : public Pizza
{
public:
    using Pizza::Pizza;

    void prepare(void) override;
};
void FruitPizza::prepare(void)
{
    std::cout << "准备" + getName() + "的原材料" << std::endl;
    std::cout << "准备水果" << std::endl;
}

  与简单工厂模式相比,工厂方法模式最重要的区别是引入了抽象工厂角色。抽象工厂可以是接口,也可以是抽象类或者具体类。在抽象工厂中声明了工厂方法但并未实现工厂方法,具体产品对象的创建由其子类负责。客户端针对抽象工厂编程,可在运行时再指定具体工厂类。

// 披萨工厂,根据顾客的订单,来生产不同的披萨
class PizzaFactory
{
public:
    virtual Pizza * ceratePizza(void) = 0;
};

  具体工厂类实现了工厂方法,不同的具体工厂可以创建不同的具体产品。

// 具体的奶酪披萨工厂
class CheesePizzaFactory : public PizzaFactory
{
public:
    Pizza * ceratePizza(void) override;
};
Pizza * CheesePizzaFactory::ceratePizza(void)
{
    return new CheesePizza("奶酪披萨");
}
// 具体的水果披萨工厂
class FruitPizzaFactory : public PizzaFactory
{
public:
    Pizza * ceratePizza(void) override;
};
Pizza * FruitPizzaFactory::ceratePizza(void)
{
    return new FruitPizza("水果披萨");
}

  在客户端代码中,只需关心工厂类即可。不同的具体工厂可以创建不同的产品。

// 披萨店,根据顾客的订单,来选择不同的披萨
class PizzaStore
{
private:
    PizzaFactory * pizzaFactory;

public:
    Pizza * order(void);                                                        // 顾客订购披萨
    void setPizzaFactory(PizzaFactory * pizzaFactory);
};
void PizzaStore::setPizzaFactory(PizzaFactory * pizzaFactory)
{
    this->pizzaFactory = pizzaFactory;
}

Pizza * PizzaStore::order(void)
{
    Pizza * pizza = pizzaFactory->ceratePizza();                                // 根据顾客的订单,来选择不同的披萨

    pizza->prepare();
    pizza->bake();
    pizza->cut();
    pizza->box();

    return pizza;
}

  main() 函数:

#include <iostream>

int main(void)
{
    PizzaStore pizzaStore;

    // 顾客订购奶酪披萨
    PizzaFactory * pizzaCheessFactory = new CheesePizzaFactory();
    pizzaStore.setPizzaFactory(pizzaCheessFactory);
    Pizza * cheesePizza = pizzaStore.order();
    std::cout << std::endl;

    // 顾客订购水果披萨
    FruitPizzaFactory * pizzaFruitFactory = new FruitPizzaFactory();
    pizzaStore.setPizzaFactory(pizzaFruitFactory);
    Pizza * fruitPizza = pizzaStore.order();

    delete pizzaCheessFactory;
    delete pizzaFruitFactory;
    delete cheesePizza;
    delete fruitPizza;

    return 0;
}

三、工厂方法的模式总结

3.1、工厂方法模式的优点

  • 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节。用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
  • 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,正是因为所有的具体工厂类都具有同一抽象父类。
  • 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合开闭原则。

3.2、工厂方法模式的缺点

  • 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
  • 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到 DOM、反射等技术,增加了系统的实现难度。

3.3、工厂方法模式的适用场景

  • 客户端不知道其所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
  • 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
posted @ 2023-08-24 20:20  星光映梦  阅读(13)  评论(0编辑  收藏  举报