# 工厂模式

工厂模式属于创建型设计模式, 是一类用于处理对象创建的设计模式.

# 简单工厂模式

在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例.

🌰 比如说体育商品店卖体育器材,里面有很多体育用品,及其相关介绍等。当你来到体育用品店买一个篮球及其相关介绍时,你只需要问售货员,他会帮你找到你所要的东西。” “当你想和小伙伴踢足球,只需要告诉店员我要买个足球即可。你使用这个商店工厂时仅仅需要记住 SportsFactory 这个工厂对象就好了。

// 篮球基类
var Basketball = function(){
    this.intro = '篮球盛行于美国';
}
Basketball.prototype = {
    getMember : function(){
        console.log('每个队伍需要5名队员');
    },
    getBallSize : function(){
        console.log('篮球很大');
    }
}
// 足球基类
var Football = function(){
    this.intro = '足球在世界范围内很流行';
}
Football.prototype = {
    getMember : function(){
        console.log('每个队伍需要11名队员');
    },
    getBallSize : function(){
        console.log('足球很大');
    }
}
// 网球基类
var Tennis = function(){
    this.intro = '每年有很多网球系列赛';
}
Tennis.prototype = {
    getMember : function(){
        console.log('每个队伍需要1名队员');
    },
    getBallSize : function(){
        console.log('网球很小');
    }
}
// 运动工厂
var SportsFactory = function(name){
    switch(name){
        case 'NBA':
            return new Basketball();
        case 'wordCup':
            return new Football();
        case 'FrenchOpen':
            return new Tennis();
    }
}
// 为世界杯创建一个足球,只需要记住运动工厂SportsFactory,调用并创建
var footnall = SportsFactory('wordCup');
console.log(footnall);
console.log(footnall.intro);
footnall.getMember();

简单工厂模式的理念就是创建对象,像上面那种方式是对不同的类实例化,不过除此之外简单工厂模式还可以用来创建相似对象。

🌰 比如你想创建一些书,那么这些书都有一些相似的地方,比如目录、页码等。也有一些不相似的地方,如书名、出版时间、书的类型等,对于创建的对象相似的属性好处理,对于不同的属性就要有针对性地进行处理了,比如我们将不同的属性作为参数传递进来处理。”

//工厂模式
function createBook(name, time, type){
    // 创建一个对象,并对对象拓展属性和方法
    var o = new Object();
    o.name = name;
    o.time = time;
    o.type = type;
    o.getName = function(){
        console.log(this.name);
    };    
    // 将对象返回
    return o;
}
var book1 = createBook("js book", 2014, "js");
var book2 = createBook("css book", 2013, "css");
book1.getName();
book2.getName();

上面这两种工厂模式: “第一种是通过类实例化对象创建的,第二种是通过创建一个新对象然后包装增强其属性和功能来实现的。他们之间的差异性也造成前面通过类创建的对象,如果这些类继承同一父类,那么他们的父类原型上的方法是可以共用的。而后面寄生方式创建的对象都是一个新的个体,所以他们的方法就不能共用了。当然选择哪种工厂方式来实现你的需求还要看你是如何分析你的需求的。”

# 工厂方法模式

工厂方法模式(Factory Method):通过对产品类的抽象使其创建业务主要负责用于创建多类产品的实例

假如说, 新来了一批广告资源需要投放,关于计算机培训的。一批是Java的,用绿色字体。还有一批是PHP的,要用黄色字体,红色背景。”

用 『 简单工厂模式 』来写的话就是:

// 创建Java学科类
var Java = function(content){
    //  ......
}
// 创建PHP学科类
var Php = function(content){
    //  ......
}
// 创建JavaScript学科
var JavaScript = function(content){
    this.content = content;
    (function(content){
        var div = document.createElement('div');
        div.innerHTML = content;
        div.style.background = 'pink';
        document.getElementById('container').appendChild(div);
    })(content);
}
// 学科类工厂
function JobFactory(type, content){
    switch(type){
        case 'java' :
            return new Java(content);
        case 'php' : 
            return new Php(content);
        case 'JavaScript' :
            return new JavaScript(content);
    }
}

