首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript设计模式第2篇:工厂模式

JavaScript设计模式第2篇:工厂模式

作者头像
leocoder
发布2019-12-16 18:04:18
4240
发布2019-12-16 18:04:18
举报
文章被收录于专栏:前端进阶之路前端进阶之路

分类

这里工厂模式分为2类:简单工厂工厂方法,下一节会介绍第3类工厂模式:抽象工厂

简单工厂

定义

简单工厂:定义一个类来创建其他类的实例,根据参数的不同返回不同类的实例,通常这些类拥有相同的父类。

例子

假设现在有 3 款车,Benz、Audi 和 BMW,他们都继承自父类 Car,并且重写了父类方法 drive:

class Car {
    drive () {
        console.log('Car drive');
    }
}

class Benz extends Car {
    drive () {
        console.log(`Benz drive`)
    }
}

class Audi extends Car {
    drive () {
        console.log(`Audi drive`)
    }
}

class BMW extends Car {
    drive () {
        console.log(`BMW drive`)
    }
}
复制代码

我们定义了一个父类 Car,包含一个方法 drive,Benz、Audi 和 BMW 继承自共同的父类 Car。

那么我们在实例化这 3 款车的时候,就需要如下调用:

let benz = new Benz();
let audi = new Audi();
let bmw = new BMW();

benz.drive();       // Benz drive
audi.drive();       // Audi drive
bmw.drive();        // BMW drive

这种写法就很繁琐,这时候就用到我们的简单工厂了,提供一个工厂类:

class SimpleFactory {
    static getCar (type) {
        switch (type) {
            case 'benz':
                return new Benz();
            case 'audi':
                return new Audi();   
            case 'bmw':
                return new BMW();  
        }
    }
}
复制代码

简单工厂类 SimpleFactory 提供一个静态方法 getCar,我们再实例化 3 款车的时候,就变成下面这样了:


let benz = SimpleFactory.getCar('benz');
let audi = SimpleFactory.getCar('audi');
let bmw = SimpleFactory.getCar('bmw');

benz.drive();       // Benz drive
audi.drive();       // Audi drive
bmw.drive();        // BMW drive

这么一看,使用简单工厂后代码行数反而变多了,这种写法真的有优势吗?

我们要知道,设计模式并不是为了减少代码行数而出现的,它是为了使我们的代码更好扩展,更好维护,更方便使用而出现的。那么使用简单工厂后,有什么好处呢?

简单工厂,用户不需要知道具体产品的类名,只需要知道对应的参数即可,对于一些复杂的类名,可以减少用户的记忆量,同时用户无需了解这些对象是如何创建及组织的,有利于整个软件体系结构的优化。

所以,使用简单工厂,是将类的实例化交给工厂函数去做,对外提供统一的方法。我们要养成一个习惯,在代码中 new 是一个需要慎重考虑的操作,new 出现的次数越多,代码的耦合性就越强,可维护性就越差,简单工厂,就是在上面做了一层抽象,将 new 的操作封装了起来,向外提供静态方法供用户调用,这样就将耦合集中到了工厂函数中,而不是暴露在代码的各个位置。

缺陷

简单工厂有它的好处,必然也有它的缺点,比如下面这种情况:

我们又新增了一类车 Ferrai,就需要在简单工厂类 SimpleFactory 中再新增一个 case,添加 Ferrari 的实例化过程。每新增一类车,就要修改简单工厂类,这样做其实违背了设计原则中的开闭原则:对扩展开放,对修改封闭,同时如果每类车在实例化之前需要做一些处理逻辑的话,SimpleFactory 会变的越来越复杂。

所以简单工厂适用于产品类比较少并且不会频繁增加的情况,那么有什么方法能解决简单工厂存在的问题呢?

工厂方法

工厂方法模式是简单工厂的进一步优化,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂,也就是说每个对象都有一个与之对应的工厂。

定义

工厂方法模式又称为工厂模式,它的实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中执行”。

例子

光看概念还是很难理解,我们沿着上面的简单工厂来做修改,举例说明。

还是先定义一个父类 Car,提供一个方法 drive:

class Car {
    drive () {
        console.log('Car drive');
    }
}
复制代码

每一款车都继承自父类 Car,并重写方法 drive:

class Benz extends Car {
    drive () {
        console.log(`Benz drive`)
    }
}

class Audi extends Car {
    drive () {
        console.log(`Audi drive`)
    }
}

class BMW extends Car {
    drive () {
        console.log(`BMW drive`)
    }
}
复制代码

然后按照定义,我们提供一个创建对象的接口:

class IFactory {
    getCar () {
        throw new Error('不允许直接调用抽象方法,请自己实现');
    }
}
复制代码

每款车都提供一个工厂类,实现自上述接口,因为 JavaScript 中没有 implements,所以用 extends 代替:

class BenzFactory extends IFactory {
    getCar () {
        return new Benz();
    }
}

class AudiFactory extends IFactory {
    getCar () {
        return new Audi();
    }
}

class BMWFactory extends IFactory {
    getCar () {
        return new BMW();
    }
}
复制代码

这样当我们需要实例化每款车的时候,就按如下操作:

let benzFactory = new BenzFactory();
let benz = benzFactory.getCar();

let audiFactory = new AudiFactory();
let audi = audiFactory.getCar();

let bmwFactory = new BMWFactory();
let bmw = bmwFactory.getCar();

benz.drive();       // Benz drive
audi.drive();       // Audi drive
bmw.drive();        // BMW drive

我们再来对比一下简单工厂的实例化过程:

let benz = SimpleFactory.getCar('benz');
let audi = SimpleFactory.getCar('audi');
let bmw = SimpleFactory.getCar('bmw');

benz.drive();       // Benz drive
audi.drive();       // Audi drive
bmw.drive();        // BMW drive

我们用 UML 类图来描述一下工厂方法模式:

可以看到,同样是为了实例化一个对象,怎么就变得这么复杂了呢····我们来总结一下工厂方法模式的步骤:

  1. 定义产品父类 -- Car
  2. 定义子类实现父类,并重写父类方法 -- BenzCar、AudiCar、BMWCar
  3. 定义抽象接口,以及抽象方法 -- IFactory
  4. 定义工厂类,实现自抽象接口,并且实现抽象方法 -- BenzFactory、AudiFactory、BMWFactory
  5. new 工厂类,调用方法进行实例化

那么工厂方法增加了如此多的流程,提高了复杂度,究竟解决了简单工厂的什么问题呢?

通过上文可以知道,简单工厂在新增一款车的时候,需要修改简单工厂类 SimpleFactory,违背了设计模式中的**“开闭原则”**:对扩展开放,对修改封闭。

当工厂方法需要新增一款车的时候,比如 Ferrari,只需要定义自己的产品类 Ferrari 以及自己的工厂类 FerrariFactory:

class Ferrari extends Car {
    drive () {
        console.log(`Ferrari drive`)
    }
}

class FerrariFactory extends IFactory {
    getCar () {
        return new Ferrari();
    }
}

let ferrariFactory = new FerrariFactory();
let ferrari = ferrariFactory.getCar();

ferrari.drive();       // Ferrari drive
复制代码

完全不用修改已有的抽象接口 IFactory,只需要扩展实现自己需要的就可以了,不会影响已有代码。这就是对扩展开放,对修改封闭

总结

  1. 简单工厂模式
    • 解决了用户多次自己实例化的问题,屏蔽细节,提供统一工厂,将实例化的过程封装到内部,提供给用户统一的方法,只需要传递不同的参数就可以完成实例化过程,有利于软件结构体系的优化;
    • 但不足之处是,增加新的子类时,需要修改工厂类,违背了“开闭原则”,并且工厂类会变得越来越臃肿;
    • 简单工厂模式适用于固定的,不会频繁新增子类的使用场景
  2. 工厂方法模式
    • 通过在上层再增加一层抽象,提供了接口,每个子类都有自己的工厂类,工厂类实现自接口,并且实现了统一的抽象方法,这样在新增子类的时候,完全不需要修改接口,只需要新增自己的产品类和工厂类就可以了,符合“开闭原则”;
    • 但不足之处也正是如此,持续的新增子类,导致系统类的个数将成对增加,在一定程度上增加了系统的复杂度,同时有更多的类需要编译和运行,会给系统代理一些额外的开销;
    • 工厂方法模式适用于会频繁新增子类的复杂场景;
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年12月10日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 分类
  • 简单工厂
    • 定义
      • 例子
        • 缺陷
        • 工厂方法
          • 定义
            • 例子
            • 总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档