在软件系统中,经常面临着创建对象的工作。由于需求的变化,需要创建的对象的具体类型经常变化。如何应对这种变化?如何绕过常规的对象创建方法(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种设计模式都是不完美的,它们都只是针对某个特定场景的解决方法。当我们需要扩展产品类型时,意味着已经不是之前的场景了,那么模式失效也就在所难免!这就是设计模式的倾向性。
别光收藏呀,点个赞朋友