深入浅出设计模式:工厂系列

在软件系统中,经常面临着创建对象的工作。由于需求的变化,需要创建的对象的具体类型经常变化。如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种"封装机制"来避免客户程序和这种"具体对象创建工作"的紧耦合?

创建型设计模式正是为了处理这类问题:

  • 简单工厂模式(Simple Factory)
  • 工厂方法模式(Factory Method)
  • 抽象工厂模式(Abstract Factory)
  • 建造者模式(Builder)
  • 原型模式(Prototype)

篇幅有限,这里简要介绍一下你问题中的三种模式。

简单工厂模式

简单工厂模式并不属于GOF定义的23种设计模式。它本质是一个静态方法,利用静态方法做一层函数抽象,将对象的创建与使用相分离。

它最大的作用是,把工程中相似的创建逻辑统一封装到Factory中,让代码更加内聚,避免创建蔓延。

优点

  • 将对象的使用与创建相分离,符合单一职责原则
  • 通过封装解决了代码重复、创建蔓延的问题
  • 降低客户程序获取对象的成本

缺点

  • 无法使用多态机制(静态方法无法重写),灵活性欠佳
  • 新增"Product类型"需要修改源码(增加if-else分支),不符合开闭原则
  • 多写一个工厂类(Factory并非实际业务需要,而是被设计用来承接"对象创建"职责,属于设计上的代价)

工厂方法模式

对于简单工厂模式而言,新增一个Product类就需要修改Factory类,不符合开闭原则。工厂方法模式就是为了解决这个问题。它的办法很简单,多态。但它不是直接让Product支持多态,而是抽象出一层Factory。

工厂方法的本质是,将Product的新增转换为Factory子类的新增,再利用多态消解if-else。

相比简单工厂,多了一层Factory抽象

这样做的好处是,将简单工厂模式的纵向扩展转变为横向扩展,而对于面向对象来说,横向扩展正是它的强项!

添加图片注释,不超过 140 字(可选)

从更高的角度看,工厂方法模式是非常符合SOLID中的依赖倒置原则的:

优点

  • 符合开闭原则(用增量的方式代替修改),新增子类型不需要修改现有的代码,具有很强的可扩展性。
  • 符合依赖倒置原则,依赖抽象可以让代码更稳定,同时对高层模块和低层模块进行解耦。

缺点

  • 在添加新的Product时,不仅要编写Product的具体实现,还要提供对应的Factory,系统中类的个数将成倍增加,增加了系统复杂度,给系统的编译和运行带来额外开销。
  • 抽象层的引入,增加了理解难度。

抽象工厂模式

工厂方法模式已经这么灵活了,抽象工厂模式还有什么改善的空间呢?

以DB查询为例。

通过引入IConnectionFactory和ICommandFactory,我们把Connection和Command的创建过程隔离到了Factory实现中,优先保证UserDao侧代码的稳定。同时,IConnectionFactory和ICommandFactory作为抽象层,将高层模块和低层模块的进行了解耦,方便后续其他数据库连接类型的接入。

但在这个案例中,工厂方法模式仍然存在不足之处:

  • 会产生较多的Factory(次要)
  • Connection和Command之间的关系较弱(重点)

什么叫Connection与Command之间的关系较弱?低耦合不正是我们的追求吗?确实,在实际开发中我们应该追求低耦合,但完整的说法是:低耦合,高内聚。对于Connection和Command而言,显然它们之间存在很强的对应关系,比如MysqlConnection只能搭配MysqlCommand,如果传入OracleCommand会导致数据库连接错误。从另一个角度来说,将Connection和Command拆分为两个Factory,其实是把"装配"的工作丢给了调用者,这无疑增加了调用者的心智负担,因为他们可能根本不知道如何组合使用这些对象。

为了让这种依赖关系更为内聚,抽象工厂模式出现了:

UserDao只需要依赖IDbFactory,而不是IConnectionFactory、ICommandFactory,后期要切换到Oracle,只需要把MySQLDbFactory换成OracleDbFactory,做到一键切换,而不是分别切换ConnectionFactory和CommandFactory。

抽象工厂模式强调的是配套地构建一系列相互依赖的对象,强调对象之间的关联性。此时,将这些方法拆分到不同类中反而不符合高内聚原则,甚至增加编写成本和使用者的心智负担。

工厂方法模式可以看做抽象工厂模式的一个特例:只有一个工厂方法的抽象工厂。

优点

  • 提供成套的接口创建对象。这一系列接口产生的对象属于同一产品族,通过切换抽象工厂便能很方便地切换产品族

缺点:

  • 不利于扩展新的产品类型

注意,这里说的是不利于增加新的产品类型,而不是工厂类型。

很容易增加新的工厂:小米家族

对于接口来说,新增createComputer是灾难性的

我们之所以要依赖抽象,就是因为它稳定。当一个原本稳定的东西发生了变化,带来的震动往往比原本变化的东西更加剧烈。这个变化就像往原本平静的湖中心丢入一颗小石子,激起的涟漪会一层层向外扩散,影响到周围的一切!所以,在使用抽象工厂模式之前,务必仔细辨别后续扩展的方向,到底是沿着横向扩展,还是纵向扩展。如果是纵向的,那就不适合使用抽象工厂模式。

设计模式是美妙的,但并非完美。如果你所谓的"完美",指的是"能够应对各种场景及其后续扩展",那么很遗憾,GOF 23种设计模式都是不完美的,它们都只是针对某个特定场景的解决方法。当我们需要扩展产品类型时,意味着已经不是之前的场景了,那么模式失效也就在所难免!这就是设计模式的倾向性。

别光收藏呀,点个赞朋友

相关推荐
沈询-阿里31 分钟前
java-智能识别车牌号_基于spring ai和开源国产大模型_qwen vl
java·开发语言
AaVictory.37 分钟前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
Yaml442 分钟前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
LuckyLay1 小时前
Spring学习笔记_27——@EnableLoadTimeWeaving
java·spring boot·spring
向阳12181 小时前
Dubbo负载均衡
java·运维·负载均衡·dubbo
Gu Gu Study1 小时前
【用Java学习数据结构系列】泛型上界与通配符上界
java·开发语言
小码编匠2 小时前
一款 C# 编写的神经网络计算图框架
后端·神经网络·c#
WaaTong2 小时前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
m0_743048442 小时前
初识Java EE和Spring Boot
java·java-ee