JavaScript设计模式——工厂模式
前言
今天开始学习JavaScript
设计模式,每天学一点,希望有所收获。
今天主要学习工厂模式,包括:简单工厂模式、工厂方法模式、抽象工厂模式。
设计模式分类
1、创建型
创建型设计模式专注于处理对象创建机制 ,以适合给定情况的方式来创建对象。创建对象的基本方法可能导致项目复杂性增加,而这些模式旨在通过控制创建过程来解决这种问题。
* Constructor (构造器)
* Factory(工厂)
* Abstract(抽象)
* Prototype(原型)
* Singleton(单例)
* Builder(生成器)
2、结构型
结构型模式与对象组合有关,通常可以用于在找出在不同对象之间建立关系的简单方法。这种模式有助于确保在系统某一部分发生变化时,系统的整个结构不需要同是改变。同时对于不适合因某一特定目的而改变的系统部分,这种模式也能够帮助它们完成重组。
* Decorator(装饰者)
* Facade(外观)
* Flyweight(享元)
* Adapter(适配器)
* Proxy(代理)
3、行为
行为模式专注于改善或简化系统中不同对象之间的通信。
* Interator(迭代器)
* Mediator(中介者)
* Observer(观察者)
* Visitor(访问)
工厂模式 🏭
1、简单工厂模式
又叫静态工厂模式,就是创建对象,并赋予属性和方法。主要用来创建同一类对象。
栗子🌰:
生产球的工厂有各种球,我们只需告诉它,你想要的球的名字,它便会把球生产出来:
let Basketball = () => {
this.intro = "🏀篮球盛行于美国";
}
Basketball.prototype = {
getNumber: function () {
console.log("每个队伍需要5名队员");
},
getBallSize: function () {
console.log("🏀篮球很大");
}
}
let Football = () => {
this.intro = "⚽️足球在世界范围内都很流行";
}
Football.prototype = {
getNumber: function () {
console.log("每个队伍需要11名队员");
},
getBallSize: function () {
console.log("⚽️足球很大");
}
}
let Tennis = () => {
this.intro = "每年有很多网球🎾比赛";
}
Tennis.prototype = {
getNumber: function () {
console.log("每个队伍需要1名队员");
},
getBallSize: function () {
console.log("🎾网球很小");
}
}
// 体育用品工厂
let SportsFactory = function (name) {
switch(name) {
case 'basketball':
return new Basketball();
case 'football':
return new Football();
case 'tennis':
return new Tennis();
}
}
当我们想要足球时,你可以告诉工厂:football
,它便会将你想要的给你:
let football = SportsFactory('football');
console.log(football.intro); // ⚽️足球在世界范围内都很流行
football.getMember(); // 每个队伍需要11名队员
这个时候,你会发现,上面的三种球的内部结构很相似,为了减少代码的重复,我们对它进行优化:
let SportsFactory = function (name) {
function Balls(option) {
this.intro = option.intro;
this.getNumber = function () {
console.log(option.number)
}
this.getSize = function () {
console.log(option.size)
}
}
switch(name) {
case 'basketball':
return new Balls({
intro: "🏀篮球盛行于美国",
number: "每个队伍需要5名队员",
size: "🏀篮球很大"
});
case 'football':
return new Balls({
intro: "⚽️足球在世界范围内都很流行",
number: "每个队伍需要11名队员",
size: "⚽️足球很大"
});
case 'tennis':
return new Balls({
intro: "🏀篮球盛行于美国",
number: "每个队伍需要5名队员",
size: "🏀篮球很大"
});
}
}
显而易见,我们无需了解,这个工厂是怎么把球给造出来的,只要给到相应的参数,它便给我们相应的球,简单又方便。但是,当我们需要更多种类的球时,如:橄榄球🏈、羽毛球🏸️、台球🎱...balabala...球的种类越多,工厂如果还是按照这种方式去生产球,它会变得越来越臃肿,变得难以维护。所以说,简单工厂模式,只适用于,对象较少,对象逻辑简单的情况。
工厂需要创新,才能获得更多的利润:
2、工厂方法模式
通过对产品类的抽象,使其创建业务主要负责用于创建多类产品的实例。
也就是说,将实际创建对象的工作,放在子类中,把核心类抽离出来,形成抽象类。
我们可以将工厂方法看作是一个实例化对象的工厂类。按照工厂方法模式,我们对上面的代码进行改造。安全起见,我们采用安全模式类,而我们将创建的基类放在工厂方法类的原型中即可。
// 安全模式创建的工厂类
let SportsFactory = function(name) {
if(this instanceof SportsFactory){
let s = new this[name]();
return s
} else {
return new SportsFactory(name);
}
}
// 工厂原型中设置创建所有类型数据对象的基类
SportsFactory.prototype = {
football : function() {
this.intro = "⚽️足球在世界范围内都很流行";
this.getNumber = function () {
console.log("每个队伍需要11名队员");
},
this.getBallSize = function () {
console.log("⚽️足球很大");
}
},
basketball : function() {
this.intro = "🏀篮球盛行于美国";
this.getNumber = function () {
console.log("每个队伍需要5名队员");
},
this.getBallSize = function () {
console.log("🏀篮球很大");
}
},
tennis: function() {
this.intro = "每年有很多网球🎾比赛";
this.getNumber = function () {
console.log("每个队伍需要1名队员");
},
this.getBallSize = function () {
console.log("🎾网球很小");
}
}
}
let football = SportsFactory('football')
football.getBallSize() // ⚽️足球很大
这样以后添加其他类时, 只要写在SportsFactory
这个工厂类的原型里就可以了。
通过工厂方法模式,我们可以轻松的创建多个类的实例对象,这样工厂方法对象在创建对象的方式,避免了使用者与对象类之间的耦合,用户不必关心创建该对象的具体类,只需调用工厂方法即可。
3、抽象工厂模式
通过对类的工厂抽象使其业务对于产品类簇的创建,而不负责某一类产品的实例。
工厂如果只生产代工一家品牌的球类,注定会被其它工厂淘汰,因此,它不能过于依赖一家品牌的订单,需要接受各种品牌的订单,才能形成自己的竞争力。例如: adidas
、nick
、lining
等等。不同的品牌使用的材质、做工可能是不一样的,像上面这三个品牌就是对应的类簇。类簇一般用父类定义,并在父类中定义一些抽象方法,再通过抽象工厂让子类继承父类。因此,抽象工厂其实就是一个实现子类继承父类的方法。
抽象类是一种声明但不能使用的类,JavaScript
中abstract
还是保留字,不能像传统面向对象语言那样轻松的创建抽象类。不过,我们可以手动地抛出错误来模拟抽象类。
// 抽象工厂方法
let SportsFactory = function() {}
SportsFactory.prototype = {
getNumber: function () {
return new Error('抽象方法不可调用');
}
}
以上代码就是一个抽象类,啥也做不了。但是在继承上却很有用,如果在子类中没有重写这些方法,那当子类调用改方法的时候,便会报错。这对于子类忘记重写这些方法时,父类的提示显得非常友好。
抽象类中定义的方法只是显性地定义一些功能,但没有具体的实现,而一个对象是要具有一套完整的功能的,所以,用抽象类创建的对象当时也是“抽象的”,因此,我们不能用它来创建一个真实的对象。
也就是说,工厂虽然拿到了订单,但是还没有可以生产不同品牌产品的设备,下面我们便来实现这些设备:
let SportsFactory = function (subType, superType) {
// 判断抽象工厂中是否有该抽象类
if (typeof SportsFactory[superType] === 'function') {
// 缓存
function F(){};
// 继承父类属性和方法
F.prototype = new SportsFactory[superType] ();
// 将子类 constructor 指向子类
subType.constructor = subType;
// 子类原型继承 “父类”
subType.prototype = new F ();
} else {
// 不存在该抽象类抛出错误
throw new Error('未创建该抽象类');
}
}
//阿迪达斯抽象类
SportsFactory.AdidasBall = function() {
this.type = 'adidas';
}
SportsFactory.AdidasBall.prototype = {
getNumber: function() {
return new Error('抽象方法不能调用');
}
}
//耐克抽象类
SportsFactory.NickBall = function() {
this.type = 'nick';
}
SportsFactory.NickBall.prototype = {
getNumber: function() {
return new Error('抽象方法不能调用');
}
}
//李宁抽象类
SportsFactory.LiNingBall = function() {
this.type = 'lining';
}
SportsFactory.LiNingBall.prototype = {
getNumber: function() {
return new Error('抽象方法不能调用');
}
}
SportsFactory
就是一个抽象工厂方法,该方法在参数中传递子类和父类,在方法体内部,通过寄生式继承,实现了子类对父类的继承。对抽象工厂方法添加抽象类的方法我们是通过点语法进行添加的。
好了,设备买回来了,我们便开始生产你想要的产品啦:
function BallsOfAdidas(option) {
this.intro = "⚽️足球在世界范围内都很流行";
}
//抽象工厂实现WechatUser类的继承
SportsFactory(BallsOfAdidas, 'AdidasBall');
//子类中重写抽象方法
BallsOfAdidas.prototype.getNumber = function() {
console.log("每个队伍需要11名队员");
}
BallsOfAdidas
通过SportsFactory
工厂类继承了AdidasBall
,并且重写了父类AdidasBall
中的getNumber
方法。
生产完成后,我们便可以去买自己想要的品牌的运动器材了:
let adidasFootball = new BallsOfAdidas();
console.log(adidasFootball.type); //adidas
adidasFootball.getNumber(); //每个队伍需要11名队员
抽象工厂模式是设计模式中最抽象的一种,也是创建模式中唯一一种抽象化创建模式。我们可以看到,抽象工厂创建出一个个类簇,固定了类的结构,它不直接创建实例,而是通过类的继承进行类簇的管理。
总结
1、简单工厂模式中,工厂Factory
类集中了所有产品创建的逻辑,一旦要拓展新产品时,就不得不修改工厂类,这就违反了开闭原则(对拓展开放,对修改封闭),并且会造成工厂的逻辑过于复杂。
2、工厂方法模式中,在新增一个新产品时,就要新增一个具体工厂和一个具体产品类,这样程序的拓展性就有了提高,符合了开闭原则,避免了简单工厂模式的缺点,但是呢,新增产品时需要新增两个类,会增加代码量,可谓是有舍有得,具体如何要结合具体情况来使用。
3、抽象工厂模式是所有工厂模式的一般形式,当抽象工厂模式退化到只有一个产品等级结构时,就变成了工厂方法模式。当工厂方法模式的工厂类只有一个时,且工厂方法为静态方法时,则又变成了简单工厂模式。与工厂方法模式相似,抽象工厂模式隔离了具体类的生成,让客户端不清楚具体什么样的对象被创建。
参考
- 《JavaScript设计模式》张容铭