Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

前端必会设计模式之(四) 工厂模式 #7

Open
LyzSg opened this issue Jun 13, 2018 · 0 comments
Open

前端必会设计模式之(四) 工厂模式 #7

LyzSg opened this issue Jun 13, 2018 · 0 comments

Comments

@LyzSg
Copy link
Owner

LyzSg commented Jun 13, 2018

我们又来看一个场景,现在我们想做一个飞机大战的小游戏,然后有很多不同种类的飞机

我们普通地创建一个小飞机对象:

var oSmallPlane = {
    width: 100,
    height: 100,
    blood: 100,
    touch: function () {
        console.log('die');
    }
}

但是如果这样肯定是不利于我们创建多架小飞机,需要复制粘贴多次,增加了很多代码量。

于是我们想到用构造函数:

function SmallPlane() {
    this.width = 100;
    this.height = 100;
    this.blood = 100;
    this.touch = function () {
        console.log('die');
    }
}
var oSmallPlane = new SmallPlane();

这样使得我们创建一架小飞机非常方便。但是如果现在我们的飞机种类有很多,我们就需要写很多个这样的不同的飞机类,不便于我们统一管理,当我们想给他们添加一些公共的方法,例如这些飞机都可以被触碰,都可以飞等等,就需要往分别往这不同的类上一个个添加,不能统一操作,很麻烦。

所以为了更好地去创建对象、管理对象,我们引入了工厂模式。 (如果使用继承可能会增加原型链的长度,并且不能用一个统一的接口去生产飞机)

简单工厂模式

定义:工厂模式定义创建对象的接口,但是让子类去真正的实例化。也就是工厂方法将类的实例化延迟到子类

于是,我们定义一个PlaneFactory方法作为创建对象的接口,然后通过SmallPlane、SmartPlane这些子类去真正地实例化飞机。实例化后,我们还可以在PlaneFactory方法里面给飞机出厂前进行一些统一的操作,例如添加一个touch方法。

var oSmallPlane = new PlaneFactory('SmallPlane');  // new不new都可以

// 通过传进不同的type来生产不同的飞机
function PlaneFactory(type) {

    var oNewPlane = null;

    switch (type) {
        case 'SmallPlane':
            oNewPlane = new SmallPlane(); 
            break;
        case 'SmartPlane':
            oNewPlane = new SmartPlane();
            break;
        case 'AttackPlane':
            oNewPlane = new AttackPlane();
            break;
    }

    // 添加公有方法
    oNewPlane.touch = function () {
        console.log('die');
    }

    // 最后出厂
    return oNewPlane;
}
// 小飞机
function SmallPlane() {
    this.width = 100;
    this.height = 100;
    this.blood = 100;
    // this.touch = function () {
    //     console.log('die');
    // }
}
// 智能飞机,带有追踪功能
function SmartPlane() {
    this.width = 200;
    this.height = 200;
    this.blood = 150;
    // this.touch = function () {
    //     console.log('die');
    // }
    this.track = function () {
        console.log('track');
    }
}
// 攻击型飞机
function AttackPlane() {
    this.width = 200;
    this.height = 200;
    this.blood = 150;
    // this.touch = function () {
    //     console.log('die');
    // }
    this.attack = function () {
        console.log('attack');
    }
}

对比普通的方式:

  • 工厂类集中了所有对象的创建,便于对象创建的统一管理

  • 实现了单一职责原则,工厂类只负责判断要造的飞机类型,真正实现生产飞机由子类完成。

  • 便于扩展,如果新增了一种业务,只需要增加相关的业务对象类和工厂类中的生产业务对象的方法,不需要修改其他的地方。例如我增加一种BossPlane,我们只需要完成他的构造函数,并且在PlaneFactory的swich中增加一个case即可。

  • 但这违反了开闭原则。例如添加一种飞机,我们必须进到PlaneFactory里面去修改;添加一种公共方法,我们又要进到里面去添加。

更进一步地讲,我们既想统一地管理子类,又想后期可以方便地添加一些新的子类、方法时,并且让这个工厂类能符合开闭原则,提高复用性,于是我们的工厂方法模式闪亮登场。

工厂方法模式

