JavaScript设计模式-工厂模式
概念
什么是工厂模式?
工厂模式是用来创建对象的一种最常用的设计模式。我们不暴露创建对象的具体逻辑,而是将将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂。工厂模式根据抽象程度的不同可以分为:简单工厂,工厂方法和抽象工厂。
简单工厂
let UserFactory = function (role) {
function SuperAdmin() {
this.name = "超级管理员",
this.viewPage = ['首页', '通讯录', '发现页', '应用数据', '权限管理']
}
function Admin() {
this.name = "管理员",
this.viewPage = ['首页', '通讯录', '发现页', '应用数据']
}
function NormalUser() {
this.name = '普通用户',
this.viewPage = ['首页', '通讯录', '发现页']
}
switch (role) {
case 'superAdmin':
return new SuperAdmin();
break;
case 'admin':
return new Admin();
break;
case 'user':
return new NormalUser();
break;
default:
throw new Error('参数错误, 可选参数:superAdmin、admin、user');
}
}
//调用
let superAdmin = UserFactory('superAdmin');
let admin = UserFactory('admin')
let normalUser = UserFactory('user')
UserFactory就是一个简单工厂,在该函数中有3个构造函数分别对应不同的权限的用户。当我们调用工厂函数时,只需要传递superAdmin, admin, user这三个可选参数中的一个获取对应的实例对象。你也许发现,我们的这三类用户的构造函数内部很相识,我们还可以对其进行优化。
let UserFactory = function (role) {
function User(opt) {
this.name = opt.name;
this.viewPage = opt.viewPage;
}
switch (role) {
case 'superAdmin':
return new User({ name: '超级管理员', viewPage: ['首页', '通讯录', '发现页', '应用数据', '权限管理'] });
break;
case 'admin':
return new User({ name: '管理员', viewPage: ['首页', '通讯录', '发现页', '应用数据'] });
break;
case 'user':
return new User({ name: '普通用户', viewPage: ['首页', '通讯录', '发现页'] });
break;
default:
throw new Error('参数错误, 可选参数:superAdmin、admin、user')
}
}
//调用
let superAdmin = UserFactory('superAdmin');
let admin = UserFactory('admin')
let normalUser = UserFactory('user')
简单工厂的优点在于,你只需要一个正确的参数,就可以获取到你所需要的对象,而无需知道其创建的具体细节。但是在函数内包含了所有对象的创建逻辑(构造函数)和判断逻辑的代码,每增加新的构造函数还需要修改判断逻辑代码。当我们的对象不是上面的3个而是30个或更多时,这个函数会成为一个庞大的超级函数,便得难以维护。所以,简单工厂只能作用于创建的对象数量较少,对象的创建逻辑不复杂时使用。
工厂方法:
-
工厂方法模式的本意是将实际创建对象的工作推迟到子类中,这样核心类就变成了抽象类。但是在JavaScript中很难像传统面向对象那样去实现创建抽象类。所以在JavaScript中我们只需要参考它的核心思想即可。我们可以将工厂方法看作是一个实例化对象的工厂类**。**
-
在简单工厂模式中,我们每添加一个构造函数需要修改两处代码。现在我们使用工厂方法模式改造上面的代码,刚才提到,工厂方法我们只把它看作是一个实例化对象的工厂,它只做实例化对象这一件事!我们采用安全模式创建对象。
//安全模式创建的工厂方法函数
let UserFactory = function(role) {
if(this instanceof UserFactory) {
var s = new this[role]();
return s;
} else {
return new UserFactory(role);
}
}
//工厂方法函数的原型中设置所有对象的构造函数
UserFactory.prototype = {
SuperAdmin: function() {
this.name = "超级管理员",
this.viewPage = ['首页', '通讯录', '发现页', '应用数据', '权限管理']
},
Admin: function() {
this.name = "管理员",
this.viewPage = ['首页', '通讯录', '发现页', '应用数据']
},
NormalUser: function() {
this.name = '普通用户',
this.viewPage = ['首页', '通讯录', '发现页']
}
}
//调用
let superAdmin = UserFactory('SuperAdmin');
let admin = UserFactory('Admin')
let normalUser = UserFactory('NormalUser')
说明一下安全模式
var Person = function (name) {
this.name = name
}
var jack = Person('Jack')
//这样子是拿不到实例的
console.log(jack) // undefined
//需要这样:
var jack = new Person('Jack')
console.log(jack)
利用安全模式改造,这样改造之后不用new也能拿到实例了,安全模式的好处
var Person = function (name) {
if(this instanceof Person) {
this.name = name
} else {
return new Person(name)
}
}
var jack = Person('Jack')
console.log(jack.name) // Jack
抽象工厂
- 上面介绍了简单工厂模式和工厂方法模式都是直接生成实例,但是抽象工厂模式不同,抽象工厂模式并不直接生成实例, 而是用于对产品类簇的创建。
- 上面例子中的superAdmin,admin,user三种用户角色,其中user可能是使用不同的社交媒体账户进行注册的,例如:wechat,qq,weibo。那么这三类社交媒体账户就是对应的类簇。在抽象工厂中,类簇一般用父类定义,并在父类中定义一些抽象方法,再通过抽象工厂让子类继承父类。所以,抽象工厂其实是实现子类继承父类的方法。
let AccountAbstractFactory = function (subType, superType) {
//判断抽象工厂中是否有该抽象类
if (typeof AccountAbstractFactory[superType] === 'function') {
//缓存类
// eslint-disable-next-line no-inner-declarations
function F () {
}
//继承父类属性和方法
F.prototype = new AccountAbstractFactory[superType]()
//将子类的constructor指向子类
subType.constructor = subType
//子类原型继承父类
subType.prototype = new F()
} else {
throw new Error('抽象类不存在!')
}
}
//微信用户抽象类
AccountAbstractFactory.WechatUser = function () {
this.type = 'wechat'
}
AccountAbstractFactory.WechatUser.prototype = {
getName: function () {
return new Error('抽象方法不能调用')
}
}
//qq用户抽象类
AccountAbstractFactory.QqUser = function () {
this.type = 'qq'
}
AccountAbstractFactory.QqUser.prototype = {
getName: function () {
return new Error('抽象方法不能调用')
}
}
//新浪微博用户抽象类
AccountAbstractFactory.WeiboUser = function () {
this.type = 'weibo'
}
AccountAbstractFactory.WeiboUser.prototype = {
getName: function () {
return new Error('抽象方法不能调用')
}
}
//普通微信用户子类
function UserOfWechat (name) {
this.name = name
this.viewPage = ['首页', '通讯录', '发现页']
}
//抽象工厂实现WechatUser类的继承
AccountAbstractFactory(UserOfWechat, 'WechatUser')
//子类中重写抽象方法
UserOfWechat.prototype.getName = function () {
return this.name
}
//普通qq用户子类
function UserOfQq (name) {
this.name = name
this.viewPage = ['首页', '通讯录', '发现页']
}
//抽象工厂实现QqUser类的继承
AccountAbstractFactory(UserOfQq, 'QqUser')
//子类中重写抽象方法
UserOfQq.prototype.getName = function () {
return this.name
}
//普通微博用户子类
function UserOfWeibo (name) {
this.name = name
this.viewPage = ['首页', '通讯录', '发现页']
}
//抽象工厂实现WeiboUser类的继承
AccountAbstractFactory(UserOfWeibo, 'WeiboUser')
//子类中重写抽象方法
UserOfWeibo.prototype.getName = function () {
return this.name
}
//实例化微信用户
let wechatUserA = new UserOfWechat('微信小李');
console.log(wechatUserA.getName(), wechatUserA.type); //微信小李 wechat
let wechatUserB = new UserOfWechat('微信小王');
console.log(wechatUserB.getName(), wechatUserB.type); //微信小王 wechat
//实例化qq用户
let qqUserA = new UserOfQq('QQ小李');
console.log(qqUserA.getName(), qqUserA.type); //QQ小李 qq
let qqUserB = new UserOfQq('QQ小王');
console.log(qqUserB.getName(), qqUserB.type); //QQ小王 qq
//实例化微博用户
let weiboUserA =new UserOfWeibo('微博小李');
console.log(weiboUserA.getName(), weiboUserA.type); //微博小李 weibo
let weiboUserB =new UserOfWeibo('微博小王');
console.log(weiboUserB.getName(), weiboUserB.type); //微博小王 weibo
ES6重写 简单工厂模式:
//User类
class User {
//构造器
constructor(opt) {
this.name = opt.name;
this.viewPage = opt.viewPage;
}
//静态方法
static getInstance(role) {
switch (role) {
case 'superAdmin':
return new User({ name: '超级管理员', viewPage: ['首页', '通讯录', '发现页', '应用数据', '权限管理'] });
break;
case 'admin':
return new User({ name: '管理员', viewPage: ['首页', '通讯录', '发现页', '应用数据'] });
break;
case 'user':
return new User({ name: '普通用户', viewPage: ['首页', '通讯录', '发现页'] });
break;
default:
throw new Error('参数错误, 可选参数:superAdmin、admin、user')
}
}
}
//调用
let superAdmin = User.getInstance('superAdmin');
let admin = User.getInstance('admin');
let normalUser = User.getInstance('user');
工厂方法模式:
class User {
constructor(name = '', viewPage = []) {
if(new.target === User) {
throw new Error('抽象类不能实例化!');
}
this.name = name;
this.viewPage = viewPage;
}
}
class UserFactory extends User {
constructor(name, viewPage) {
super(name, viewPage)
}
create(role) {
switch (role) {
case 'superAdmin':
return new UserFactory( '超级管理员', ['首页', '通讯录', '发现页', '应用数据', '权限管理'] );
break;
case 'admin':
return new UserFactory( '普通用户', ['首页', '通讯录', '发现页'] );
break;
case 'user':
return new UserFactory( '普通用户', ['首页', '通讯录', '发现页'] );
break;
default:
throw new Error('参数错误, 可选参数:superAdmin、admin、user')
}
}
}
let userFactory = new UserFactory();
let superAdmin = userFactory.create('superAdmin');
let admin = userFactory.create('admin');
let user = userFactory.create('user');
ES6重写抽象工厂模式:
class User {
constructor(type) {
if (new.target === User) {
throw new Error('抽象类不能实例化!')
}
this.type = type;
}
}
class UserOfWechat extends User {
constructor(name) {
super('wechat');
this.name = name;
this.viewPage = ['首页', '通讯录', '发现页']
}
}
class UserOfQq extends User {
constructor(name) {
super('qq');
this.name = name;
this.viewPage = ['首页', '通讯录', '发现页']
}
}
class UserOfWeibo extends User {
constructor(name) {
super('weibo');
this.name = name;
this.viewPage = ['首页', '通讯录', '发现页']
}
}
function getAbstractUserFactory(type) {
switch (type) {
case 'wechat':
return UserOfWechat;
break;
case 'qq':
return UserOfQq;
break;
case 'weibo':
return UserOfWeibo;
break;
default:
throw new Error('参数错误, 可选参数:superAdmin、admin、user')
}
}
let WechatUserClass = getAbstractUserFactory('wechat');
let QqUserClass = getAbstractUserFactory('qq');
let WeiboUserClass = getAbstractUserFactory('weibo');
let wechatUser = new WechatUserClass('微信小李');
let qqUser = new QqUserClass('QQ小李');
let weiboUser = new WeiboUserClass('微博小李');
总结:
-
简单工厂模式又叫静态工厂方法,用来创建某一种产品对象的实例,用来创建单一对象;工厂方法模式是将创建实例推迟到子类中进行;抽象工厂模式是对类的工厂抽象用来创建产品类簇,不负责创建某一类产品的实例。
-
何时使用工厂模式:
- 当我们的对象或者组件设置涉及到高程度级别的复杂度时。
- 当我们需要根据我们所在的环境方便的生成不同对象的实体时。
- 当我们在许多共享同一个属性的许多小型对象或组件上工作时。
- 当带有其它仅仅需要满足一种API约定(又名鸭式类型)的对象的组合对象工作时.这对于解耦来说是有用的。
- 何时不要去使用工厂模式:
- 当被应用到错误的问题类型上时,这一模式会给应用程序引入大量不必要的复杂性.除非为创建对象提供一个接口是我们编写的库或者框架的一个设计上目标,否则我会建议使用明确的构造器,以避免不必要的开销。