工厂模式
在现实生活中,工厂是负责生产产品的,比如早上吃的面包, 但是如果我们从和面开始制作一个面包时间成本就会很高。 所以我们更倾向于直接去买一个,而为什么工厂就能生产效率这么高呢? 很显然工厂,有专门生产面包的机器,我们只需要按照我们的要求设定就能得到我们想要的面包。 而在程序中,工厂模式就是生产我们需要的对象,直接提供给你面包,而不是需要你自己制作。
分类
工厂模式分为两种
- 工厂方法模式
- 抽象工厂模式
工厂方法模式
定义
工厂方法模式是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。
工厂方法是一种创建型设计模式, 解决了在不指定具体类的情况下创建产品对象的问题。
适合场景
-
当你在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。 工厂方法将创建产品的代码与实际使用产品的代码分离, 从而能在不影响其他代码的情况下扩展产品创建部分代码。 例如,你的面包需要满足不同口味的客户。而你需要制作的面包是未知的。 当客户需要新的手撕面包时候,你就可以创建一个手新的手撕面包子类,并重写工厂方法即可。
-
如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。 例如:用户说你的面包口味太淡了,而且吃不饱,需要加一根烤肠。 你直接重写面包制作方法,加上一根烤肠即可。
工厂方法模式优缺点
优点
- 你可以避免创建者和具体产品之间的紧密耦合。
- 单一职责原则。 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。
- 开闭原则。 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。
缺点
- 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。
与简单工厂模式的区别
简单工厂模式 描述了一个类, 它拥有一个包含大量条件语句的构建方法, 可根据方法的参数来选择对何种产品进行初始化并将其返回。
人们通常会将简单工厂与普通的工厂或其它创建型设计模式混淆。 在绝大多数情况下, 简单工厂是引入工厂方法或抽象工厂模式时的一个中间步骤。
简单工厂通常没有子类。 但当从一个简单工厂中抽取出子类后, 它看上去就会更像经典的工厂方法模式了。 顺便提一句, 如果你将一个简单工厂声明为 abstract 类型, 它并不会神奇地变成抽象工厂模式。
代码
问题:假设你正在开发一款物流管理应用。 最初版本只能处理火车运输, 因此大部分代码都在位于名为 火车 的类中。一段时间后,广受好评,开拓了海外市场,开始使用轮船运输
/**
* 运输工具
*/
interface Transport {
// 必须有 work 方法,让资本家剥削
work(): void;
}
abstract class Creator {
public abstract createMethod(): Transport;
}
// 造船工厂
class ShipFactory implements Creator {
createMethod() {
return new Ship()
}
}
// 造火车工厂
class TrainFactory implements Creator {
createMethod() {
return new Train()
}
}
const trainFactory = new TrainFactory()
const shipFactory = new ShipFactory()
// '造个火车来拉货'
const train = trainFactory.createMethod()
train.work()
// 需要运到国外了
// 造个轮船来拉货
const ship = shipFactory.createMethod()
ship.work()
// 运输工具之 火车
class Train implements Transport {
work() {
// 如果需要在 火车 工作前假如一些其它工作,即可在此修改而不用修改 创建者 或者 具体产品
// 例如工作前先检查 火车 是否能够正常工作
// console.log('一切正常!我要工作了~')
console.log('咣哧 咣哧 咣哧 咣哧! 开始工作了')
}
}
// 运输工具之 轮船
class Ship implements Transport {
work() {
console.log('呜~~ 呜~~ 呜~~! 开始工作了')
}
}
抽象工厂模式
定义
抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。
例如:当你需要为你的新家购置家具时,通常桌椅是配套售卖的,这样能确保风格和大小匹配。 但是工厂在生产桌椅时却是分开生产的,这样能提高生产效率。 而为了确保最后得到的桌椅大小风格都是配套的,我们需要抽象一个能生产桌椅的类。
抽象工厂模式适合应用场景
-
如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。
-
如果代码需要与多个不同系列的相关产品交互,但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。
抽象工厂模式优缺点
优点
- 你可以确保同一工厂生成的产品相互匹配。
- 你可以避免客户端和具体产品代码的耦合。
- 单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
- 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。
缺点
- 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。
与其他模式的关系
- 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。
- 生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。
- 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。
- 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂来代替外观模式。
- 你可以将抽象工厂和桥接模式搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。
- 抽象工厂、 生成器和原型都可以用单例模式来实现。
代码
问题:假设你正在开发一款家具商店模拟器:需要一系列相关产品, 例如桌子、椅子;需要不同风格如大气的中式、精美的欧式。 解决:首先, 抽象工厂模式建议为系列中的每件产品明确声明接口 (例如桌子、椅子)。 然后, 确保所有产品变体都继承这些接口。 例如, 所有风格的桌子都实现 桌子接口; 所有风格的椅子都实现 椅子 接口, 以此类推。
interface AbstractFurniture {
createDesk(): Desk;
createChair(): Chair;
}
class ChineseFurniture {
createDesk() {
return new ChineseDesk()
}
createChair() {
return new ChineseChair()
}
}
class EuropeFurniture {
createDesk() {
return new EuropeDesk()
}
createChair() {
return new EuropeChair()
}
}
const chineseFurniture = new ChineseFurniture()
const europeFurniture = new EuropeFurniture()
// 来一套中式家具
chineseFurniture.createDesk()
chineseFurniture.createChair()
// 来一套欧式家具
europeFurniture.createDesk()
europeFurniture.createChair()
interface Desk {
// 桌子能放东西
putOn(): void;
}
interface Chair {
// 椅子能坐人
sit(): void;
}
class ChineseDesk implements Desk {
putOn() {
console.log('中国传统桌子很庄重大气')
}
}
class ChineseChair implements Chair {
sit() {
console.log('中国传统椅子很简约实用')
}
}
class EuropeDesk implements Desk {
putOn() {
console.log('欧洲传统桌子很时尚奢侈')
}
}
class EuropeChair implements Chair {
sit() {
console.log('欧洲传统椅子很精致华丽')
}
}