前言
创建型设计模式用于描述“怎么创建对象”。它的主要特点是“将对象的创建与使用分离”。
前端创建型设计模式用到的有:构造器模式
、工厂模式
、抽象工厂模式
一、构造器模式
构造函数想必就是一种构造器如:
function f(name,age,career) {
this.name=name;
this.age=age;
this.career=career;
}
const user=new f('张三',23,'coder');
只用传给构造函数对应的值,就可以创建出一个实例对象出来
总的来说:像这样一个新建对象分配内存后,用来初始化对象的特殊函数,叫做构造器。在JS中,我们通过构造函数去初始化对象,就是应用了构造器模式。
在整个过程中,改变的是属性的值,而这些属性则是固定不变的。
构造器将属性赋值给对象的过程给封装了,使每个对象都具有这些属性,这一部分不可更改使其稳定,而属性赋值的过程开放,可以灵活的给属性赋值。
构造器模式本质是去抽象每个对象实例的变与不变
二、简单工厂模式
工厂模式本质上是抽象不同构造函数(类)之间的变与不变,他将创建对象的过程单独封装。
当不同类中属性相同,但其中的属性会受到其它属性的值而产生对应的值,就比如一个教室中,有学生和老师两种类型,学生类中包含年龄、姓名、职位、工作组成;而老师类也是年龄、姓名、职位、工作组成;但工作会因为职位的值,自动匹配工作的值,学生:学习,老师:授课。
function student(name,age){
this.name=name;
this.age=age;
this.position='学生';
this.work='学习';
}
function teacher(name,age) {
this.name=name;
this.age=age;
this.position='老师';
this.work='授课';
}
const student1=new student('张三',23);
const student2=new student('李四',23);
const student3=new student('王五',23);
const teacher=new student('李',23);
这样一个教室的角色就可以被构造出来实例,但会发现有个问题就是,得到一条数据后,还得手动判断职业,才能选择对用的构造函数构建。这部分的变可以给一个大函数来管理。
function roleManagement(name,age,career) {
switch (career) {
case '学生':
return new student(name,age);
case '老师':
return new teacher(name,age);
...
}
}
这样判断职业的事交给了
roleManagement
函数管理,但是又有个问题就是假如有一个听课老师,那是不是又得写一个构造函数来构造这个听课老师,roleManagement
中又得添加一个判断,又来一个校长又得写一个,显然是乏力的。
可以想到在这两个类中共同的部分依旧是那四个属性,改变的是每个属性的取值与work属性需要根据position属性值来判断,我们何不将 不变的构造函数和,判断改变的逻辑封装在同一个方法中,让他自己判断,从而构建出不同的实例对象。
function gather(name,age,position) {
function User(name,age,position,work) {
this.name = name
this.age = age
this.position = position
this.work = work
}
function roleManagement(name,age,position) {
switch (position) {
case '学生':
work='学习'
break
case '老师':
work='授课'
break
...
}
}
return new User(name,age,position,work);
}
const student5=new gather('张三',23,'学生');
const teacher2=new gather('李老师',32,'老师');
现在就只用传入姓名、年龄、职业就可以创建出对应的实例拉。
总的说,工厂模式就是把对象的创建过程封装,只给用户提供传参和结果
三、抽象工厂模式
对于构造器模式解决的是多个对象实例的问题,而简单工厂模式解决的是多个类的问题。抽象工厂解决的则是多个工厂的共存。
在JS的语法中,不支持抽象类的直接实现,只能考模拟还原抽象类。
有以下场景,在之前老师和学生的基础上,又有一个角色类型,但是他的属性完全和老师和学生不同,他多了其它的属性权限,这时候按照刚刚的逻辑又得在
roleManagement
中添加关于这个新角色的相关权限逻辑。这使得整个roleManagement
在项目的迭代更新中变的越来越庞大,不易于维护。这样显然是没有遵循开放封闭原则的。
function gather(name,age,position) {
function User(name,age,position,work) {
this.name = name
this.age = age
this.position = position
this.work = work
}
function roleManagement(name,age,position) {
switch (position) {
case '学生':
work='学习'
break
case '老师':
work='授课'
break
case '新角色':
work='发工资'
newPrivileges='管理老师'
break
...
}
}
return new User(name,age,position,work);
}
对于开放粉笔原则:我们要对拓展开放,对修改封闭。也就是说实体(类、模块、函数)可以拓展,不可以修改。
抽象工厂模式会创建一个定义规则抽象工厂类,这个类只会提供具体工厂的接口,可以通过这个接口来扩展具体工厂的逻辑内容。就比如生产汽车,你不知道具体需要生产什么样的汽车,但你知道生产汽车需要发动机、车架、轮胎、玻璃…,这时候就可以创建出一个抽象类表示这辆车需要什么,但具体需要什么样的部件得靠具体工厂提供。
class automobileProduction {
// 提供发动机的接口
createTheEngine(){
throw new Error("抽象工厂方法不允许被直接调用,需要重写该方法!")
}
//提供车架的接口
createTheFrame(){
throw new Error("抽象工厂方法不允许被直接调用,需要重写该方法!")
}
}
值得注意的是抽象工厂是不能被实例化的,包括里边的方法是不能被调用的,如果能被调用那不就是修改嘛,我们需要的是扩展而不是修改。
那如何去生产一台车呢,这时候需要用具体工厂来生产需要的型号,也就是定制一个特定具体工厂:
//具体工厂继承抽象工厂
class gallop extends automobileProduction{
creatingEngine() {
//提供奔驰的发动机
return new MercedesBenzEngine();
}
createTheFrame(){
//提供奔驰车架
return new MercedesBenzChassis();
}
}
在具体工厂中调用的构造函数
MercedesBenzEngine
、MercedesBenzChassis
,分别用于生产具体的发动机和车架实例,这样的类称为具体产品类,具体产品类往往不会孤立存在,而不同的具体产品类往往又相同的功能,比如:奔驰发动机类和大众发动机类都有给汽车带来动力。所以这里可以用一个抽象产品类来声明这些类具有的基本功能,就像最开始汽车生产抽象类一样声明了汽车生产的基本功能。
//具体工厂继承抽象工厂
class gallop extends automobileProduction{
creatingEngine() {
//提供奔驰的发动机
return new MercedesBenzEngine();
}
createFrame(){
//提供奔驰车架
return new MercedesBenzChassis();
}
}
//定义发动机类的抽象产品类
class engine {
createTheEngine(){
throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
}
}
//定义车架类的抽象产品类
class frame {
createFrame(){
throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
}
}
//定义具体发动机的具体产品类
class MercedesBenzEngine extends engine{
createTheEngine(){
console.log("奔驰发动机")
}
}
//定义具体车架的具体产品类
class MercedesBenzChassis extends frame{
createFrame(){
console.log("奔驰车架")
}
}
//我的车
const car=new gallop();
// 选则发动机
const myEngine=new MercedesBenzEngine();
//选择车架
const myFrame=new MercedesBenzChassis();
//组装发动机
myEngine.createTheEngine();
//组装车架
myFrame.createFrame();
最终组装出来一辆奔驰车
假设后来我又想生产大众车了,这时候我们不用对抽象工厂做任何改变,只需要拓展他的具体工厂和具体产品的种类就行了。这就完美的实现了拓展。
总结
对于简单工厂和抽象工厂模式来看:
共同点是:尝试去分离一个系统中变与不变的部分
不同点:场景的复杂度
在简单工厂的使用场景里,处理的对象是类,并且是一些非常好对付的类——它们的共性容易抽离,同时因为逻辑本身比较简单,故而不苛求代码可扩展性。抽象工厂本质上处理的其实也是类,但是是一帮非常棘手、繁杂的类,这些类中不仅能划分出门派,还能划分出等级,同时存在着千变万化的扩展可能性——这使得我们必须对共性作更特别的处理、使用抽象类去降低扩展的成本,同时需要对类的性质作划分。