问题
简单工厂中我们通过参数来返回不同的产品对象,如果管理的对象过多,这个工厂函数会比较庞大,且当我们需要增加一个新的产品时,需要修改这个工厂方法,违反开闭原则(对拓展开放,对修改关闭)。
为了解决简单工厂模式的问题,出现了工厂方法模式。
解决简单工厂思路
简单工厂类图关系类似如下:
如上图当我们需要增加一个产品时,我们需要修改create 方法增加相关判断的逻辑,这不符合开闭原则。
假如一个工厂只负责他自己的产品,那么新增产品也只是新增一个工厂以及对应的产品,那么简单工厂的问题是不是也解决了。
总结:将对象的创建与实例化延迟到子类,这样的工厂设计就符合“开闭原则”了,扩展时不必去修改原来的代码。
工厂方法
如上图当我们增加一个产品时,只需要增加一个工厂(实现抽象类),然后在各自的工厂返回自己需要的产品即可;
工厂方法由4个要素组成:
抽象工厂 AbstractFactory: 这个是工厂方法模式的核心,定义用来生产对应产品的方法,是具体工厂必须实现的接口或者必须继承的父类,通常使用中它由抽象类或者接口来实现。
具体工厂 Factory:被应用程序调用以创建具体产品的对象,含有和具体业务逻辑有关的代码。
抽象产品 AbstractProduct:把产品共有的特征抽象,通常使用中它由抽象类或者接口来实现。
具体产品 Product:具体工厂所创建的产品(对象)。
如果只是接触了js,可能对抽象不太了解 ,其实抽象只是一种思考的问题的方式。
抽象的定义是从众多的事物中抽取出共同的、本质性的特征,而舍弃其非本质的特征的过程。
简单的理解其实就是共有的特征、特性,比如我们要规范人这个对象具有的特点,就用一个接口或者抽象类声明一些共有的属性、方法,具体的子类在实现这个接口或者抽象类,这样子类也有这些属性、方法。
这样做的好处就是约束子类,保证子类都有这些属性、方法,不会存在乱七八糟的对象,这也符合我们大脑工作方式。
实现
工厂方法是将实际创建对象的工作推迟到子类中,这样核心类就变成了抽象类。但是在JavaScript中很难像传统面向对象那样去实现创建抽象类。所以在JavaScript中我们只需要参考它的核心思想即可。我们可以将工厂方法看作是一个实例化对象的工厂类。虽然ES6也没有实现abstract,但是我们可以使用new.target来模拟出抽象类。new.target指向直接被new执行的构造函数,我们对new.target进行判断,如果指向了该类则抛出错误来使得该类成为抽象类。
ts 扩展了js,所以这里我用ts 来模拟
/*
工厂方法是简单工厂的优化版本,在之前把生成具体产品的实例逻辑都写在了工厂方法中,
导致增加一个产品,这个工厂方法需要修改。
所以工厂方法主要是把这一部分功能独立出来了,一个工厂负责一个产品
工厂方法核心结构有四个角色
1、抽象工厂
2、具体工厂
3、抽象产品
4、具体产品
因为js不支持没有抽象类这里用ts 代替,只要符合核心思想即可
*/
// 抽象工厂
abstract class CardFactor{
abstract getCardInstance(name:string, time:string):Card
}
// 抽象产品
abstract class Card {
carName: string;
time: string;
abstract getCarDesc():void
}
// 具体产品-BydCar
class BydCar extends Card {
constructor (name, time) {
super()
this.carName = name
this.time = time
}
runCarName () {
console.log(this.carName)
}
getCarDesc () {
console.log(`我是${this.carName},生产于:${this.time}`)
}
}
// 具体产品-JeepCar
class JeepCar extends Card {
constructor (name, time) {
super()
this.carName = name
this.time = time
}
runCarName () {
console.log(this.carName)
}
getCarDesc () {
console.log(`我是${this.carName},生产于:${this.time}`)
}
}
// 具体产品-BenzCar
class BenzCar extends Card {
carName: string
time: string
constructor (name, time) {
super()
this.carName = name
this.time = time
}
runCarName () {
console.log(this.carName)
}
getCarDesc () {
console.log(`我是${this.carName},生产于:${this.time}`)
}
}
// 具体的工厂-BydCarFactory
class BydCarFactory extends CardFactor {
getCardInstance (name, time) {
return new BydCar(name, time)
}
}
// 具体的工厂-JeepCarFactory
class JeepCarFactory extends CardFactor{
getCardInstance (name, time) {
return new JeepCar(name, time)
}
}
// 具体的工厂-BenzCarFactory
class BenzCarFactory extends CardFactor{
getCardInstance (name, time) {
return new BenzCar(name, time)
}
}
// test 直接使用具体的工厂
const bydCar = new BydCarFactory().getCardInstance('byd 汽车', '2022-08-20')
const jeepCar = new JeepCarFactory().getCardInstance('jeep 汽车', '2022-08-21')
const benzCar = new BenzCarFactory().getCardInstance('benz 汽车', '2022-08-22')
bydCar.runCarName()
bydCar.getCarDesc()
jeepCar.runCarName()
jeepCar.getCarDesc()
benzCar.runCarName()
benzCar.getCarDesc()
如果我们现在需要增加一种车型,那我们只需增加对应的工厂,跟产品类即可,我们对原有的工厂不会造成任何影响 所谓的“对拓展开放,对修改封闭”就这么圆满实现了。
问题
在工厂方法模式中,我们使用一个工厂创建一个产品,一个具体工厂对应一个具体产品,但有时候我们需要一个工厂能够提供多个产品对象,而不是单一的对象,这个时候我们就需要使用抽象工厂模式。
上面代码中我们的比亚迪工厂只是生产一种产品,现实是一个工厂大概率是要生产很多种不同的产品,如比亚迪有汉dmi、dmp、唐suv等车型。这种一个工厂需要多种产品的时候,按照工厂方法的思路,我们需要写很多个这种工厂类,这也非常繁琐。
抽象工厂
我们需要的是一个工厂可以生产多种不同的产品对象,按照设计模式的思路把变化的抽出来封装。变的是一个工厂可以支持多个产品对象,不变的依旧是在工厂中拿到产品对象。按照工厂方法的思路,只需要在抽象工厂中增加创建对应的产品即可,以之前汽车的例子把工厂方法改成抽象工厂,只要把抽象工厂中增加对应的获取产品,修改之后现在比亚迪工厂支持默认车型,汉车型,suv车型;
/*
抽象工厂:一个工厂可以生产多种不同的产品,它思路跟工厂方法基本一致。
抽象工厂核心结构有四个角色
1、抽象工厂
2、具体工厂
3、抽象产品
4、具体产品
因为js不支持没有抽象类这里用ts 代替,只要符合核心思想即可
*/
// 抽象工厂
abstract class CardFactor{
// 默认的车型
abstract getCardInstance(name:string, time:string):Card
// 汉车型
abstract getCardHanDmiInstance(name:string, time:string):Card
// suv 车型
abstract getCardSuvInstance(name:string, time:string):Card
}
// 抽象产品,同理:这里也可以把产品进一步抽象,如汉的抽象产品,suv的抽象产品,这里不在调整
abstract class Card {
carName: string;
time: string;
abstract getCarDesc():void
}
// 具体产品-BydCar
class BydCar extends Card {
constructor (name, time) {
super()
this.carName = name
this.time = time
}
runCarName () {
console.log(this.carName)
}
getCarDesc () {
console.log(`我是${this.carName},生产于:${this.time}`)
}
}
// 具体产品-BydHanCar
class BydHanCar extends Card {
name = '中大型轿车汉'
desc = '这是来自比亚迪的汉'
constructor () {
super()
}
runCarName () {
console.log(this.carName)
}
getCarDesc () {
console.log(`我是${this.carName},生产于:${this.time}`)
}
}
// 具体产品-BydSuvCar
class BydSuvCar extends Card {
name = 'suv'
desc = '这是来自比亚迪的suv'
constructor () {
super()
}
runCarName () {
console.log(this.carName)
}
getCarDesc () {
console.log(`我是${this.carName},生产于:${this.time}`)
}
}
// 具体的工厂-BydCarFactory
class BydCarFactory extends CardFactor {
// 生产默认的byd 车型
getCardInstance (name, time) {
return new BydCar(name, time)
}
// 生产byd汉车型
getCardHanDmiInstance () {
return new BydHanCar()
}
// 生产byd suv车型
getCardSuvInstance () {
return new BydSuvCar()
}
}
// test 直接使用具体的工厂
const bydCarFactory = new BydCarFactory()
// 现在这个工厂具备生产三种车的产品了
const bydCar = bydCarFactory.getCardInstance('默认车型', '2023-3-7');
const hanCar = bydCarFactory.getCardHanDmiInstance();
const suvCar = bydCarFactory.getCardSuvInstance();
抽象工厂模式与工厂方法模式区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。
抽象工厂模式同工厂方法模式一样,也是由4 个要素构成,但抽象工厂中方法个数不同,抽象产品的个数也不同。
- AbstractFactory:抽象工厂
- ConcreteFactory:具体工厂
- AbstractProduct:抽象产品
- Product:具体产品
这种管理多个产品结构的抽象类,就是抽象工厂模式,让一个工厂具备管理多个产品结构。
抽象工厂模式相对于工厂方法模式来说,工厂方法模式是针对一个产品系列的,而抽象工厂模式是针对多个产品系列的,即工厂方法模式是一个产品系列一个工厂类,而抽象工厂模式是多个产品系列一个工厂类。
定义:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
工厂方法与抽象工厂
抽象工厂模式与工厂方法模式区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂结构可以负责多个不同产品等级结构中的产品对象的创建 。
(1)工厂方法只有一个抽象产品类和一个抽象工厂类,可以派生出多个具体产品类和具体工厂类,每个具体工厂类只能创建一个具体产品类的实例。
(2)抽象工厂模式拥有多个抽象产品类(产品族)和一个抽象工厂类,每个抽象产品类可以派生出多个具体产品类;抽象工厂类也可以派生出多个具体工厂类,同时每个具体工厂类可以创建多个具体产品类的实例。
小结
- 工厂方法是一个工厂只生产一种产品,类似极端情况下的抽象工厂模式(即只生产一种产品的抽象工厂模式)
- 抽象工厂是一个工厂可以生产多种同类的产品(产品族)