# 安全模式类

安全模式类可以屏蔽, 对类的错误使用, 造成的错误。

比如对于一个类(我们暂且称之为 Demo 类)的创建,我们知道类的前面是需要有 new 关键字的,不过如果其他人不知道这个对象(Demo)是一个类,那么在使用时很可能忽略 new 关键字直接执行类,此时我们得到的并不是我们预期的对象.

var Demo = function(){}
Demo.prototype = {
    show : function(){
        console.log('成功获取!');
    }
}
var d = new Demo();
d.show(); // 成功获取!
var d = Demo();
d.show(); // Uncaught TypeError: Cannot read property 'show' of undefined

解决这种错误的方法很简单, 就是在构造函数开始时先判断当前对象 this 指向是不是类(Demo)的实例, 如果是则表示构造函数前是有 new 操作符的,如果不是说明类在全局作用域中执行(通常情况下, 如非浏览器环境可为其他全局对象)这样我们就要重新返回新创建的对象了。

var Demo = function(){
    if(!(this instanceof Demo)){
        return new Demo();
    }

    // 其余代码
}
var d = Demo();
d.show(); // 成功获取!

# 安全的工厂方法

“工厂方法模式本意是说将实际创建对象工作推迟到子类当中。这样核心类就成了抽象类,不过对于JavaScript不必这么深究,JavaScript没有像传统创建抽象类那样的方式轻易创建抽象类,所以我们可以将工厂方法看作是一个实例化对象的工厂类。安全起见,我们采用安全模式类,而我们将创建对象的基类放在工厂方法类的原型中即可。”

前面的例子, 用 『 安全工厂方法 』来写的话就是:

// 安全模式创建的工厂类
var Factory = function(type, content){
    if(this instanceof Factory){
        var s = new this[type](content);
        return s;
    }else{
        return new Factory(type, content);
    }    
}
// 工厂原型中设置创建所有类型数据对象的基类
Factory.prototype = {
    Java : function(content){
        //  ......
    },
    JavaScript : function(content){
        //  ......
    },
    UI : function(content){
        this.content = content;
        (function(content){
            var div = document.createElement('div');
            div.innerHTML = content;
            div.style.border = '1px solid red';
            document.getElementById('container').appendChild(div);
        })(content);
    },
    php : function(content){
        //  ......
    }
};

# 抽象工厂模式

抽象工厂模式(Abstract Factory):通过对类的工厂抽象使其业务用于对产品类簇的创建,而不负责创建某一类产品的实例

# 抽象类

抽象类是一种声明但不能使用的类,当你使用时就会报错。

// 汽车抽象类,当使用其实例对象的方法时会抛出错误
var Car = function(){};
Car.prototype = {
    getPrice : function(){
        return new Error('抽象方法不能调用');
    },
    getSpeed : function(){
        return new Error('抽象方法不能调用');
    }
};

“我们看到我们创建的这个 Car 类其实什么都不能做,创建时没有任何属性,然而原型 prototype 上的方法也不能使用,否则会报错。但在继承上却是很有用的,因为定义了一种类,并定义了该类所必备的方法,如果在子类中没有重写这些方法,那么当调用时能找到这些方法便会报错。

抽象类的一个作用,即定义一个产品簇,并声明一些必备的方法,如果子类中没有去重写就会抛出错误

# 抽象工厂模式

抽象类中定义的方法只是显性地定义一些功能,但没有具体的实现,而一个对象是要具有一套完整的功能的,所以用抽象类创建的对象当然也是‘抽象的’, 我们不能使用它来创建一个具体的对象. 所以一般我们用抽象类作为父类来创建一些子类。

// 抽象工厂方法
var VehicleFactory = function(subType, superType){
    // 判断抽象工厂中是否有该抽象类
    if(typeof VehicleFactory[superType] === 'function'){
        // 缓存类
        function F(){};
        // 继承父类属性和方法
        F.prototype = new VehicleFactory[superType]();
        // 将子类constructor指向子类
        subType.constructor = subType;
        // 子类原型继承“父类”
        subType.prototype = new F();
    }else{
        // 不存在该抽象类抛出错误
        throw new Error('未创建该抽象类');
    }    
}
// 小汽车抽象类
VehicleFactory.Car = function(){
    this.type = 'car';
};
VehicleFactory.Car.prototype = {
    getPrice : function(){
        return new Error('抽象方法不能调用');
    },
    getSpeed : function(){
        return new Error('抽象方法不能调用');
    }
};

// 公交车抽象类
VehicleFactory.Bus = function(){
    this.type = 'bus';
};

VehicleFactory.Bus.prototype = {
    getPrice : function(){
        return new Error('抽象方法不能调用');
    },
    getPassengerNum : function(){
        return new Error('抽象方法不能调用');
    }
};

// 货车抽象类
VehicleFactory.Truck = function(){
    this.type = 'truck';
};

VehicleFactory.Truck.prototype = {
    getPrice : function(){
        return new Error('抽象方法不能调用');
    },
    getTrainload : function(){
        return new Error('抽象方法不能调用');
    }
}

可以看到,抽象工厂其实是一个实现子类继承父类的方法,在这个方法中我们需要通过传递子类以及要继承父类(抽象类)的名称,并且在抽象工厂方法中又增加了一次对抽象类存在性的一次判断,如果存在,则将子类继承父类的方法。然后子类通过寄生式继承。

继承父类过程中有一个地方需要注意,就是在对过渡类的原型继承时,我们不是继承父类的原型,而是通过new关键字复制的父类的一个实例,这么做是因为过渡类不应仅仅继承父类的原型方法,还要继承父类的对象属性

# 抽象与实现

既然抽象工厂是用来创建子类的(而本例中其实是让子类继承父类,是对子类的一个拓展),所以我们需要一些产品子类,然后让子类继承相应的产品簇抽象类

// 宝马汽车子类
var BMW = function(price, speed){
    this.price = price;
    this.speed = speed;
}

// 抽象工厂实现对Car抽象类的继承
VehicleFactory(BMW, 'Car');

BMW.prototype.getPrice = function(){
    return this.price;
}

BMW.prototype.getSpeed = function(){
    return this.speed;
}

// 兰博基尼汽车子类
var Lamborghini = function(price, speed){
    this.price = price;
    this.speed = speed;
}

// 抽象工厂实现对Car抽象类的继承
VehicleFactory(Lamborghini, 'Car');

Lamborghini.prototype.getPrice = function(){
    return this.price;
}

Lamborghini.prototype.getSpeed = function(){
    return this.speed;
}

// 宇通汽车子类
var YUTONG = function(price, passenger){
    this.price = price;
    this.passenger = passenger;
}

// 抽象工厂实现对Bus抽象类的继承
VehicleFactory(YUTONG, 'Bus');

YUTONG.prototype.getPrice = function(){
    return this.price;
}

YUTONG.prototype.getPassengerNum = function(){
    return this.passenger;
}

// 奔驰汽车子类
var BenzTruck = function(price, trainLoad){
    this.price = price;
    this.trainLoad = trainLoad;
}

// 抽象工厂实现对Truck抽象类的继承
VehicleFactory(BenzTruck, 'Truck')

BenzTruck.prototype.getPrice = function(){
    return this.price;
}

BenzTruck.prototype.getTrainload = function(){
    return this.price;

通过抽象工厂,我们就能知道每个子类到底是哪一种类别了,然后他们也具备了该类所必备的属性和方法了

上次更新: 7/4/2020, 4:14:54 AM