简单工厂模式

现实生活中,原始社会自给自足(没有工厂),农耕社会小作坊(简单工厂,民间酒坊),工业革命流水线(工厂方法,自产自销),现代产业链代工厂(抽象工厂,富士康)。我们的项目代码同样是由简到繁一步一步迭代而来的,但对于调用者来说,却越来越简单。
在日常开发中,凡是需要生成复杂对象的地方,都可以尝试考虑使用工厂模式来代替。

注意:上述复杂对象指的是类的构造函数参数过多等对类的构造有影响的情况,因为类的构造过于复杂,如果直接在其他业务类内使用,则两者的耦合过重,后续业务更改,就需要在任何引用该类的源代码内进行更改,光是查找所有依赖就很消耗时间了,更别说要一个一个修改了。

工厂模式的定义:定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。
按实际业务场景划分,工厂模式有 3 种不同的实现方式,分别是简单工厂模式、工厂方法模式和抽象工厂模式。
我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”。
在简单工厂模式中创建实例的方法通常为静态(static)方法,因此简单工厂模式(Simple Factory Pattern)又叫作静态工厂方法模式(Static Factory Method Pattern)。
简单来说,简单工厂模式有一个具体的工厂类,可以生成多个不同的产品,属于创建型设计模式。简单工厂模式不在 GoF 23 种设计模式之列。
简单工厂模式每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度,违背了“开闭原则”。

“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。


优点和缺点

优点:

  1. 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
  2. 客户端无需知道所创建具体产品的类名,只需知道参数即可。
  3. 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。

缺点:

  1. 简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
  2. 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
  3. 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
  4. 简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。

应用场景

对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。


Js代码的实现

正规意义上而言,一个简单工厂需要具备的条件是这样的:

抽象: 定义一个最初的对象(告诉你有这么一个东西);

工厂: 用于创建对象,也即对象的大本营(告诉你这个东西是一堆宝箱,你可以选择要哪个);

具体对象: 最具象的对象,也就是某个大本营(告诉你这个宝箱中都有什么)

好吧,感觉是要贴点代码出来了~

1.我们先创建一个person原型,代码如下:

var Person = function(name,methods){
	if(arguments.length > 2){
		throw new Error('参数过多');
	};
	this.name    = name;
	this.methods = [];
	for(var i=0,l=methods.length;i<l;i++){
		if(typeof methods[i] !== 'string'){
			throw new Error('参数命名需要为字符串');
		}
		this.methods.push(methods[i]);
	}
};
Person.ensureImplements = function(obj){
	for(var i=1,l=obj.length;i<l;i++){
		var interface = arguments[i];
		if(interface.constructor!=Interface){
			throw new Error('此类型接口有误');
		}
		for(var j=0,ml=interface.methods.length;j<ml;j++){
			var methods = interface.methods[j];
			if(!obj[methods] || typeof obj[methods] !== 'function'){
				throw new Error(methods + '方法不存在');
			}
		}
	}
}

这里我们创建了一个Person对象,拥有name和method属性,并做了简单处理。然后我们创建一个抽象的Person,代码如下:

var interfacePerson = new Person('Ren',['run','eat']);

然后,看看我们的具体对象,如下所示:

var PersonOne = function(){};
PersonOne.prototype = {
	run : function(){
		console.log('第一个人文文要跑了');
	},
	eat : function(){
		console.log('第一个人文文要吃饭了')
	}
}

var PersonTwo = function(){};
PersonTwo.prototype = {
	run : function(){
		console.log('第二个人瑞瑞要跑了');
	},
	eat : function(){
		console.log('第二个人瑞瑞要吃饭了')
	}
}

var PersonThree = function(){};
PersonThree.prototype = {
	run : function(){
		console.log('第三个人文瑞要跑了');
	},
	eat : function(){
		console.log('第三个人文瑞要吃饭了')
	}
}

我们定义了3个人,分别拥有run和eat的功能,并且每个人又都不一样。好了,接下来我们要开始创建我们的工厂了,哈哈,我们把我们的文瑞这个逗逼pm抛出去~

var WenRui = function(){};
WenRui.prototype = {
	catchWenRui : function(whichOne){
		switch(whichOne){
			case '1' : pm = new PersonOne(); break;
			case '2' : pm = new PersonTwo(); break;
			default : pm = new PersonThree();
		}
		Interface.ensureImplements(pm);
		return pm;
	}
}

接下来就到了秋香点‘文瑞’的时刻了,兄弟们,调戏起来:

var wenRui = new WenRui();
wenRui.catchWenRui('1').run(); //第一个人文文要跑了
wenRui.catchWenRui('2').run(); //第二个人瑞瑞要跑了
wenRui.catchWenRui('3').eat(); //第三个人文瑞要吃饭了

好吧,道理讲清楚了。WenRui这个构造函数充当了我们的factory的角色,我们放跑了第一个和第二个的文文和瑞瑞,留下了文瑞来吃饭~。

自我感觉开销大,来一个人就得加一份饭,工厂本身一旦瘫痪,全线崩盘。对开放封闭这个理念也是没能好好的贯彻,所以嘛,需要调教,得给个别对象添加个lesson方法来约束约束


链接: https://www.fly63.com/course/27_1260