JS设计模式之“幽灵工厂” - 抽象工厂模式
image.png
一. 了解带头模范 - 抽象类
JavaScript
中并没有原生的抽象类的概念,但可以通过一些方式来模拟实现抽象类的效果。
抽象类是一种不能被直接实例化的类,只能作为其他类的基类使用。它定义了一组抽象方法,子类必须实现这些抽象方法。在其他面向对象语言中,抽象类可以用来提供一些通用的属性和方法,并约定具体子类的实现细节。
创建抽象类
在JavaScript
中,可以通过以下方式模拟抽象类的效果:
-
使用普通类模拟抽象类:
定义一个普通类,并在其中定义抽象方法,即方法只有方法签名而没有具体实现。子类必须继承这个普通类,并实现其中的抽象方法。如果子类没有实现抽象方法,将会抛出异常或提示错误。
class AbstractClass {
abstractMethod() {
throw new Error('抽象方法必须被实现');
}
}
class ConcreteClass extends AbstractClass {
abstractMethod() {
console.log('具体子类实现的方法');
}
}
const instance = new ConcreteClass();
instance.abstractMethod(); // 输出:具体子类实现的方法
-
使用ES6的Symbol模拟抽象类(不常用,仅供了解):
使用Symbol
定义抽象方法,在子类中使用与抽象方法相同名称的Symbol
定义具体实现。
const abstractMethod = Symbol('abstractMethod');
class AbstractClass {
[abstractMethod]() {
throw new Error('抽象方法必须被实现');
}
}
class ConcreteClass extends AbstractClass {
[abstractMethod]() {
console.log('具体子类实现的方法');
}
}
const instance = new ConcreteClass();
instance[abstractMethod](); // 输出:具体子类实现的方法
需要注意的是,这种方式只是通过约定和模式来实现抽象类的效果,并没有在JavaScript
语法层面上提供对抽象类的直接支持。
抽象类的作用
抽象类在面向对象编程中有以下几个主要作用:
-
提供一种约束和规范: 抽象类可以定义一些抽象方法,子类必须实现这些方法。这样可以约束子类在继承抽象类时必须按照一定的规范来实现这些方法,确保代码的一致性和可维护性。
-
封装通用的属性和方法: 抽象类可以包含一些通用的属性和方法,这样子类就可以继承这些属性和方法,避免了重复编写代码。通过继承抽象类,子类可以共享抽象类中已经实现的功能。
-
实现多态性: 抽象类可以作为多个具体子类的类型,通过抽象类可以实现多态性的特性。在编程中,可以通过抽象类来声明变量或参数的类型,然后在运行时根据实际的具体子类对象赋值给这些变量或参数实现不同子类对象的统一对待。
-
降低耦合度: 抽象类可以作为中间层,将具体实现和调用方进行解耦。调用方可以通过抽象类进行交互,而不必依赖具体的子类。这样可以提高代码的灵活性和可维护性,方便进行扩展和修改。
总结:抽象类在面向对象编程中起到了规范、封装、多态和解耦的作用,使得代码更加灵活、可扩展和可维护。
二. “幽灵工厂” - 抽象工厂模式
fileOf7174.png
了解了抽象类之后,我们下面进入正题,一听到工厂你应该会想到就是用来创建对象的,然而这个抽象工厂可不简单,抽象工厂本身不直接创建具体的对象,而是通过具体的工厂子类来创建一组相关的对象,因此被称为“幽灵工厂”,也可以称为“隐藏工厂”。
在JavaScript
中,抽象工厂模式也是一种创建对象的设计模式,它提供一个接口用于创建一系列相关对象的家族,而无需指定具体的类。
在传统的面向对象编程语言中,抽象工厂模式通常使用类和继承来实现。但在JavaScript
中,由于其动态性和灵活性,可以使用函数和原型继承来实现抽象工厂模式。
在JavaScript
中,抽象工厂模式通常由以下组件组成:
-
抽象工厂函数:由一个函数定义,并负责定义一组抽象产品对象的接口。它可以是一个普通的函数,也可以是一个构造函数。
-
具体工厂函数:由一个函数定义,并负责创建具体产品对象,实现了抽象工厂的接口。具体工厂函数通常使用原型继承或对象字面量来实现。
-
抽象产品对象:由一个普通对象或原型对象定义,并负责定义产品对象的接口。抽象产品对象通常定义了一组共同的方法或属性。
-
具体产品对象:由一个普通对象或原型对象定义,并实现了抽象产品对象的接口。具体产品对象通常是根据抽象产品对象定义的模板创建的具体实例。
抽象工厂模式的主要思路是通过抽象工厂函数定义的接口来创建具体的产品对象,这样可以实现对象的解耦和灵活性。客户端代码只需关注抽象工厂函数和抽象产品对象,从而实现了高度的可扩展性和可维护性。
类图 - 抽象工厂模式
参考以上抽象工厂模式的类图,下面以一个简单的JavaScript
抽象工厂模式的示例,让我们有一个初步的了解:
// 定义抽象工厂函数
function AbstractFactory() {}
// 定义抽象产品对象
AbstractFactory.prototype.createProduct = function() {
throw new Error('This is an abstract method.');
};
// 定义具体工厂函数
function ConcreteFactoryA() {}
ConcreteFactoryA.prototype = Object.create(AbstractFactory.prototype);
// 实现具体工厂的抽象产品创建方法
ConcreteFactoryA.prototype.createProduct = function() {
return {
name: 'Product A',
description: 'This is product A.'
};
};
// 定义具体工厂函数
function ConcreteFactory() {}
ConcreteFactoryB.prototype = Object.create(AbstractFactory.prototype);
// 实现具体工厂的抽象产品创建方法
ConcreteFactoryB.prototype.createProduct = function() {
return {
name: 'Product B',
description: 'This is product B.'
};
};
// 使用抽象工厂和具体工厂创建产品对象const factoryA = new ConcreteFactoryA();
const productA = factoryA.createProduct();
console.log(productA);
const factoryB = new ConcreteFactoryB();
const productB = factoryB.createProduct();
console.log(productB);
在此示例中,AbstractFactory
函数定义了一个抽象工厂的接口,而具体的工厂函数ConcreteFactoryA
和ConcreteFactoryB
别实现了抽象工厂的接口。每个体工厂函数都有一个createProduct方法来创建具体产品对象。客户端代码可以通过具体工厂来创建具体产品对象,而无需与具体产品对象直接交互,从而实现了对象的解耦和灵活性。
三. 深入实现抽象工厂模式
场景介绍
当以汽车为例来介绍JavaScript抽象工厂模式的实现时,我们可以假设有两种类型的汽车,即轿车(sedan)和越野车(SUV)。每种汽车类型都有不同的品牌,例如轿车可以是宝马(BMW)或奥迪(Audi),越野车可以是Jeep或福特(Ford)。
我们可以使用抽象工厂模式来实现这个示例:
// 定义抽象工厂函数
function CarFactory() {}
// 定义抽象产品对象
CarFactory.prototype.createCar = function() {
throw new Error('This is an abstract method.');
};
// 定义轿车工厂函数
function SedanFactory() {}
SedanFactory.prototype = Object.create(CarFactory.prototype);
// 实现轿车工厂的抽象产品创建方法
SedanFactory.prototype.createCar = function(brand) {
switch (brand) {
case 'BMW':
return new BMW();
case 'Audi':
return new Audi();
default:
throw new Error('Invalid car brand: ' + brand);
}
};
// 定义越野车工厂函数
function SUVFactory() {}
SUVFactory.prototype = Object.create(CarFactory.prototype);
// 实现越野车工厂的抽象产品创建方法
SUVFactory.prototype.createCar = function(brand) {
switch (brand) {
case 'Jeep':
return new Jeep();
case 'Ford':
return new Ford();
default:
throw new Error('Invalid car brand: ' + brand);
}
};
// 定义轿车类
function Sedan(brand) {
this.brand = brand;
}
Sedan.prototype = function() {
console('Driving sedan ' + this.brand);
};
// 定义越野车类
function SUV(brand) {
this.brand = brand;
}
SUV.prototype.drive = function() {
console.log('Driving SUV ' + this.brand);
};
// 定义具体轿车类
function BMW() {
Sedan.call(this, 'BMW');
}
BMW.prototype = Object.create(Sedan.prototype);
// 定义具体轿车类
function Audi() {
Sedan.call(this, 'Audi');
}
Audi.prototype = Object.create(Sedan.prototype);
// 定义具体越野车类
function Jeep() {
SUV.call(this, 'Jeep');
}
Jeep.prototype = Object.create(SUV.prototype);
// 定义具体越野车类
function Ford() {
SUV.call(this, 'Ford');
}
Ford.prototype = Object.create(SUV.prototype);
// 使用抽象工厂和具体工厂创建具体产品对象
const sedanFactory = new SedanFactory();
const suvFactory = new SUVFactory();
const sedan1 = sedanFactory.createCar('BMW');
const sedan2 = sedanFactory.createCar('Audi');
const suv1 = suvFactory.createCar('Jeep');
const suv2 = suvFactory.createCar('Ford');
sedan1.drive(); // 输出:Driving sedan BMW
sedan2.drive(); // 输出:Driving sedan Audi
suv1.drive(); // 输出:Driving SUV
suv2.drive(); // 输出:Driving SUV Ford
在此示例中,我们定义了抽象工厂函数CarFactory
,它有一个createCar
的抽象方法。然后,我们创建了SedanFactory
和SUVFactory
作为具体工厂函数,分别实现了CarFactory
的抽象方法,用于创建轿车和越野车对象。
为了具体化每个汽车类型,我们定义了Sedan
和SUV
类,并通过继承关系创建了具体轿车类(例如BMW
和Audi
)以及具体越野车类(例如Jeep
和Ford
)。
最后,我们使用具体工厂函数创建了具体汽车对象,然后使用drive
方法演示了它们的行驶功能。
这样,通过抽象工厂模式,我们可以轻松地扩展汽车的类型和品牌,而不会影响现有的客户端代码,并且实现了汽车类型和品牌的解耦。
四. 对抽象工厂模式的理解
抽象工厂模式是一种使用工厂对象来创建一系列相关对象的设计模式。它通过提供一个共同的接口来创建一组相关或相互依赖的对象,而不需要指定具体的类。
抽象工厂模式的核心思想是将对象的创建与使用分离开来。通过抽象工厂函数定义一个接口,具体工厂函数实现这个接口,并负责创建具体的产品对象。客户端代码只与抽象工厂函数和抽象产品对象进行交互,而不需要了解具体的产品对象是如何创建的。
抽象工厂模式的优点之一是它能够提供灵活的对象创建机制。通过定义不同的具体工厂函数,我们可以根据需求创建不同类型或变种的产品对象,而无需修改原有代码。抽象工厂模式还可以隐藏具体产品对象的实现细节,只提供一个统一的接口给客户端使用。
抽象工厂模式适用于以下情况:
-
当有多个相关的对象需要创建,并且这些对象之间有一定的约束关系时,可以使用抽象工厂模式统一创建这些对象。
-
当希望通过一个统一的接来创建一组相关对象时,可以使用抽象工厂模式。
不过,抽象工厂模式也有一些限制和注意事项:
-
抽象工厂模式增加了系统的复杂性,因为它引入了多个抽象类和多个具体类。
-
当需要新增一种产品时,需要同时修改抽象工厂接口和具体工厂实现,这可能会影响到系统的稳定性。
-
抽象工厂模式难以支持新类型的产品的变化,因为它的设计侧重于一类产品。
在实际使用抽象工厂模式的过程中,我们需要根据具体的业务需求和系统架构来判断是否适合采用。它可以帮助我们实现代码的解耦和灵活性,但也需要权衡其可能引入的复杂性和代码的维护成本。
五. 工厂模式三部曲对比
如果需要对“简单工厂模式”和“工厂方法模式”详细的了解,请参考本人之前文章学习,如下:
JS设计模式之“神奇的魔术师” - 简单工厂模式
JS设计模式之“名片设计师” - 工厂方法模式
简单来说:简单工厂模式、工厂方法模式、抽象工厂模式是三种常见的创建型设计模式。它们都用于创建对象,但在实现方式和适用场景上有一些差异。
相同点
-
都属于创建型设计模式,旨在解耦对象的和使用。
-
都通过一个工厂对象/函数创建对象,从而避免了直接使用new操作符来创建对象。
异同点
设计模式 | 异同点 | 适用场景 |
---|---|---|
简单工厂模式 (Simple Factory Pattern | 通过一个一个的工厂类(即简单工厂)来创建所有的产品对象。只需要调用工厂类的静态方法,传入相应的参数即可获取特定类型的对象。 | 简单工厂模式适用于创建数量不多,且对象类型相对简单的情况。 |
工厂方法模式 (Factory Pattern) | 将对象的创建延迟到具体的子类工厂中,具体的子工厂实现这个方法来创建不同类型的对象。每个具体的子类工厂负责创建一种具体的产品对象。 | 工厂方法模式适于需要创建多个类型产品,且每个产品可能有不同的逻辑。 |
抽象工厂模式 (Abstract Factory Pattern) | 抽象工厂定义一个接口,该接口声明了一组可以创建不同类型对象的抽象方法。具体的工厂则实现这个接口,并负责创建一组相关的具体产品对象,通过抽象工厂的方法来创建不同类型的体产品对象。 | 抽象工厂模式适用于需要创建一组相关产品对象,而且这些产品对象彼此之间有一定约束关系。 |
关系
-
简单工厂模式是创建型设计模式的基础,它通过一个共同的工厂类来创建所有的产品对象,只需通过传入不同的参数来获得不同类型的对象。它属于静态工厂模式。
-
工厂方法模式扩展了简单工厂模式,它引入了一个抽象的工厂类和具体的工厂子类。具体的工厂子类实现该方法来创建特定类型的产品对象。
-
抽象工厂模式在工厂方法模式的基础上进一步扩展,它提供了一种组织一组相关或相互依赖的工厂类的方式。具体的工厂类实现了抽象工厂接口,通过与抽象工厂接口交互,无需直接调用具体的工厂类,从而实现了一种高层次的对象创建。
总结
简单工厂模式是创建型模式的基础,工厂方法模式通过引入抽象工厂类和具体工厂子类来实现对象的可定制性,而抽象工厂模式进一步扩展了工厂方法模式,提供了一种组织一组相关工厂类的方式,用于创建相关或相互依赖的对象。