工厂方法模式:不再有一个唯一的工厂类就创建产品,而是将不同的产品交给对应的工厂子类去实现。每个产品由负责生产的子工厂来创造。如果添加新的产品,需要做的是添加新的子工厂和产品,而不需要修改其他的工厂代码。

什么意思呢,下面我们来看具体实现:

首先我们定义一个抽象工程类,里面先不写东西

function PlaneFactory() {
}

然后我们希望通过这个PlaneFactory的静态方法create来生产飞机,create的具体实现后面再说。

function PlaneFactory() {
}

PlaneFactory.create = function(type) {
    /* ... */
}

var oSp = PlaneFactory.create('SmallPlane');
var oAp = PlaneFactory.create('AttackPlane');
var oSmp = PlaneFactory.create('SmartPlane');

我们先来看看产品子工厂,他是定义在这个抽象工程类的原型上的:

PlaneFactory.prototype.SmallPlane = function () {
    this.name = 'SmallPlane';
    this.width = 100;
    this.height = 100;
    this.blood = 100;
}
PlaneFactory.prototype.SmartPlane = function () {
    this.name = 'SmartPlane';
    this.width = 50;
    this.height = 50;
    this.blood = 100;
    this.track = function () {
        console.log('track');
    }
}
PlaneFactory.prototype.AttackPlane = function () {
    this.name = 'AttackPlane';
    this.width = 200;
    this.height = 200;
    this.blood = 150;
    this.attack = function () {
        console.log('attack');
    }
}

接着再来看看公有方法是怎么添加的:

PlaneFactory.prototype.touch = function () {
    console.log('die');
}

没错,公有方法也是添加到抽象工厂类的原型上的,这样写的好处是,它作为公共接口,所有的子类所实例化的对象都可以使用,而且什么时候往原型上添加一个新方法,所有实例都会同步更新。

那么怎么把这些公有方法和子工厂联系到一起呢,他们都是定义在原型上呀?

前面提到了,子工厂继承抽象工厂,所以,我们只需要修改一下原型链就可以了。

例如,我们让SmallPlane子工厂继承抽象工厂:

PlaneFactory.prototype.SmallPlane.prototype = new PlaneFactory();

这样一来,通过子工厂构造出来的飞机,就通过自身的__proto__属性找到了new PlaneFactory(),又通过new PlaneFactory()的__proto__属性找到了PlaneFactory.prototype,于是就可以使用touch方法了。

知道了这一点,我们就能回过头来实现create方法了:

PlaneFactory.create = function(type) {

    // 1. 首先我们需要根据类型,找到对应的子工厂。如果没有找到,则抛出错误
    if(PlaneFactory.prototype[type] === undefined) {
        throw 'no this constructor';
    }
    
    // 2. 找到以后,我们还不能生产,需要先看一下子工厂是否和PlaneFactory具备继承的关系
    if(PlaneFactory.prototype[type].prototype.__proto__ !== PlaneFactory.prototype) {
        
        // 如果不具备,那么就修改继承关系,让其能使用公有方法
        PlaneFactory.prototype[type].prototype = new PlaneFactory();

    }
    
    // 3. 拥有继承关系后,我们就可以创建飞机了。
    var oNewPlane = new PlaneFactory.prototype[type]();

    // 4. 出厂
    return oNewPlane;
}

这样一来,我们的工厂方法模式,这个飞机工厂就封装好了。来看一下结果:
image
没问题,很棒棒。

而且,如果这时我们需要新添加一种飞机,例如BossPlane,也会非常地方便,只需要增加一个子工厂即可:

PlaneFactory.prototype.BossPlane = function () {
    this.name = 'BossPlane';
    this.width = 300;
    this.height = 300;
    this.blood = 500;
    this.bomb = function () {
        console.log('bomb');
    }
}

接着我们直接尝试生成一个实例,发现完全没问题:
image
image

因为我们修改继承关系是在create方法里实现的,所以你不需要每当添加一个新的子工厂时都要手动去继承。创建实例的同时已经自动继承好了。也就是说,添加新子类,又或者新的公有方法(这里就不演示了),只需要直接往上添就是了,不用改其他地方,显得非常方便,便于管理。

同时因为这些子工厂都写在抽象工厂的原型上,所以也不需要像简单工厂模式那样通过switch去匹配,直接通过找对象属性的方式直接调用子工厂就是了。从头到尾就一个PlaneFactory类,简洁。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant