从ES6重新认识JavaScript设想形式(二): 工场形式

1 什么是工场形式?

工场形式是用来建立对象的一种最经常运用的设想形式。我们不暴露建立对象的详细逻辑,而是将将逻辑封装在一个函数中,那末这个函数就可以被视为一个工场。工场形式依据笼统水平的差别可以分为:简朴工场,工场要领和笼统工场。

假如只打仗过JavaScript这门言语的的人可以会对笼统这个词的观点有点隐约,由于JavaScript一向将abstract作为保留字而没有去完成它。假如不能很好的邃晓笼统的观点,那末就很难邃晓工场形式中的三种要领的异同。所以,我们先以一个场景去简朴的报告一下笼统和工场的观点。

设想一下你的女朋友华诞要到了,你想晓得她想要什么,因而你问她:“亲爱的,华诞要到了你想要什么华诞礼物啊?”

正巧你女朋友是个猫奴,最经迷上了抖音上的一只超等可爱的苏格兰折耳猫,她也很想要一只网红同款猫。

因而她回复你说:“亲爱的,我想要一只动物。”

你平心静气的问她:“想要什么动物啊?”

你女友说:“我想要猫科动物。”

这时刻你心田就疑惑了,猫科动物有山君,狮子,豹子,猞猁,另有种种小猫,我那里晓得你要什么?

因而你问女友:“你要哪一种猫科动物啊?”

“笨死了,还要哪一种,肯定是小猫咪啊,岂非我们家还能像迪拜土豪那样养山君啊!”你女朋友答道。

“好好, 那你想要哪一个品种的猫呢?”你问道

“我想要外国的品种, 不要中国的土猫” 你女友傲娇的回复到。

这时刻你已快奔溃了,作为程序员的你再也受不了这类挤牙膏式的发问,因而你乞求到:“亲爱的,你就直接告诉我你究竟想要哪一个品种,哪一个色彩,多大的猫?”

你女友想了想抖音的那只猫,回复道:“我想要一只灰色的,不凌驾1岁的苏格兰短耳猫!”

因而,你在女友华诞当天到全国最大的宠物批发市场内里去,挑了一只“灰色的,不凌驾1岁的苏格兰短耳猫”回家送给了你女友, 圆了你女友具有网红同款猫的妄想!

上面中你终究买到并送给女友那只猫可以被看做是一个实例对象宠物批发市场可以看做是一个工场,我们可以以为它是一个函数,这个工场函数内里有着林林总总的动物,那末你是怎样猎取到实例的呢?由于你给宠物批发市场通报了准确的参数, “color: 灰色”“age: 不凌驾1岁”“breed:苏格兰短耳”,**“category:
猫”。前面的对话中, 你女朋友回复“动物”,“猫科动物”,“外洋的品种”让你不邃晓她究竟想要什么,就是由于她说得太笼统了。她回复的是一大类动物的共有特性而不是详细动物,这类将庞杂事物的一个或多个共有特性抽取出来的思维历程就是笼统**。

既然已邃晓了笼统的观点,下面我们来看一下之前提到的工场形式的三种完成要领: 简朴工场形式、工场要领形式、笼统工场形式。

1.1 简朴工场形式

简朴工场形式又叫静态工场形式,由一个工场对象决议建立某一种产物对象类的实例。重要用来建立统一类对象。

在现实的项目中,我们经常须要依据用户的权限来衬着差别的页面,高等权限的用户所具有的页面有些是没法被初级权限的用户所检察。所以我们可以在差别权限品级用户的组织函数中,保留该用户可以看到的页面。在依据权限实例化用户。代码以下:

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个或更多时,这个函数会成为一个巨大的超等函数,便得难以保护。所以,简朴工场只能作用于建立的对象数目较少,对象的建立逻辑不庞杂时运用

1.2 工场要领形式

工场要领形式的本意是将现实建立对象的事变推晚到子类中,如许中心类就变成了笼统类。然则在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')

上面的这段代码就很好的处理了每增加一个组织函数就须要修正两处代码的题目,假如我们须要增加新的角色,只须要在UserFactory.prototype中增加。比方,我们须要增加一个VipUser:

UserFactory.prototype = {
  //....
  VipUser: function() {
    this.name = '付费用户',    
    this.viewPage = ['首页', '通讯录', '发明页', 'VIP页']
  }
}

//挪用
let vipUser = UserFactory('VipUser');

上面的这段代码中,运用到的平安形式可以很难一次就可以邃晓。

let UserFactory = function(role) {
  if(this instanceof UserFactory) {
    var s = new this[role]();
    return s;
  } else {
    return new UserFactory(role);
  }
}

由于我们将SuperAdminAdminNormalUser等组织函数保留到了UserFactory.prototype中,也就意味着我们必需实例化UserFactory函数才可以举行以上对象的实例化。以下面代码所示

let UserFactory = function() {}

UserFactory.prototype = {
 //...
}

//挪用
let factory = new UserFactory();
let superAdmin = new factory.SuperAdmin();

在上面的挪用函数的历程当中, 一旦我们在任何阶段遗忘运用new, 那末就没法准确猎取到superAdmin这个对象。然则一旦运用平安形式去举行实例化,就可以很好处理上面的题目。

1.3 笼统工场形式

上面引见了简朴工场形式和工场要领形式都是直接天生实例,然则笼统工场形式差别,笼统工场形式并不直接天生实例, 而是用于对产物类簇的建立。

上面例子中的superAdminadminuser三种用户角色,个中user多是运用差别的交际媒体账户举行注册的,比方:wechatqqweibo。那末这三类交际媒体账户就是对应的类簇。在笼统工场中,类簇平常用父类定义,并在父类中定义一些笼统要领,再经由历程笼统工场让子类继续父类。所以,笼统工场现实上是完成子类继续父类的要领

上面提到的笼统要领是指声明但不能运用的要领。在其他传统面向对象的言语中经常运用abstract举行声明,然则在JavaScript中,abstract是属于保留字,然则我们可以经由历程在类的要领中抛出毛病来模仿笼统类。

let WechatUser = function() {}
WechatUser.prototype = {
  getName: function() {
    return new Error('笼统要领不能挪用');
  }
}

上述代码中的getPrice就是笼统要领,我们定义它然则却没有去完成。假如子类继续WechatUser然则并没有去重写getName,那末子类的实例化对象就会挪用父类的getName要领并抛出毛病提醒。

下面我们离别来完成账号治理的笼统工场要领:

let AccountAbstractFactory = function(subType, superType) {
  //推断笼统工场中是不是有该笼统类
  if(typeof AccountAbstractFactory[superType] === 'function') {
    //缓存类
    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('笼统要领不能挪用');
  }
}

AccountAbstractFactory 就是一个笼统工场要领,该要领在参数中通报子类和父类,在要领体内部完成了子类对父类的继续。对笼统工场要领增加笼统类的要领我们是经由历程点语法举行增加的。

下面我们来定义一般用户的子类:

//一般微信用户子类
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;
}

上述代码我们离别定义了UserOfWechatUserOfQqUserOfWeibo三品种。这三个类作为子类经由历程笼统工场要领完成继续。迥殊须要注重的是,挪用笼统工场要领后不要遗忘重写笼统要领,否则在子类的实例中挪用笼统要领会报错。

我们来离别对这三品种举行实例化,检测笼统工场要领是完成了类簇的治理。

//实例化微信用户
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

从打印效果上看,AccountAbstractFactory这个笼统工场很好的完成了它的作用,将差别用户账户根据交际媒体这一个类簇举行了分类。这就是笼统工场的作用,它不直接建立实例,而是经由历程类的继续举行类簇的治理。笼统工场形式平常用在多人合作的超大型项目中,而且严厉的要求项目以面向对象的头脑举行完成。

2 ES6中的工场形式

ES6中给我们供应了class新语法,虽然class本质上是一颗语法糖,并也没有转变JavaScript是运用原型继续的言语,然则确切让对象的建立和继续的历程变得越发的清楚和易读。下面我们运用ES6的新语法来重写上面的例子。

2.1 ES6重写简朴工场形式

运用ES6重写简朴工场形式时,我们不再运用组织函数建立对象,而是运用class的新语法,并运用static关键字将简朴工场封装到User类的静态要领中:

//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');

2.2 ES6重写工场要领形式

在上文中我们提到,工场要领形式的本意是将现实建立对象的事变推晚到子类中,如许中心类就变成了笼统类。然则JavaScript的abstract是一个保留字,并没有供应笼统类,所以之前我们只是自创了工场要领形式的中心头脑。

虽然ES6也没有完成abstract,然则我们可以运用new.target来模仿出笼统类。new.target指向直接被new实行的组织函数,我们对new.target举行推断,假如指向了该类则抛出毛病来使得该类成为笼统类。下面我们来革新代码。

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');

2.3 ES6重写笼统工场形式

笼统工场形式并不直接天生实例, 而是用于对产物类簇的建立。我们一样运用new.target语法来模仿笼统类,并经由历程继续的体式格局建立出UserOfWechat, UserOfQq, UserOfWeibo这一系列子类类簇。运用getAbstractUserFactor来返回指定的类簇。

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('微博小李');

3 工场形式的项目实战运用

在现实的前端营业中,最经常运用的简朴工场形式。假如不是超大型的项目,是很难有机会运用到工场要领形式和笼统工场要领形式的。下面我引见在Vue项目中现实运用到的简朴工场形式的运用。

在一般的vue + vue-router的项目中,我们一般将一切的路由写入到router/index.js这个文件中。下面的代码我置信vue的开发者会异常熟习,总共有5个页面的路由:

// index.js

import Vue from 'vue'
import Router from 'vue-router'
import Login from '../components/Login.vue'
import SuperAdmin from '../components/SuperAdmin.vue'
import NormalAdmin from '../components/Admin.vue'
import User from '../components/User.vue'
import NotFound404 from '../components/404.vue'

Vue.use(Router)

export default new Router({
  routes: [
    //重定向到登录页
    {
      path: '/',
      redirect: '/login'
    },
    //上岸页
    {
      path: '/login',
      name: 'Login',
      component: Login
    },
    //超等治理员页面
    {
      path: '/super-admin',
      name: 'SuperAdmin',
      component: SuperAdmin
    },
    //一般治理员页面
    {
      path: '/normal-admin',
      name: 'NormalAdmin',
      component: NormalAdmin
    },
    //一般用户页面
    {
      path: '/user',
      name: 'User',
      component: User
    },
    //404页面
    {
      path: '*',
      name: 'NotFound404',
      component: NotFound404
    }
  ]
})

当触及权限治理页面的时刻,一般须要在用户上岸依据权限开放牢固的接见页面并举行响应权限的页面跳转。然则假如我们照样根据老办法将一切的路由写入到router/index.js这个文件中,那末低权限的用户假如晓得高权限路由时,可以经由历程在浏览器上输入url跳转到高权限的页面。所以我们必需在上岸的时刻依据权限运用vue-router供应的addRoutes要领赋予用户相对应的路由权限。这个时刻就可以运用简朴工场要领来革新上面的代码。

router/index.js文件中,我们只供应/login这一个路由页面。

//index.js

import Vue from 'vue'
import Router from 'vue-router'
import Login from '../components/Login.vue'

Vue.use(Router)

export default new Router({
  routes: [
    //重定向到登录页
    {
      path: '/',
      redirect: '/login'
    },
    //上岸页
    {
      path: '/login',
      name: 'Login',
      component: Login
    }
  ]
})

我们在router/文件夹下新建一个routerFactory.js文件,导出routerFactory简朴工场函数,用于依据用户权限供应路由权限,代码以下

//routerFactory.js

import SuperAdmin from '../components/SuperAdmin.vue'
import NormalAdmin from '../components/Admin.vue'
import User from '../components/User.vue'
import NotFound404 from '../components/404.vue'

let AllRoute = [
  //超等治理员页面
  {
    path: '/super-admin',
    name: 'SuperAdmin',
    component: SuperAdmin
  },
  //一般治理员页面
  {
    path: '/normal-admin',
    name: 'NormalAdmin',
    component: NormalAdmin
  },
  //一般用户页面
  {
    path: '/user',
    name: 'User',
    component: User
  },
  //404页面
  {
    path: '*',
    name: 'NotFound404',
    component: NotFound404
  }
]

let routerFactory = (role) => {
  switch (role) {
    case 'superAdmin':
      return {
        name: 'SuperAdmin',
        route: AllRoute
      };
      break;
    case 'normalAdmin':
      return {
        name: 'NormalAdmin',
        route: AllRoute.splice(1)
      }
      break;
    case 'user':
      return {
        name: 'User',
        route:  AllRoute.splice(2)
      }
      break;
    default: 
      throw new Error('参数毛病! 可选参数: superAdmin, normalAdmin, user')
  }
}

export { routerFactory }

在上岸页导入该要领,要求上岸接口后依据权限增加路由:

//Login.vue

import {routerFactory} from '../router/routerFactory.js'
export default {
  //... 
  methods: {
    userLogin() {
      //要求上岸接口, 猎取用户权限, 依据权限挪用this.getRoute要领
      //..
    },
    
    getRoute(role) {
      //依据权限挪用routerFactory要领
      let routerObj = routerFactory(role);
      
      //给vue-router增加该权限所具有的路由页面
      this.$router.addRoutes(routerObj.route);
      
      //跳转到响应页面
      this.$router.push({name: routerObj.name})
    }
  }
};

在现实项目中,由于运用this.$router.addRoutes要领增加的路由革新后不能保留,所以会致使路由没法接见。一般的做法是当地加密保留用户信息,在革新后猎取当地权限并解密,依据权限从新增加路由。这里由于和工场形式没有太大的关联就不再赘述。

总结

上面说到的三种工场形式和上文的单例形式一样,都是属于建立型的设想形式。简朴工场形式又叫静态工场要领,用来建立某一种产物对象的实例,用来建立单一对象;工场要领形式是将建立实例推晚到子类中举行;笼统工场形式是对类的工场笼统用来建立产物类簇,不担任建立某一类产物的实例。在现实的营业中,须要依据现实的营业庞杂度来挑选适宜的形式。关于非大型的前端运用来讲,天真运用简朴工场实在就可以处理大部分题目。

    原文作者:李棠辉
    原文地址: https://segmentfault.com/a/1190000014196851
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