阿里云面试:设计模式中为什么工厂模式被称之为创建型模式?

前言:

工厂模式在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。在面向对象设计中工厂模式是程序设计的首先模式。为了深入理解这个概念,首先讲一下“工厂”的概念。工厂是可以大量重复生产相同产品的场所。对于工厂来讲,生产一件产品和生产一万件产品在技术和管理层面上不应该有本质差别。工厂生产的前提是原料,工人是充足的。在现实生活中,一个日产量100件和10000件的工厂在技术和管理细节上可能还存在较大差别,但是在计算机软件中没有任何差别。微软卖出一个Windows软件和卖出一万个Windows软件对技术开发来讲没有任何差别。在面向对象设计里,软件工程师要假定内存空间是充足的,只要抽象出一个类,创建1个对象和100个对象在实现方法上是一样的。这个和工程生产的模式很相似,所以叫“工厂模式”。

 工厂模式定义

定义一个用于创建对象的接口,让子类决定实例化哪个类。Factory Method使得一个类的实例化延迟(目的:解耦 手段:虚函数)到子类。

在一个工厂中,不同的产品由管理者发配给不同的车间进行生产,当有新的产品需要生产时,则建立一个新的车间,减少不同车间之间的联系。工厂模式就是模拟这样一个工厂生产方式,定义一个工厂类,通过工厂类创建不同子类对象,从紧耦合变为松耦合。

工厂模式主要分为三种:

简单工厂模式
工厂方法模式
抽象工厂模式

分类:

以生产汽车电子控制器(ECU)为例说明,假设某个初创公司一开始只研发出一款针对一个客户A的四缸发动机控制器(ECU-i4),那么它只要把这一款ECU的生产工序拆解出来,按照一套方法生产就够了,这就叫简单工厂模式

 过一段时间,又来了新的客户B他需要六缸发动机控制器(ECU-i6),那么需要重新创建一个工厂吗?当然可以创建,可是如果开了这个先例。后面又有八缸,十二缸的需求还能这么办吗?稍微想一下就会觉得这个方法很快就会耗尽当前的工厂资源,公司规模的发展很快停滞。那就再进一步,把四缸发动机控制器的生产工序再抽象,哪些和六缸有关,哪些和六缸无关。公司只要再开发一套六缸的工序,在组装时分两条线,就相当于有了一个新的六缸的ECU工厂,不过它实际上和四缸ECU同处一个工厂,但是在管理上可以虚拟成两个工厂,这叫工厂方法模式

 又过了一段时间,六缸发动机控制器(ECU-i6)的客户说,我要在新款车上降低成本,需要一款塑料外壳的ECU-i6-2,原来的金属外壳ECU-i6也继续维持供货。这在工厂的生产流程上没有任何变化,只是产品组装的零件发生变化。只需要对产品再做一层抽象,工厂生产的是抽象产品,最终的具体产品由客户选择的组装零件来决定,这就叫抽象工厂模式

 

简单工厂模式(Single Factory Pattern)

工厂模式是GOF23中的创建型模式,简单工厂模式并不属于23中设计模式中的一种,而是工厂方法模式中的一种简单实现。这里以访问不同数据库的相同数据为例,首先定义一个用户信息表以及一个用户表访问的抽象基类,并且定义用户表访问的虚方法进行多态继承。

//用户表
class User
{
public:
    int m_id;
    std::string m_name;
 
    User(int id, std::string name) : m_id(id), m_name(name) {}
};
 
// 不同数据库对用户表的数据访问
// 简单工厂模式
class IUser
{
public:
    virtual void insert(User user) = 0;
    virtual void getUser(int id) = 0;
};

定义不同的子类针对不同的数据库访问进行实例化,并重写继承而来的insert和getUser方法

//MySQL数据库访问User表
class MySQLUser : public IUser
{
public:
    void insert(User user) override
    {
        std::cout << "( MySQL ) insert a data userid: " << user.m_id << " and username: " << user.m_name << std::endl;
    }
 
    void getUser(int id) override
    {
        std::cout << "( MySQL ) get a data where user id is " << id << std::endl;
    }
};
 
//SQLServer数据库访问User表
class SQLServerUser : public IUser
{
public:
    void insert(User user) override
    {
        std::cout << "( SQLServer ) insert a data userid: " << user.m_id << " and username: " << user.m_name << std::endl;
    }
 
    void getUser(int id) override
    {
        std::cout << "( SQLServer ) get a data where user id is " << id << std::endl;
    }
};
 
//Access数据库访问User表
class AccessUser : public IUser
{
public:
    void insert(User user) override
    {
        std::cout << "( Access ) insert a data userid: " << user.m_id << " and username: " << user.m_name << std::endl;
    }
 
    void getUser(int id) override
    {
        std::cout << "( Access ) get a data where user id is " << id << std::endl;
    }
};

定义一个数据库工厂类DatabaseFactory对基类指针进行不同数据库子类实例,这里通过定义一个数据库的枚举类型进行实例的分发

enum database_type
{
    MYSQL,
    SQLSERVER,
    ACCESS
};
 
class DatabaseFactory
{
public:
    static IUser* createUser(int&& type)
    {
        IUser *iu = nullptr;
        switch (type)
        {
        case MYSQL:
            iu = new MySQLUser();
            break;
        case SQLSERVER:
            iu = new SQLServerUser();
            break;
        case ACCESS:
            iu = new AccessUser();
            break;
        default:
            break;
        }
        return iu;
    }
};

用户要使用不同的数据库对用户表进行访问,只需告诉工厂类进行访问的具体数据库类型,而无需关注类内是怎么进行实例化和运算的

int main()
{
    User u(202009, "Mr. Lin");
    IUser *iu = DatabaseFactory::createUser(SQLSERVER);
    iu->insert(u);
    iu->getUser(u.m_id);
}

从上面可以看出,用户通过工厂类实现了不同数据库子类的实例化。当需要增加新的子类,即数据库类型时,需要修改三处地方。一是新增一个子类;二是在枚举表中增加一个新的变量;三是在工厂类中的switch语句中,增加case分支,这修改了类的内部实现,违背了开放—封闭原则。

简单工厂模式与无工厂模式相比,其最大优点在于工厂类包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。

简单工厂模式的结构图如下: 

工厂方法模式(Factory Method Pattern)

虽然简单工厂模式在很大程度上解耦了,但是在增加功能时,还是会违背设计模式的开放—封闭原则,因此我们需要对工厂类进行一次重新的定义修改。这里还是以数据库访问数据为例,抽象基类IUser、子类MySQLUser、SQLServerUser和AccessUser与简单工厂模式中的一样。

 首先定义一个工厂方法的抽象基类,该类只提供一个创建具体实例的虚方法

// 工厂方法抽象基类
class DatabaseFactoryMethod
{
public:
    virtual IUser* createUser() = 0;
};

定义MySQL工厂、SQLServer工厂和Access工厂,返回具体的实例对象

//MySQL工厂
class MySQLFactory : public DatabaseFactoryMethod
{
public:
    IUser* createUser() override
    {
        return new MySQLUser();
    }
};
 
//SQLServer工厂
class SQLServerFactory : public DatabaseFactoryMethod
{
public:
    IUser* createUser() override
    {
        return new SQLServerUser();
    }
};
 
//Access工厂
class AccessFactory : public DatabaseFactoryMethod
{
public:
    IUser* createUser() override
    {
        return new AccessUser();
    }
};

最后用户直接通过工厂子类实例化工厂方法对象,调用getDatabaseType返回具体的运算类实例,再调用insert和getUser方法访问数据。

int main()
{
    User u(202009, "Mr. Lin");
    DatabaseFactoryMethod *db = new SQLServerFactory();
    IUser* iu = db->createUser();
    iu->insert(u);
    iu->getUser(u.m_id);
}

工厂方法模式与简单工厂模式相比,当需要增加新的数据库时,只需增加一个IUser的子类,以及一个DatabaseFactoryMethod的子类即可,虽然类的数量增加了,但是无需像简单工厂模式中需要修改类的内部实现,从而更大程度地解耦。

从工厂类上可以看出,工厂方法模式定义了一个用于创建对象的接口,让子类决定实例化哪一个类,使得一个类的实例化延迟到了子类。

也即是说,将创建具体实例的方式从只通过一个类来全部实现,变成了通过子类继承基类,重写继承接口的方式来创建。

工厂方法模式的结构图如下:

抽象工厂模式(Abstract Factory Pattern)

到现在可以看出工厂方法模式已经很好地诠释了设计模式的主要思想,但是在实际情况中数据库要访问多个表,而工厂方法模式里只针对了一个用户表User,因此出现了抽象工厂模式。 

在GOF中,抽象工厂是这样定义的,提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

定义多一个部门表Department,以及一个部门表访问抽象基类IDepartment,并派生不同数据库的子类

class Department
{
public:
    int m_id;
    std::string m_name;
 
    Department(int id, std::string name) : m_id(id), m_name(name) {}
};
 
class IDepartment
{
public:
    virtual void insert(Department dep) = 0;
    virtual void getDepartment(int id) = 0;
};
 
class MySQLDepartment : public IDepartment
{
public:
    void insert(Department dep) override
    {
        std::cout << "( MySQL ) insert a data departmentid: " << dep.m_id << " and departmentname: " << dep.m_name << std::endl;
    }
 
    void getDepartment(int id) override
    {
        std::cout << "( MySQL ) get a data where department id is " << id << std::endl;
    }
};
 
class SQLServerDepartment : public IDepartment
{
public:
    void insert(Department dep) override
    {
        std::cout << "( SQLServer ) insert a data departmentid: " << dep.m_id << " and departmentname: " << dep.m_name << std::endl;
    }
 
    void getDepartment(int id) override
    {
        std::cout << "( SQLServer ) get a data where department id is " << id << std::endl;
    }
};
 
class AccessDepartment : public IDepartment
{
public:
    void insert(Department dep) override
    {
        std::cout << "( Access ) insert a data departmentid: " << dep.m_id << " and departmentname: " << dep.m_name << std::endl;
    }
 
    void getDepartment(int id) override
    {
        std::cout << "( Access ) get a data where department id is " << id << std::endl;
    }
};

然后在DatabaseFactoryMethod抽象基类中增加虚方法createDepartment,并派生不同数据库工厂重写createDepartment

class IDatabaseFactory
{
public:
    virtual IUser* createUser() = 0;
    virtual IDepartment* createDepartment() = 0;
};
 
class MySQLFactory : public IDatabaseFactory
{
public:
    IUser* createUser()
    {
        return new MySQLUser();
    }
 
    IDepartment* createDepartment()
    {
        return new MySQLDepartment();
    }
};
 
class SQLServerFactory : public IDatabaseFactory
{
public:
    IUser* createUser()
    {
        return new SQLServerUser();
    }
 
    IDepartment* createDepartment()
    {
        return new SQLServerDepartment();
    }
};
 
class AccessFactory : public IDatabaseFactory
{
public:
    IUser* createUser()
    {
        return new AccessUser();
    }
 
    IDepartment* createDepartment()
    {
        return new AccessDepartment();
    }
 
};

可以看出在新增表的时候,IDatabaseFactory需要增加新的虚方法,而其子类也需要新增方法并重写,这也违背了开放—封闭原则。

但抽象工厂模式还是有两大优点:

易于交换产品系列,由于具体工厂类在应用中只需在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,只需要改变具体工厂即可使用不同的产品配置;
将具体的创建实例过程与客户端分离,客户端通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
                                                                                                                                                                      ——摘自《大话设计模式》
 

抽象工厂模式的结构图如下:

小结:

1. 工厂模式是面向对象开发的必须方法。因为对象不可能只有一个,会有很多个。

2. 当客户对产品的功能需要有差异时,就要考虑用工厂方法模式组件虚拟工厂,生产不同的产品。

3. 当客户对产品的组装有个性化需求时,就要继续对产品抽象生产虚拟产品。

总之,这些方法是循序渐进式的。

扩展:

AUTOSAR软件开发的方法论和软件架构本身就抽象工厂模式在汽车电子软件中的应用。把BCM,VCU,TCU,MCU等所有ECU的共性抽象出来,成为抽象ECU,组件大量的虚拟工厂,但只在少量的工厂内生产。

把相同产品的不同客户的的个性化需求抽象出来,通过简单配置,组装成性能丰富的具体产品,快速实现客户需求。

 最后希望大家能从文章中得到帮助获得收获,也可以评论出你想看哪方面的技术。文章会持续更新,希望能帮助到大家,哪怕是让你灵光一现。喜欢的朋友可以点点赞和关注,也可以分享出去让更多的人看见,一起努力一起进步!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值