We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
设计模式是任何软件开发人员日常工作的一部分,不管他们是否意识到这一点。
在本文中,我们将研究如何识别这些设计模式,以及如何在自己的项目中开始使用它们。
简单地说,设计模式就是一种可以让你以某种方式更好的组织代码的方法,从而获得一些好处。比如更快的开发速度、代码的可重用性等等。
所有模式都很容易采用OOP范式。尽管考虑到JavaScript的灵活性,你也可以在非OOP项目中实现这些概念。事实上,每年都会有新的设计模式被创建,有太多的设计模式无法在一篇文章中涵盖,本文将重点介绍和实现几种常见的设计模式。
我要展示的第一个是允许同时定义和调用函数的模式。由于JavaScript作用域的工作方式,使用IIFE可以很好地模拟类中的私有属性之类的东西。事实上,这个特定的模式有时被用作其他更复杂需求的一部分。我们待会再看。
在我们深入研究用例和它背后的机制之前,让我快速地看一下它到底是什么样子的:
(function() { const x = 20 const y = 20 const answer = x + y console.log(answer) })()
通过将上述代码粘贴到浏览器的控制台,你将立即得到结果,因为正如其名所示,一旦定义了函数,就会立即执行它。
IIFE由一个匿名函数声明组成,在一组括号内(括号将定义转换为函数表达式),然后在它的尾部有一组调用括号。像这样:
(function(/*received parameters*/) { //your code here })(/*parameters*/)
还记得静态变量吗? 例如在C或c#中。使用静态变量的好处是,如果你在一个函数中定义一个静态变量,不管你调用它多少次,这个变量对函数的所有实例都是通用的。一个简单的例子是像下面这样的:
function autoIncrement() { static let number = 0 number++ return number }
上面的函数每次调用都会返回一个新的数字(当然,假设静态关键字在JS中可用)。我们可以在JS中来实现它,你可以像这样模拟一个静态变量:
let autoIncrement = (function() { let number = 0 return function () { number++ return number } })()
立即函数执行后返回一个新函数(闭包),该函数内部引用了number变量。由于JS闭包机制,立即函数执行完毕后,number变量不会立即被垃圾回收掉,所以autoIncrement每次执行总是可以访问number变量(就像它是一个全局变量一样)。
ES6类将每个成员都视为公共的,这意味着没有私有属性或方法。但是多亏了IIFE,如果你想的话,你可以模拟它。
const autoIncrementer = (function() { let value = 0 return { incr() { value++ }, get value() { return value } } })() > autoIncrementer.incr() undefined > autoIncrementer.incr() undefined > autoIncrementer.value 2 > autoIncrementer.value = 3 3 > autoIncrementer.value 2
上面的代码展示了一种创建私有变量的方法,立即函数执行后,返回一个object赋值给变量autoIncrementer,在外部只能通过value方法和incr方法获取和修改value的值。
这个模式是我最喜欢的模式之一,因为它可以让代码变动清楚简洁。
工厂模式是用来创建对象的一种最常用的设计模式。我们不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂。
这可能听起来并不是那么有用,但请看下面这个简单例子就会明白。
( _ => { let factory = new MyEmployeeFactory() let types = ["fulltime", "parttime", "contractor"] let employees = []; for(let i = 0; i < 100; i++) { employees.push(factory.createEmployee({type: types[Math.floor( (Math.random(2) * 2) )]}) )} //.... employees.forEach( e => { console.log(e.speak()) }) })()
上面的代码的关键点是通过factory.createEmployee添加了多个对象到employees数组中,所有这些对象都共享相同的接口(他们有相同的一组方法),也有自己特有的属性,但是你真的不需要关心对象创建的细节和什么时候创建。
下面我们具体看一下构造函数MyEmployeeFactory的实现
class Employee { speak() { return "Hi, I'm a " + this.type + " employee" } } class FullTimeEmployee extends Employee{ constructor(data) { super() this.type = "full time" //.... } } class PartTimeEmployee extends Employee{ constructor(data) { super() this.type = "part time" //.... } } class ContractorEmployee extends Employee{ constructor(data) { super() this.type = "contractor" //.... } } class MyEmployeeFactory { createEmployee(data) { if(data.type == 'fulltime') return new FullTimeEmployee(data) if(data.type == 'parttime') return new PartTimeEmployee(data) if(data.type == 'contractor') return new ContractorEmployee(data) } }
前面的代码已经显示了一个通用用例,但是如果我们想更具体一些,接下来使用这个模式的来处理错误对象的创建。
假设有一个大约有10个端点的Express应用程序,其中每个端点都需要根据用户输入返回2到3个错误,假设我们有30个语句大概像这样:
if(err) { res.json({error: true, message: “Error message here”}) }
如果突然需要给error对象新加一个属性,那么需要修改30个位置,这是非常麻烦的。可以过将错误对象定义到一个简单的类中解决这个问题。如果有多种类型的错误对象,这个时候就可以创建一个工厂函数来决定实例化那个错误对象。
如果你要集中创建错误对象的逻辑,那么你在整个代码中要做的就是:
if(err) { res.json(ErrorFactory.getError(err)) }
这是另一个老的但很好用的设计模式。注意,这是一个非常简单的模式,但是它可以帮助你跟踪正在实例化的类的实例数量。实际上,它可以帮助你一直保持这个数字为1。单例模式允许实例化一个对象一次,然后当每次需要时都使用它,而不是创建一个新的对象。这样可以更好的追踪对这个对象的引用。
通常,其他语言使用一个静态属性实现此模式,一旦实例存在,它们将存储该实例。这里的问题是,正如我之前提到的,我们无法访问JS中的静态变量。我们可以用两种方式实现,一种是使用IIFE而不是类。
另一种方法是使用ES6模块,并让我们的单例类使用本地全局变量来存储实例。通过这样做,类本身被导出到模块之外,但是全局变量仍然是模块的本地变量。
这听起来比看起来要复杂得多:
let instance = null class SingletonClass { constructor() { this.value = Math.random(100) } printValue() { console.log(this.value) } static getInstance() { if(!instance) { instance = new SingletonClass() } return instance } } module.exports = SingletonClass
你可以像下面这样使用:
const Singleton = require(“./singleton”) const obj = Singleton.getInstance() const obj2 = Singleton.getInstance() obj.printValue() obj2.printValue() console.log("Equals:", obj === obj2)
上面的代码输出:
0.5035326348000628 0.5035326348000628 Equals:: true
确实,我们只实例化对象一次,并返回现有实例。
在决定是否需要类似于单例的实现时,需要考虑以下问题:你真正需要多少类实例?如果答案是2或更多,那么这不是你的模式。
但是当处理数据库连接时,你可能需要考虑它。
考虑一下,一旦连接到数据库,在整个代码中保持连接活动和可访问性是一个好主意。注意,这可以用很多不同的方法来解决,但是单例模式确实是其中之一。
利用上面的例子,我们可以把它写成这样:
const driver = require("...") let instance = null class DBClass { constructor(props) { this.properties = props this._conn = null } connect() { this._conn = driver.connect(this.props) } get conn() { return this._conn } static getInstance() { if(!instance) { instance = new DBClass() } return instance } } module.exports = DBClass
现在,可以确定,无论在哪里使用getInstance方法,都将返回惟一的活动连接(如果有的话)。
上面几种是我们写代码时常用的设计模式,下一篇我们将重点深入理解观察者模式和职责链模式
关注作者github,第一时间获得更新。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
Node.js && JavaScript 面试常用的设计模式
设计模式是任何软件开发人员日常工作的一部分,不管他们是否意识到这一点。
在本文中,我们将研究如何识别这些设计模式,以及如何在自己的项目中开始使用它们。
什么是设计模式
简单地说,设计模式就是一种可以让你以某种方式更好的组织代码的方法,从而获得一些好处。比如更快的开发速度、代码的可重用性等等。
所有模式都很容易采用OOP范式。尽管考虑到JavaScript的灵活性,你也可以在非OOP项目中实现这些概念。事实上,每年都会有新的设计模式被创建,有太多的设计模式无法在一篇文章中涵盖,本文将重点介绍和实现几种常见的设计模式。
立即执行函数表达式(IIFE)
我要展示的第一个是允许同时定义和调用函数的模式。由于JavaScript作用域的工作方式,使用IIFE可以很好地模拟类中的私有属性之类的东西。事实上,这个特定的模式有时被用作其他更复杂需求的一部分。我们待会再看。
IIFE常见例子
在我们深入研究用例和它背后的机制之前,让我快速地看一下它到底是什么样子的:
通过将上述代码粘贴到浏览器的控制台,你将立即得到结果,因为正如其名所示,一旦定义了函数,就会立即执行它。
IIFE由一个匿名函数声明组成,在一组括号内(括号将定义转换为函数表达式),然后在它的尾部有一组调用括号。像这样:
用例
模拟静态变量
还记得静态变量吗? 例如在C或c#中。使用静态变量的好处是,如果你在一个函数中定义一个静态变量,不管你调用它多少次,这个变量对函数的所有实例都是通用的。一个简单的例子是像下面这样的:
上面的函数每次调用都会返回一个新的数字(当然,假设静态关键字在JS中可用)。我们可以在JS中来实现它,你可以像这样模拟一个静态变量:
立即函数执行后返回一个新函数(闭包),该函数内部引用了number变量。由于JS闭包机制,立即函数执行完毕后,number变量不会立即被垃圾回收掉,所以autoIncrement每次执行总是可以访问number变量(就像它是一个全局变量一样)。
模拟私有变量
ES6类将每个成员都视为公共的,这意味着没有私有属性或方法。但是多亏了IIFE,如果你想的话,你可以模拟它。
上面的代码展示了一种创建私有变量的方法,立即函数执行后,返回一个object赋值给变量autoIncrementer,在外部只能通过value方法和incr方法获取和修改value的值。
工厂方法模式
这个模式是我最喜欢的模式之一,因为它可以让代码变动清楚简洁。
工厂模式是用来创建对象的一种最常用的设计模式。我们不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂。
这可能听起来并不是那么有用,但请看下面这个简单例子就会明白。
上面的代码的关键点是通过factory.createEmployee添加了多个对象到employees数组中,所有这些对象都共享相同的接口(他们有相同的一组方法),也有自己特有的属性,但是你真的不需要关心对象创建的细节和什么时候创建。
下面我们具体看一下构造函数MyEmployeeFactory的实现
用例
前面的代码已经显示了一个通用用例,但是如果我们想更具体一些,接下来使用这个模式的来处理错误对象的创建。
假设有一个大约有10个端点的Express应用程序,其中每个端点都需要根据用户输入返回2到3个错误,假设我们有30个语句大概像这样:
如果突然需要给error对象新加一个属性,那么需要修改30个位置,这是非常麻烦的。可以过将错误对象定义到一个简单的类中解决这个问题。如果有多种类型的错误对象,这个时候就可以创建一个工厂函数来决定实例化那个错误对象。
如果你要集中创建错误对象的逻辑,那么你在整个代码中要做的就是:
单例模式
这是另一个老的但很好用的设计模式。注意,这是一个非常简单的模式,但是它可以帮助你跟踪正在实例化的类的实例数量。实际上,它可以帮助你一直保持这个数字为1。单例模式允许实例化一个对象一次,然后当每次需要时都使用它,而不是创建一个新的对象。这样可以更好的追踪对这个对象的引用。
通常,其他语言使用一个静态属性实现此模式,一旦实例存在,它们将存储该实例。这里的问题是,正如我之前提到的,我们无法访问JS中的静态变量。我们可以用两种方式实现,一种是使用IIFE而不是类。
另一种方法是使用ES6模块,并让我们的单例类使用本地全局变量来存储实例。通过这样做,类本身被导出到模块之外,但是全局变量仍然是模块的本地变量。
这听起来比看起来要复杂得多:
你可以像下面这样使用:
上面的代码输出:
确实,我们只实例化对象一次,并返回现有实例。
用例
在决定是否需要类似于单例的实现时,需要考虑以下问题:你真正需要多少类实例?如果答案是2或更多,那么这不是你的模式。
但是当处理数据库连接时,你可能需要考虑它。
考虑一下,一旦连接到数据库,在整个代码中保持连接活动和可访问性是一个好主意。注意,这可以用很多不同的方法来解决,但是单例模式确实是其中之一。
利用上面的例子,我们可以把它写成这样:
现在,可以确定,无论在哪里使用getInstance方法,都将返回惟一的活动连接(如果有的话)。
结论
上面几种是我们写代码时常用的设计模式,下一篇我们将重点深入理解观察者模式和职责链模式
关注作者github,第一时间获得更新。
The text was updated successfully, but these errors were encountered: