文章目录

  • 案例:外设店铺
  • 简单工厂
  • 工厂方法
  • 抽象工厂
  • 总结
    • 要点
    • 三类工厂模式的特点
    • 三种工厂模式的适用场景
  • 完整代码与文档

工厂模式模式是创建型模式中较为常用的一个,它并不是一个模式,而是三种功能相近的设计模式的统称,它们分别是简单工厂模式、工厂方法模式、抽象工厂模式,下面我将结合案例来一一讲解它们的特点

案例:外设店铺

假设我们经营着一家外设店铺,我们主要售卖雷蛇和罗技这两个型号的鼠标,为了方便用户购买,我们设计了一个网上购物的平台,用户在网上下单后我们会去根据需求来生成鼠标,再经过测试、包装、注册信息后,就将合格的产品发送给客户。于是我们设计的代码如下

我们设计了一个鼠标类,当有新型号的鼠标发布时,只需要继承这个类即可9

class Mouse
{public:virtual ~Mouse() = default;void showMessage() const{std::cout << "鼠标名:" << _name << "\n" << std::endl;}virtual void test(){std::cout << "正在对鼠标进行测试...." << std::endl;}virtual void packing(){std::cout << "正在对鼠标处理...." << std::endl;}virtual void registerItem(){std::cout << "正在注册鼠标的商品信息...." << std::endl;}
protected:std::string _name;
};

我们还实现了一个店铺类,用来处理订单以及销售

class PeripheralStore
{public:Mouse* orderMouse(const std::string& type) //订购鼠标{Mouse* mouse;  //确定生产的型号if(type == "Logitech_G403"){mouse = new Logitech_G403;}else if(type == "Logitech_G502"){mouse = new Logitech_G502;}else if(type == "Razer_DeathAdder"){mouse = new Razer_DeathAdder;}else if(type == "Razer_Mamba"){mouse = new Razer_Mamba;}mouse->test();          //测试鼠标mouse->packing();       //包装鼠标mouse->registerItem();  //注册商品信息return mouse;           //发货}
};

但是随着产品的不断迭代以及我们的销量,我们售卖的产品时刻都会发生变化,因此我们就会经常来到这里对代码进行修改。

我们发现,在这段代码中我们需要改变的只有上面生产鼠标的部分,而下面对鼠标的测试、包装、注册都是固定的步骤,是不需要改变的,而我们将这两者放在一起,违反了我们封闭原则,可能会因为我们生产的操作不当,影响这些固定功能的发挥。那我们要如何做呢?接下来就到工厂模式发挥作用的时候了


简单工厂

为了将生产与产品处理分割开,我们可以构建出一个鼠标工厂类,将生产鼠标的任务委托给它,它生产完后把鼠标给我们,我们进行处理后即可销售

鼠标工厂类实现起来很简单,我们只需要将原来创建鼠标的代码迁移过去即可

class MouseFactory
{public:Mouse* createMouse(const std::string& type){Mouse* mouse;if(type == "Logitech_G403"){mouse = new Logitech_G403;}else if(type == "Logitech_G502"){mouse = new Logitech_G502;}else if(type == "Razer_DeathAdder"){mouse = new Razer_DeathAdder;}else if(type == "Razer_Mamba"){mouse = new Razer_Mamba;}return mouse;}
};

当有了工厂之后,为了将生产任务转交给工厂,店铺代码修改如下

class PeripheralStore
{public:Mouse* orderMouse(const std::string& type) //订购鼠标{Mouse* mouse;mouse = _factory.createMouse(type);  //让工厂生产鼠标mouse->test();          //测试鼠标mouse->packing();       //包装鼠标mouse->registerItem();  //注册商品信息return mouse;           //发货}
private:MouseFactory _factory;  //工厂对象,让其来负责鼠标的生产
};

此时店铺的类图如下

像这样通过工厂类创建对象,并且根据传入的参数决定具体子类对象的做法,就是简单工厂模式

有时候为了避免实例化工厂对象,我们会将创建对象的方法声明为静态的,所以简单工厂模式又被叫做静态工厂方法模式,但是这种方法也存在缺点,它不能通过继承来改变创建方法的行为


工厂方法

虽然我们实现了简单工厂模式,但是我们发现,如果我们新增或者减少鼠标的类型,我们就要去修改口罩工厂中的if-else判断,这不符合面向对象中的开放-封闭原则,这样对旧代码的修改不仅容易出错,可读性也不好,我们拓展起来也十分麻烦,如何来优化它呢?

在上面的代码中,一个工厂类需要负责对所有具体鼠标类的实例化,我们可以进行一个转变,让每个工厂只针对一种鼠标类的生产

我们可以将工厂抽象为一个接口,而为每一个鼠标型号都创建一个工厂子类,这些子类分别去实现工厂接口,如下

class IMouseFactory
{public:virtual Mouse* createMouse() = 0;
};class Logitech_G403_Factory : public IMouseFactory
{Mouse* createMouse() override{return new Logitech_G403;}
};class Razer_DeathAdder_Factory : public IMouseFactory
{public:Mouse* createMouse() override{return new Razer_DeathAdder;}
};

当我们需要新增一个种类的鼠标的时候,只需要继承并实现工厂接口即可

这样一来,我们就依靠面向对象中的多态,将每个工厂进行特化,当我们需要某一种类的鼠标时,只需要创建一个该类型的工厂并调用统一的接口,就可以得到这个类型的鼠标对象

int main()
{Logitech_G403_Factory* G403_factory = new Logitech_G403_Factory;Razer_DeathAdder_Factory* DeathAdder_factory = new Razer_DeathAdder_Factory;Mouse* m1 = G403_factory->createMouse();m1->showMessage();Mouse* m2 = DeathAdder_factory->createMouse();m2->showMessage();delete m2;delete m1;delete G403_factory;delete DeathAdder_factory;return 0;
}

测试结果如下

上面这种做法也就是工厂方法模式,其核心就是每一个产品都对应一个工厂之类,并利用多态特性动态创建对象

工厂方法模式定义了一个创建对象的接口,但由子类来决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类

此时的类图如下

从类图中可以看出,与简单工厂方法相比,工厂方法起到了解耦合的作用,此时的无论是增加还是删除产品,都不会对工厂接口造成影响,这样我们程序就更具有弹性,未来想拓展产品时只需要实现接口即可。

但是在实际中,考虑到工厂方法过于复杂的问题,如果在业务逻辑十分简单的情况下,我们没必要使用工厂方法模式,这时使用简单工厂模式更加简单、方便


抽象工厂

随着商店越做越大,我们已经不满足于鼠标这个种类,我们想在商店中引入耳机、手柄、键盘等商品。但是如果我们还是使用工厂方法,那就意味着我们还需要创建耳机工厂、手柄工厂、键盘工厂…并且根据它们的各种型号再次派生出大量的工厂类。

为了方便举例,这里假设每个种类的外设我们只卖一种型号 (太多了图放不下,而且不好举例)

如果每一个子类都对应一个工厂,那样不仅代码会越发繁琐,代码的维护也愈发艰难,所以此时就到了抽象工厂模式大展身手的时候了

我们不需要再为每一个产品分配上一个工厂,而是寻找它们之间的关联,将它们进行分组。对于上面的产品,我们可以发现主要就是雷蛇和罗技两个品牌,所以我们可以将它们按照品牌进行分组,建立罗技工厂和雷蛇工厂

所以我们根据品牌不同,抽象出一个品牌工厂接口,它提供了生产鼠标、键盘、耳机的接口

class IPeripheralFactory
{public:
virtual ~IPeripheralFactory() = default;
virtual Mouse* createMouse() = 0;
virtual Earphtones* createEarPhones () = 0;
virtual KeyBoard* createKeyBoard() = 0;
};

接着让雷蛇和罗技分别去实现这个接口

class LogitechFactory : public IPeripheralFactory
{Mouse* createMouse() override{return new Razer_DeathAdder;}Earphtones* createEarPhones () override{return new Razer_Mako;}KeyBoard* createKeyBoard() override{return new Razer_Huntsman;}
};class RazerFactory : public IPeripheralFactory
{Mouse* createMouse() override{return new Logitech_G502;}Earphtones* createEarPhones () override {return new Logitech_G443;}KeyBoard* createKeyBoard() override{return new Logitech_G913;}
};

下面写一个测试程序,分别生产一个罗技鼠标和一个雷蛇键盘

int main()
{IPeripheralFactory* logitech = new LogitechFactory;  //罗技工厂IPeripheralFactory* razer = new RazerFactory;        //雷蛇工厂Mouse* m1 = logitech->createMouse();m1->showMessage();KeyBoard* k1 = razer->createKeyBoard();k1->showMessage();delete k1;delete m1;delete logitech;delete razer;
}

通过抽象工厂,我们就可以将产品划分为几个大家族

当我们想要增加新种类时(例如加入手柄),就需要到抽象工厂接口以及每一个工厂类中添加一个新的方法。

说到这有人就会疑问,那么这不是不符合开放-封闭规则吗?确实,抽象工厂模式为了能够实现这样的分组,在这方面就做出了牺牲。

如果我们想对鼠标再进行一层细分,即想上面一样分为具体的型号,那就只需要对鼠标再实现一层的简单工厂或者工厂方法

并且我们还能发现,其实工厂方法早就以及潜伏在抽象工厂中

从上面的例子中我们可以得出,抽象工厂模式其实就是依据某个特点来将相关(依赖)的产品分组,组内不同的产品就对应同一个工厂类中的不同方法。通过这种分组就能大大减少类的数量

抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际产出的产品是什么,这样一来就使得客户从具体的产品中被解耦


总结

要点

  • 所有的工厂模式都是用来封装对象的创建
  • 所有工厂都通过减少应用程序和具体类之间的依赖来促进松耦合,更加具有弹性
  • 所有的工厂都是针对抽象编程而非针对具体类编程

三类工厂模式的特点

简单工厂模式

  • 简单工厂模式具有唯一的工厂类,通过对传入的参数做if-else判断来决定生产的对象

工厂方法模式

  • 工厂方法模式提供了一个工厂接口,由多个派生工厂类实现接口,利用继承以及多态来创建不同的产品对象,避免了大量的条件判断
  • 工厂方法将类的实例化推迟到了子类进行
  • 工厂方法中一个工厂对应着一种产品,导致类的数量过多

抽象工厂模式

  • 抽象工厂将具有关联(依赖)的产品进行分组,并且同组中的产品由同一个工厂子类的不同方法创建,大大减少了工厂类的数量
  • 抽象工厂通过对象组合的方式维护了一个产品家族

三种工厂模式的适用场景

  1. 当我们的对象创建的逻辑十分简单,并且可以将多个对象的创建逻辑放到一个工厂类时,就没必要大费周章,使用简单工厂模式即可
  2. 当对象创建逻辑复杂时,又或者是想将客户代码从具体类中解耦,又或者是我们并不知道未来还有哪些拓展的产品时,为了避免设计出一个庞大的简单工厂,我们会将创建逻辑细分,让每个类绑定一个工厂,这时就使用工厂方法模式
  3. 当需要创建产品家族,或者想让制造的相关产品集合起来时,就使用抽象工厂模式

完整代码与文档

如果有需要完整代码或者markdown文档的同学可以点击下面的github链接
github

