JavaScript 设计模式核心原理与应用实践(二)创建型


前言

创建型设计模式用于描述“怎么创建对象”。它的主要特点是“将对象的创建与使用分离”。

前端创建型设计模式用到的有:构造器模式工厂模式抽象工厂模式


一、构造器模式

构造函数想必就是一种构造器如:

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();
    }
}

在具体工厂中调用的构造函数MercedesBenzEngineMercedesBenzChassis,分别用于生产具体的发动机和车架实例,这样的类称为具体产品类,具体产品类往往不会孤立存在,而不同的具体产品类往往又相同的功能,比如:奔驰发动机类和大众发动机类都有给汽车带来动力。所以这里可以用一个抽象产品类来声明这些类具有的基本功能,就像最开始汽车生产抽象类一样声明了汽车生产的基本功能。

//具体工厂继承抽象工厂
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();

最终组装出来一辆奔驰车
在这里插入图片描述
假设后来我又想生产大众车了,这时候我们不用对抽象工厂做任何改变,只需要拓展他的具体工厂和具体产品的种类就行了。这就完美的实现了拓展。


总结

对于简单工厂和抽象工厂模式来看:
共同点是:尝试去分离一个系统中变与不变的部分
不同点:场景的复杂度

在简单工厂的使用场景里,处理的对象是类,并且是一些非常好对付的类——它们的共性容易抽离,同时因为逻辑本身比较简单,故而不苛求代码可扩展性。抽象工厂本质上处理的其实也是类,但是是一帮非常棘手、繁杂的类,这些类中不仅能划分出门派,还能划分出等级,同时存在着千变万化的扩展可能性——这使得我们必须对共性作更特别的处理、使用抽象类去降低扩展的成本,同时需要对类的性质作划分。

猜你喜欢

转载自blog.csdn.net/smznbhh/article/details/127105814