什么是工厂模式?工厂模式是创建对象最常用的设计模式之一。我们不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数就可以看成是一个工厂。工厂模式按抽象程度可分为:简单工厂、工厂方法和抽象工厂。只接触过JavaScript语言的人可能对abstract这个词的概念有些模糊,因为JavaScript一直把abstract作为保留字使用,并没有实现。如果不能很好地理解抽象概念,就很难理解工厂模式中三个方法的异同。我们先来看看前面提到的工厂模式的三种实现方式:简单工厂模式、工厂方法模式、抽象工厂模式。简单工厂模式简单工厂模式也称为静态工厂模式。工厂对象决定创建某个产品对象类的实例。主要用于创建同类对象。在实际项目中,我们经常需要根据用户的权限渲染不同的页面。有些页面属于高权限的用户,低权限的用户是看不到的。所以我们可以在不同权限级别用户的构造函数中保存用户可以看到的页面。根据权限实例化用户。代码如下:UserFactory是一个简单工厂,这个函数中有3个构造函数分别对应不同权限的用户。我们在调用工厂函数的时候,只需要传入三个可选参数superAdmin、admin、user其中之一,就可以得到对应的实例对象。大家可能会发现我们这三类用户的构造函数内部很熟悉,我们可以优化一下。简单工厂的好处是你只需要一个正确的参数就可以得到你需要的对象,而无需知道它的创建细节。但是函数中包含了所有对象的创建逻辑(构造函数)和判断逻辑代码,每增加一个新的构造函数就需要修改判断逻辑代码。当我们的对象不是上面3个而是30个或者更多的时候,这个函数就会变成一个庞大的超函数,很难维护。所以简单工厂只能在创建的对象数量少,对象创建逻辑不复杂的情况下使用。工厂方法模式工厂方法模式的初衷是将对象的实际创建推迟到子类中,使核心类成为抽象类。但是很难像传统的面向对象那样在JavaScript中创建抽象类。所以在JavaScript中我们只需要参考它的核心思想即可。我们可以把工厂方法看成是实例化对象的工厂类。在简单工厂模式中,我们每增加一个构造函数就需要修改两段代码。现在我们使用工厂方法模式对上面的代码进行改造。前面说了,我们只是把工厂方法看成是实例化对象的工厂,它只为实例化对象做了一件事!我们使用安全模式来创建对象。上面的代码很好的解决了每次添加构造函数都要修改两段代码的问题。如果我们需要添加新的角色,只需要在UserFactory.prototype中添加即可。比如我们需要添加一个VipUser:在上面的代码中,使用的安全模式可能一次都难以理解。因为我们在UserFactory.prototype中保存了SuperAdmin、Admin、NormalUser等构造函数,也就是说我们必须实例化UserFactory函数来实例化上述对象。如下代码所示,在调用上述函数的过程中,一旦我们在任何阶段忘记使用new,那么就无法正确获取到superAdmin对象。但是一旦使用安全模式进行实例化,就可以很好的解决上述问题。上面的抽象工厂模式介绍了简单工厂模式和工厂方法模式都是直接生成实例,但是抽象工厂模式不同。抽象工厂模式不直接生成实例,而是用来创建产品集群。在上面的示例中,存在三个用户角色:superAdmin、admin和user。用户可以使用不同的社交媒体帐户注册,例如微信、qq和微博。那么这三类社交媒体账号就是对应的集群。在抽象工厂中,类簇一般由父类定义,在父类中定义一些抽象方法,然后子类通过抽象工厂继承父类。因此,抽象工厂实际上是实现子类继承父类的一种方法。上面所说的抽象方法是指声明了但不能使用的方法。abstract在其他传统的面向对象语言中普遍用于声明,但是在JavaScript中,abstract是一个保留字,但是我们可以通过在类方法中抛出错误来模拟抽象类。上面代码中的getPrice是一个抽象方法,我们定义了它但没有实现。如果子类继承了WechatUser但没有重写getName,子类的实例化对象会调用父类的getName方法并抛出错误信息。接下来我们实现账户管理的抽象工厂方法:AccountAbstractFactory是一个抽象工厂方法,在参数中传递子类和父类,在方法体内实现子类对父类的继承。将抽象类添加到抽象工厂方法的方法是通过点语法添加的。接下来定义普通用户的子类:以上代码分别定义了三个类:UserOfWechat、UserOfQq、UserOfWeibo。这三个类通过抽象工厂方法作为子类继承。尤其不要忘记在调用抽象工厂方法后重写抽象方法,否则在子类的实例中调用抽象方法会报错。我们分别实例化这三个类型,检测抽象工厂方法就是实现类簇的管理。从打印结果来看,抽象工厂AccountAbstractFactory很好地实现了它的功能,根据社交媒体的集群对不同的用户账户进行分类。这就是抽象工厂的作用,它不直接创建实例,而是通过类继承来管理类簇。抽象工厂模式一般用在多人协作的超大型项目中,严格要求项目以面向对象的思维来完成。ES6中的工厂模式ES6为我们提供了一种新的类语法。虽然class本质上是一个语法糖,但这并没有改变JavaScript是一门使用原型继承的语言,但它确实让对象创建和继承的过程变得更加简单。清晰度和易读性。让我们使用ES6的新语法重写上面的例子。ES6重写简单工厂模式在使用ES6重写简单工厂模式时,我们不再使用构造函数创建对象,而是使用class的新语法,并使用static关键字将简单工厂封装成类的静态方法用户类:ES6重写工厂方法模式上文我们提到,工厂方法模式的初衷是将对象的实际创建推迟到子类中,让核心类成为抽象类。但是JavaScript的abstract是保留字,并没有提供抽象类,所以我们只是借用了之前工厂方法模式的核心思想。虽然ES6没有实现abstract,但是我们可以使用new.target来模拟抽象类。new.target指向new直接执行的构造函数。我们判断new.target。如果它指向这个类,就会抛出一个错误,使这个类成为一个抽象类。让我们修改下面的代码。ES6重写抽象工厂模式抽象工厂模式不直接生成实例,而是用于创建产品集群。我们还使用new.target语法模拟抽象类,通过继承创建UserOfWechat、UserOfQq、UserOfWeibo等一系列子类簇。使用getAbstractUserFactor返回指定的集群。工厂模式项目其实应用在实际的前端业务中,最常用的简单工厂模式。如果不是非常大的项目,很难有机会使用工厂方法模式和抽象工厂方法模式。下面我介绍一下在Vue项目中实际使用的简单工厂模式的应用。在普通的vue+vue-router项目中,我们通常会将所有的路由都写到router/index.js文件中。相信Vue开发者对下面的代码会非常熟悉。总共有5条路由:涉及到权限管理页面,通常需要在用户登录时根据权限打开一个固定的访问页面,跳转到相应权限的页面。但是如果我们还是按照老方法把所有的路由都写到router/index.js文件中,那么如果低权限的用户知道高权限的路由,就可以在上面输入url跳转到高权限的页面浏览器。所以我们必须使用vue-router提供的addRoutes方法,在登录时根据用户的权限赋予相应的路由权限。这时候可以使用简单工厂方法对上面的代码进行改造。在router/index.js文件中,我们只提供了/login路由页面。我们在router/文件夹下新建一个routerFactory.js文件,导出routerFactory的简单工厂函数,用于根据用户权限提供路由权限。代码如下,在登录页面引入该方法,请求登录界面后根据权限添加路由:实际项目中,由于使用this.$router.addRoutes方法添加的路由刷新后无法保存,所以路线将无法访问。通常的做法是在本地加密保存用户信息,获取本地权限刷新后解密,根据权限重新添加路由。这里因为与工厂模式关系不大,这里不再赘述。综上所述,上面提到的三种工厂模式,和上面的单例模式一样,都是创建型设计模式。简单工厂模式也叫静态工厂方法,用于创建某个产品对象的实例,用于创建单个对象;工厂方法模式将实例的创建推迟到子类;抽象工厂模式用于对类的工厂抽象来创建产品集群,不负责创建某类产品的实例。在实际业务中,需要根据实际业务复杂度选择合适的模式。对于非大型前端应用,灵活使用简单工厂其实可以解决大部分问题。