趣谈设计模式 | 工厂模式(Factory):利用工厂来创建对象相关推荐

  1. 趣谈设计模式 | 状态模式(State):如何实现游戏中的状态切换?

    文章目录 案例:马里奥积分竞赛 有限状态机 分支逻辑法 查表法 状态模式 状态模式与策略模式 总结 完整代码与文档 案例:马里奥积分竞赛 喜欢马里奥的小伙伴们都应该知道,前不久马里奥为了庆祝35周年, ...

  2. 趣谈设计模式 | 模板方法模式(Template Method):封装不变部分,扩展可变部分

    文章目录 案例:房屋建造 模板方法模式 模板方法模式与策略模式 总结 完整代码与文档 这个设计模式过于简单,所以不是很好举例- 案例:房屋建造 假设我们是建筑公司中的规划者,负责设定建筑方案,在初期我 ...

  3. 趣谈设计模式 | 代理模式(Proxy):利用代理来控制对象的访问

    文章目录 案例:房屋中介 代理模式 代理模式与装饰器模式 代理模式的应用 远程代理 虚拟代理 安全代理 智能引用代理 写时拷贝代理 总结 完整代码与文档 由于代理模式相较于前面的其他设计模式来说更加简 ...

  4. 趣谈设计模式 | 策略模式(Strategy):你还在使用冗长的if-else吗?

    文章目录 案例:指挥官AI 策略模式 配合工厂模式 总结 完整代码与文档 案例:指挥官AI 案例可能不符合实际逻辑,仅用于表述设计模式的思想,勿介意 假设我们开发了一款类似全面战争的即时战略游戏,为了 ...

  5. 趣谈设计模式 | 桥接模式(Bridge):将抽象与实现分离

    文章目录 案例:跨平台应用开发 桥接模式 总结 完整代码与文档 案例:跨平台应用开发 A公司最近准备开发一组应用合集,目前包括了视频播放器.音乐播放器.文本播放器三种应用,但是在发售前,他们遇到了困难 ...

  6. 趣谈设计模式 | 外观模式(Facade):为子系统提供高粒度接口

    文章目录 案例:自动驾驶飞机 外观模式 总结 完整代码与文档 案例:自动驾驶飞机 随着自动驾驶汽车的大卖,特X拉开始把目标转向飞行领域,打算开发出一款能够完全自动行驶的飞机,系统初步的设计如下 我们将 ...

  7. 趣谈设计模式 | 命令模式(Command):将命令封装为对象

    文章目录 案例:智能遥控 命令模式 应用场景 队列请求 日志系统 总结 完整代码与文档 命令模式的应用场景较少,且不易理解,因此我也不好举例,所以下面的描述可能会存在一些问题,请见谅 案例:智能遥控 ...

  8. 设计模式(四)——工厂模式(Factory Pattern)

    工厂模式(Factory Pattern) 意义 工厂模式 实现了创建者和调用者的分离.将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦.从而提高项目的扩展和维护性 ...

  9. 设计模式(一)工厂模式Factory(创建型)

    设计模式一 工厂模式Factory 在面向对象编程中, 最通常的方法是一个new操作符产生一个对象实例,new操作符就是用来构造对象实例的.但是在一些情况下, new操作符直接生成对象会带来一些问题. ...

  10. 设计模式 - 学习笔记 - 工厂模式Factory Pattern

    设计模式 - 学习笔记 - 工厂模式Factory Pattern 1. 简单工厂 1.1 应用场景 1.2 UML 1.3 优劣分析 好处 缺点 1.4 代码示例 抽象产品 AbstractProd ...

最新文章

  1. Java生鲜电商平台-监控模块的设计与架构
  2. Python_基础知识储备
  3. java maven 没有target_Maven最全知识点总结 可以收藏啦
  4. ae抠像插件_AE抠像背景残留去除
  5. VS2005混合编译ARM汇编代码
  6. px,em,rem,vw单位在网页和移动端的应用
  7. SpringMVC的视图解析器
  8. 泛泰A880S升级官方4.4.2 binx教程
  9. 机器学习笔记(一)----基本概念
  10. LWIP源代码文件目录解析
  11. java多级菜单列表怎么做_JAVA构造多级菜单
  12. Java生成和操作Excel文件
  13. 关于多线程编程您不知道的5 件事---有关高性能线程处理的微妙之处 (转)
  14. 重新审视虚拟桌面存储
  15. Tenor 和numpy array 相互转换
  16. Alien Skin Exposure v6.x 最新通用完整版汉化补丁
  17. 小小粉刷匠(区间dp)
  18. 工具“正确”打开方式——如何用notion来谈恋爱
  19. Web课程设计高校物资管理系统
  20. CPT104-Operating Systems Concepts

热门文章

  1. 特定SQL的查询优化
  2. 计算机系统优化的目的和原理,优化原理
  3. JavaFX 中的像素、分辨率与缩放比
  4. alias用法和对当前用户的永久配置
  5. Kebernetes 学习总结(9)认证-授权-RBAC
  6. 一个小米SRE的日常问题排查记录
  7. 《数据结构与抽象:Java语言描述(原书第4版)》一练习
  8. ICC_lab总结——ICC_lab6:版图完成
  9. JAVA/JSP学习系列之Resin+Apache安装
  10. 关于Notes更改internet密码所需的缓存时间