细话工厂模式

构造器

构造器是一类对象的抽象。如有以下几个员工

const liLei = {
    name: '李雷',
    age: 25,
    career: 'coder',
}

const hanMeiMei = {
    name: '韩梅梅',
    age: 24,
    career: 'product manager'
}

在人数较少的时候 我们可以通过一个个重新声明的方式来实现员工的 增加。
但是当一次性需要导入大量员工数据时 就会出现 代码冗余的情况
因此 构造器 便应运而生,其目的在于抽离公共逻辑——也就是将构造声明这个操作抽离出来

function User(name , age, career) {
    this.name = name
    this.age = age
    this.career = career 
}

那么我们在使用的时候 直接new生成即可

const user = new User(name, age, career)

那么构造器做了什么?

构造器是不是将 name、age、career 赋值给对象的过程封装,确保了每个对象都具备这些属性,确保了共性的不变,同时将 name、age、career 各自的取值操作开放,确保了个性的灵活?

如果在使用构造器模式的时候,我们本质上是去抽象了每个对象实例的变与不变。
那么使用工厂模式时,我们要做的就是去抽象不同构造函数(类)之间的变与不变。

简单工厂模式

当传参复杂时候,普通的构造器无法实现,那么 工厂模式的目的便是为了实现无脑传参

通过抽离公共属性公共逻辑并封装到一个函数里

function User(name , age, career, work) {
    this.name = name
    this.age = age
    this.career = career 
    this.work = work
}

function Factory(name, age, career) {
    let work
    switch(career) {
        case 'coder':
            work =  ['写代码','写系分', '修Bug'] 
            break
        case 'product manager':
            work = ['订会议室', '写PRD', '催更']
            break
        case 'boss':
            work = ['喝茶', '看报', '见客户']
        case 'xxx':
            // 其它工种的职责分配
            ...
           
    return new User(name, age, career, work)
}

整体上看:工厂模式实际就是将创建对象的过程进行封装类似于一个黑箱,我们无需在意黑箱内部是如何操作的,只需要在黑箱外按下一个按钮就可轻松实现对象的创建啦ovo

应用场景

在写了大量构造函数、调用了大量的 new、自觉非常不爽的情况下,我们就应该思考是不是可以掏出工厂模式重构我们的代码了。

抽象工厂模式

抽象工厂这块知识,对入行以来一直写纯 JavaScript 的同学可能不太友好——因为抽象工厂在很长一段时间里,都被认为是 Java/C++ 这类语言的专利。

Java/C++ 的特性是什么?它们是强类型的静态语言。用这些语言创建对象时,我们需要时刻关注类型之间的解耦,以便该对象日后可以表现出多态性。但 JavaScript,作为一种弱类型的语言,它具有天然的多态性,好像压根不需要考虑类型耦合问题。而目前的 JavaScript 语法里,也确实不支持抽象类的直接实现,我们只能凭借模拟去还原抽象类。因此有一种言论认为,对于前端来说,抽象工厂就是鸡肋。

简单工厂的缺点 : 当我们需要对factory内部的某一个角色(如boss)进行修改时,必须重写factory 没有遵循开闭原则

开闭原则:对拓展开放,对修改封闭。说得更准确点,软件实体(类、模块、函数)可以扩展,但是不可修改。

大家知道一部智能手机的基本组成是操作系统(Operating System,我们下面缩写作 OS)和硬件(HardWare)组成。所以说如果我要开一个山寨手机工厂,那我这个工厂里必须是既准备好了操作系统,也准备好了硬件,才能实现手机的量产。考虑到操作系统和硬件这两样东西背后也存在不同的厂商,而我现在并不知道我下一个生产线到底具体想生产一台什么样的手机,我只知道手机必须有这两部分组成,所以我先来一个抽象类来约定住这台手机的基本组成:

class MobilePhoneFactory {
    // 提供操作系统的接口
    createOS(){
        throw new Error("抽象工厂方法不允许直接调用,你需要将我重写!");
    }
    // 提供硬件的接口
    createHardWare(){
        throw new Error("抽象工厂方法不允许直接调用,你需要将我重写!");
    }
}

MobilePhoneFactory 就是一个抽象工厂类。我们在使用的时候 需要明确具体需要生成的‘手机’,并继承抽象MobilePhoneFactory类。

比如我现在想要一个专门生产 Android 系统 + 高通硬件的手机的生产线,我给这类手机型号起名叫 FakeStar,那我就可以为 FakeStar 定制一个具体工厂:

class FakeStarFactory extends MobilePhoneFactory {
    createOS() {
        // 提供安卓系统实例
        return new AndroidOS()
    }
    createHardWare() {
        // 提供高通硬件实例
        return new QualcommHardWare()
    }
}

这里我们在提供安卓系统的时候,调用了两个构造函数:AndroidOS 和 QualcommHardWare,它们分别用于生成具体的操作系统和硬件实例。像这种被我们拿来用于 new 出具体对象的类,叫做具体产品类(ConcreteProduct)。具体产品类往往不会孤立存在,不同的具体产品类往往有着共同的功能,比如安卓系统类和苹果系统类,它们都是操作系统,都有着可以操控手机硬件系统这样一个最基本的功能。因此我们可以用一个抽象产品(AbstractProduct)类来声明这一类产品应该具有的基本功能

// 定义操作系统这类产品的抽象产品类
class OS {
    controlHardWare() {
        throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
    }
}

// 定义具体操作系统的具体产品类
class AndroidOS extends OS {
    controlHardWare() {
        console.log('我会用安卓的方式去操作硬件')
    }
}

class AppleOS extends OS {
    controlHardWare() {
        console.log('我会用的方式去操作硬件')
    }
}

硬件类的同理

// 定义手机硬件这类产品的抽象产品类
class HardWare {
    // 手机硬件的共性方法,这里提取了“根据命令运转”这个共性
    operateByOrder() {
        throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
    }
}

// 定义具体硬件的具体产品类
class QualcommHardWare extends HardWare {
    operateByOrder() {
        console.log('我会用高通的方式去运转')
    }
}

class MiWare extends HardWare {
    operateByOrder() {
        console.log('我会用小米的方式去运转')
    }
}

好了,如此一来,当我们需要生产一台FakeStar手机时,我们只需要这样做:

// 这是我的手机
const myPhone = new FakeStarFactory()
// 让它拥有操作系统
const myOS = myPhone.createOS()
// 让它拥有硬件
const myHardWare = myPhone.createHardWare()
// 启动操作系统(输出‘我会用安卓的方式去操作硬件’)
myOS.controlHardWare()
// 唤醒硬件(输出‘我会用高通的方式去运转’)
myHardWare.operateByOrder()

关键的时刻来了——假如有一天,FakeStar过气了,我们需要产出一款新机newStar投入市场,这时候怎么办?我们是不是不需要对抽象工厂MobilePhoneFactory做任何修改,只需要拓展它的种类:

class newStarFactory extends MobilePhoneFactory {
    createOS() {
        // 操作系统实现代码
    }
    createHardWare() {
        // 硬件实现代码
    }
}

这么个操作,对原有的系统不会造成任何潜在影响 所谓的“对拓展开放,对修改封闭”就这么圆满实现了。前面我们之所以要实现抽象产品类,也是同样的道理。
大家现在回头对比一下抽象工厂和简单工厂的思路,思考一下:它们之间有哪些异同?

它们的共同点,在于都尝试去分离一个系统中变与不变的部分。它们的不同在于场景的复杂度。在简单工厂的使用场景里,处理的对象是类,并且是一些非常好对付的类——它们的共性容易抽离,同时因为逻辑本身比较简单,故而不苛求代码可扩展性。抽象工厂本质上处理的其实也是类,但是是一帮非常棘手、繁杂的类,这些类中不仅能划分出门派,还能划分出等级,同时存在着千变万化的扩展可能性——这使得我们必须对共性作更特别的处理、使用抽象类去降低扩展的成本,同时需要对类的性质作划分,于是有了这样的四个关键角色:
1.抽象工厂(抽象类,它不能被用于生成具体实例): 用于声明最终目标产品的共性。在一个系统里,抽象工厂可以有多个(大家可以想象我们的手机厂后来被一个更大的厂收购了,这个厂里除了手机抽象类,还有平板、游戏机抽象类等等),每一个抽象工厂对应的这一类的产品,被称为“产品族”。
2.具体工厂(用于生成产品族里的一个具体的产品): 继承自抽象工厂、实现了抽象工厂里声明的那些方法,用于创建具体的产品的类。
3.抽象产品(抽象类,它不能被用于生成具体实例): 上面我们看到,具体工厂里实现的接口,会依赖一些类,这些类对应到各种各样的具体的细粒度产品(比如操作系统、硬件等),这些具体产品类的共性各自抽离,便对应到了各自的抽象产品类。
4.具体产品(用于生成产品族里的一个具体的产品所依赖的更细粒度的产品): 比如我们上文中具体的一种操作系统、或具体的一种硬件等。

你可能感兴趣的:(细话工厂模式)