构建可重用接口的更好方法。

开发人员所做的最常见的事情之一是设计界面,它也是开始将不必要的复杂性引入应用程序的最容易的地方之一。构建一个超出它应该能够做的范围的功能非常容易,并且为这些功能引入不需要的副作用可能是技术债务的一个巨大原因。作为开发人员,我们希望构建可以处理完成功能所需的所有交互的界面,同时表达这些交互的相关方式和原因,并使我们的模块足够灵活,可以在应用程序的其他部分调用,并且可能更重要的是,易于测试和维护。

实现这一点的一种常见模式是类。大多数面向对象的语言都实现了类,并且 ES6 Javascript 也增加了对类的支持。类是一个很好的工具,因为它们可以帮助我们准确地实现我们上面描述的内容;它们封装了不同的功能,明确描述了哪些变量和方法可用以及它们之间的关系,并且易于扩展和构建以增加功能。他们为我们想要的东西勾选了很多框,所以很自然地,它们是许多开发人员的首选工具。

我确实认为类是一个有用的工具,但是最近几个月我发现自己越来越不喜欢它们。以下原因是我现在不喜欢它们的原因。

可扩展但不灵活

我对类的一个问题是它们是僵化的。当我们初始化一个类的实例时,这正是我们得到的;但是如果我们想改变一个类的内部机制,那么事情就会变得棘手。当然,我们可以通过扩展类或使用对象原型来覆盖方法,但是对于一个应该相对简单的操作来说,这很麻烦。类使添加新功能变得容易,但如果您需要更改任何现有功能,则需要付出更多努力。

“这个”关键字

这同时是 Javascript 最重要的特性之一,也是最令人困惑的特性之一。这可能意味着一些不同的事情,具体取决于它所使用的上下文,并且几个不同的 javascript 功能在如何绑定方面具有不同的功能(箭头函数与标准函数)。我不认为 this 关键字有什么问题,但我发现自己经常避免使用它,而不仅仅是因为它引入了复杂性。

“新”关键字

New 是一个很好的关键字,但我确实认为没有它我们也可以相处。New 在幕后为我们做了很多事情,并使类在 JS 中成为可能。我敢打赌,相当多的开发人员不知道(也不需要知道)它实际上在做什么,而且大多数人需要被告知的是“您将它们与类一起使用”。

有很多话要说“课程不是我的果酱”,所以如果你做到了这一点,谢谢你给我带来的娱乐。总而言之,我们不想使用类,因为它们有一些问题,但我们仍然希望能够构建封装功能并表达变量和方法之间关系的接口。我们希望这些比类具有可扩展性和灵活性,而不会迷失在实现细节中。

这就是串联继承的用武之地。串联继承是“将一个或多个源对象的属性组合成一个新的目标对象的过程”。​​(source)​​ ' 有时这些对象被称为混入,但如果这个定义听起来很熟悉,那就太好了!连接继承在 javascript 中很常见,你可能以前做过。如果你曾经写过这样的代码:

return {   ...objectOne,   ...objectTwo }  or  Object.assign({}, objectOne, objectTwo);

那你以前干过这种事!

本质上,这是一种跨对象共享属性的模式,而无需明确重新分配这些属性。这绝对是一种继承,但它不是人们通常认为的继承方式。开发人员往往会陷入类继承或原型继承的定义中,但是有很多方法可以在不同的对象之间共享属性。我们在Javascript中不断地动态扩展对象,很多动态扩展都是通过这种模式来完成的!

老实说,我已经说得够多了,所以让我们举一些例子来说明我的意思。我们要做的是利用 Javascript 的能力来快速、动态地组合对象,以创建可以根据需要轻松更改的界面,同时仍保留我们想要的功能。

我们要实现这一目标的最重要方法之一是使用工厂函数。如果您不熟悉这意味着什么,工厂函数是任何返回对象的函数。任何时候我们需要重复生成具有相似属性的对象,我们都可以使用工厂函数。这听起来很适合我们正在寻找的东西,所以让我们继续吧!

const instanceFactory = ({ name, propsOptions, methodsOptions }) => {   return {     ...propsInstanceFactory(name, propsOptions),     ...methodsInstanceFactory(methodsOptions)   } }  const propsInstanceFactory = (name, options) => {   return {     name,     ...options   } }  const methodsInstanceFactory = (options) => {   return {     hello() {       console.log(`Hi there! My name is ${this.name}`);     },     ...options   } }  const instance = instanceFactory({   name: 'bobby',    propsOptions,   methodsOptions, });

所以在这里,我们有三个工厂函数。我们的顶层是instanceFactory,它是我们将从中返回我们的接口的函数。这个函数接受你需要的任何参数,但注意它接受两个对象;道具选项和方法选项。然后将这些对象传递给更多的工厂函数propsInstanceFactory和methodsInstanceFactory。这些对象将被分散到返回对象中,然后返回,混合在一起成为一个单一的接口,这就是我们接下来要采取的行动。

instance.hello(); // Hi there! My name is bobby

我们所要做的就是调用我们的函数并返回我们的界面。我们不必使用 new 关键字或担心构造函数。只是一个普通的函数调用返回一个普通的对象。最重要的是,我们有很多能力来修改我们返回的内容。由于我们正在扩展我们的选项对象,我们可以传递我们想要用新数据或功能覆盖的细节。

propsOptions = { name: 'sophia' }; methodsOptions = { hello() { console.log(`This is a new hello from ${this.name}!`)} };  const secondInstance = instanceFactory({   name: 'bobby',    propsOptions,   methodsOptions, });  secondInstance.hello(); // This is a new hello from sophia!

因此,我们能够通过传递选项来覆盖我们的 name 参数,并且能够对我们的 hello 函数执行相同的操作。当我说这是一种更灵活的模式时,这就是我的意思。对于类,我们将在实例化时覆盖类的内部结构更加困难。通过这种方式,我们可以更好地控制界面的外观和行为方式。我们甚至可以简单地通过声明另一个函数来扩展我们的功能,我们不必扩展类原型并调用一个全新的类。

const instance = instanceFactory({   name: 'bobby',    propsOptions: {},   methodsOptions: {}, });  const propsOptions = { name: 'sophia' }; const methodsOptions = {    newMethod() { console.log(`A whole new method!`)}  };  const secondInstance = instanceFactory({   name: 'bobby',    propsOptions,   methodsOptions, });  secondInstance.newMethod(); // A whole new method! instance.newMethod(); // throws an error since this instance does not have this method

这个易于扩展的模型是我最喜欢这种编码风格的部分之一。对于类,我们将有更多的工作来声明一个扩展我们以前的类的新类。现在我们可以以更简单的方式扩展功能。

现在,我将介绍这种模式的另一个好处,我个人认为它是一个很大的好处。类的一个问题是它们在实例化时不支持异步操作。考虑:

class UserNotifications {   async constructor(userName) {     this.user = await getUser(userName);   } }

以上不是有效代码,这也不是:

class UserNotifications {   constructor(userName) {     this.user = getUser(userName).then(user => user);   } }

我们不能将构造函数变成异步生成器,也不能在其中执行任何异步活动。构造函数的唯一工作是构建和返回对象。因此,我们必须声明一个在类实例化后调用的 init 方法,该方法可用于执行我们需要完成的任何异步工作。

class UserNotifications {   constructor(userName) {     this.userName = userName;     this.user = null;   }   async init() {     this.user = await getUser(this.userName);   } }  const notify = new UserNotifications(myUser); await notify.init();

由于我们在初始化对象时不允许进行异步工作,因此我们定义了一个名为 init 的方法来运行我们的异步工作,我们在对象实例化后直接调用该方法。但是,如果我们不需要额外的步骤呢?实际上,我们的对象初始化和异步工作是密切相关的,因此让单独的操作来完成它们有点痛苦。

好吧,我们之前使用的函数只是普通函数,它们确实支持异步操作,因为它们只是函数。我们可以像这样重写上面的内容:

const notificationsFactory = async ({ userName, options }) => {   const user = await getUser(userName);   return {     user,     ...options   } }  const notify = await notificationsFactory({ userName, options: {});

当然我们的函数支持异步操作,只是一个普通的函数。最终,这正是我喜欢这种模式的原因;我们正在实现返回普通对象的普通函数,以构建我们将从类中获得的相同类型的接口。由于我们不依赖于类的语法糖,因此我们的代码具有更多的可预测性,并且我们最终得到了更加灵活和可扩展的接口。总的来说,我发现它是一种超级干净且易于阅读的方式来编写我的代码,我将继续以这种方式构建结构!

标签构建可重用接口的更好方法。 开发人员所做的最常见的事情之一是设计界面,它也是开始将不必要的复杂性引入应用程序的最容易的地方之一。构建一个超出它应该能够做的范围的功能非常容易,并且为这些功能引入不需要的副作用可能是技术债务的一个巨大原因。作为开发人员,我们希望构建可以处理完成功能所需的所有交互的界面,同时表达这些交互的相关方式和原因,并使我们的模块足够灵活,可以在应用程序的其他部分调用,并且可能更重要的是,易于测试和维护。 实现这一点的一种常见模式是类。大多数面向对象的语言都实现了类,并且 ES6 Javascript 也增加了对类的支持。类是一个很好的工具,因为它们可以帮助我们准确地实现我们上面描述的内容;它们封装了不同的功能,明确描述了哪些变量和方法可用以及它们之间的关系,并且易于扩展和构建以增加功能。他们为我们想要的东西勾选了很多框,所以很自然地,它们是许多开发人员的首选工具。 我确实认为类是一个有用的工具,但是最近几个月我发现自己越来越不喜欢它们。以下原因是我现在不喜欢它们的原因。 可扩展但不灵活 我对类的一个问题是它们是僵化的。当我们初始化一个类的实例时,这正是我们得到的;但是如果我们想改变一个类的内部机制,那么事情就会变得棘手。当然,我们可以通过扩展类或使用对象原型来覆盖方法,但是对于一个应该相对简单的操作来说,这很麻烦。类使添加新功能变得容易,但如果您需要更改任何现有功能,则需要付出更多努力。 “这个”关键字 这同时是 Javascript 最重要的特性之一,也是最令人困惑的特性之一。这可能意味着一些不同的事情,具体取决于它所使用的上下文,并且几个不同的 javascript 功能在如何绑定方面具有不同的功能(箭头函数与标准函数)。我不认为 this 关键字有什么问题,但我发现自己经常避免使用它,而不仅仅是因为它引入了复杂性。 “新”关键字 New 是一个很好的关键字,但我确实认为没有它我们也可以相处。New 在幕后为我们做了很多事情,并使类在 JS 中成为可能。我敢打赌,相当多的开发人员不知道(也不需要知道)它实际上在做什么,而且大多数人需要被告知的是“您将它们与类一起使用”。 有很多话要说“课程不是我的果酱”,所以如果你做到了这一点,谢谢你给我带来的娱乐。总而言之,我们不想使用类,因为它们有一些问题,但我们仍然希望能够构建封装功能并表达变量和方法之间关系的接口。我们希望这些比类具有可扩展性和灵活性,而不会迷失在实现细节中。 这就是串联继承的用武之地。串联继承是“将一个或多个源对象的属性组合成一个新的目标对象的过程”。(source) ' 有时这些对象被称为混入,但如果这个定义听起来很熟悉,那就太好了!连接继承在 javascript 中很常见,你可能以前做过。如果你曾经写过这样的代码: return { ...objectOne, ...objectTwo } or Object.assign({}, objectOne, objectTwo); 那你以前干过这种事! 本质上,这是一种跨对象共享属性的模式,而无需明确重新分配这些属性。这绝对是一种继承,但它不是人们通常认为的继承方式。开发人员往往会陷入类继承或原型继承的定义中,但是有很多方法可以在不同的对象之间共享属性。我们在Javascript中不断地动态扩展对象,很多动态扩展都是通过这种模式来完成的! 老实说,我已经说得够多了,所以让我们举一些例子来说明我的意思。我们要做的是利用 Javascript 的能力来快速、动态地组合对象,以创建可以根据需要轻松更改的界面,同时仍保留我们想要的功能。 我们要实现这一目标的最重要方法之一是使用工厂函数。如果您不熟悉这意味着什么,工厂函数是任何返回对象的函数。任何时候我们需要重复生成具有相似属性的对象,我们都可以使用工厂函数。这听起来很适合我们正在寻找的东西,所以让我们继续吧! const instanceFactory = ({ name, propsOptions, methodsOptions }) => { return { ...propsInstanceFactory(name, propsOptions), ...methodsInstanceFactory(methodsOptions) } } const propsInstanceFactory = (name, options) => { return { name, ...options } } const methodsInstanceFactory = (options) => { return { hello() { console.log(​​Hi there! My name is ${this.name}​​​); }, ...options } } const instance = instanceFactory({ name: 'bobby', propsOptions, methodsOptions, }); 所以在这里,我们有三个工厂函数。我们的顶层是instanceFactory,它是我们将从中返回我们的接口的函数。这个函数接受你需要的任何参数,但注意它接受两个对象;道具选项和方法选项。然后将这些对象传递给更多的工厂函数propsInstanceFactory和methodsInstanceFactory。这些对象将被分散到返回对象中,然后返回,混合在一起成为一个单一的接口,这就是我们接下来要采取的行动。 instance.hello(); // Hi there! My name is bobby 我们所要做的就是调用我们的函数并返回我们的界面。我们不必使用 new 关键字或担心构造函数。只是一个普通的函数调用返回一个普通的对象。最重要的是,我们有很多能力来修改我们返回的内容。由于我们正在扩展我们的选项对象,我们可以传递我们想要用新数据或功能覆盖的细节。 propsOptions = { name: 'sophia' }; methodsOptions = { hello() { console.log(​​This is a new hello from ${this.name}!​​​)} }; const secondInstance = instanceFactory({ name: 'bobby', propsOptions, methodsOptions, }); secondInstance.hello(); // This is a new hello from sophia! 因此,我们能够通过传递选项来覆盖我们的 name 参数,并且能够对我们的 hello 函数执行相同的操作。当我说这是一种更灵活的模式时,这就是我的意思。对于类,我们将在实例化时覆盖类的内部结构更加困难。通过这种方式,我们可以更好地控制界面的外观和行为方式。我们甚至可以简单地通过声明另一个函数来扩展我们的功能,我们不必扩展类原型并调用一个全新的类。 const instance = instanceFactory({ name: 'bobby', propsOptions: {}, methodsOptions: {}, }); const propsOptions = { name: 'sophia' }; const methodsOptions = { newMethod() { console.log(​​A whole new method!​​)} }; const secondInstance = instanceFactory({ name: 'bobby', propsOptions, methodsOptions, }); secondInstance.newMethod(); // A whole new method! instance.newMethod(); // throws an error since this instance does not have this method 这个易于扩展的模型是我最喜欢这种编码风格的部分之一。对于类,我们将有更多的工作来声明一个扩展我们以前的类的新类。现在我们可以以更简单的方式扩展功能。 现在,我将介绍这种模式的另一个好处,我个人认为它是一个很大的好处。类的一个问题是它们在实例化时不支持异步操作。考虑: class UserNotifications { async constructor(userName) { this.user = await getUser(userName); } } 以上不是有效代码,这也不是: class UserNotifications { constructor(userName) { this.user = getUser(userName).then(user => user); } } 我们不能将构造函数变成异步生成器,也不能在其中执行任何异步活动。构造函数的唯一工作是构建和返回对象。因此,我们必须声明一个在类实例化后调用的 init 方法,该方法可用于执行我们需要完成的任何异步工作。 class UserNotifications { constructor(userName) { this.userName = userName; this.user = null; } async init() { this.user = await getUser(this.userName); } } const notify = new UserNotifications(myUser); await notify.init(); 由于我们在初始化对象时不允许进行异步工作,因此我们定义了一个名为 init 的方法来运行我们的异步工作,我们在对象实例化后直接调用该方法。但是,如果我们不需要额外的步骤呢?实际上,我们的对象初始化和异步工作是密切相关的,因此让单独的操作来完成它们有点痛苦。 好吧,我们之前使用的函数只是普通函数,它们确实支持异步操作,因为它们只是函数。我们可以像这样重写上面的内容: const notificationsFactory = async ({ userName, options }) => { const user = await getUser(userName); return { user, ...options } } const notify = await notificationsFactory({ userName, options: {}); 当然我们的函数支持异步操作,只是一个普通的函数。最终,这正是我喜欢这种模式的原因;我们正在实现返回普通对象的普通函数,以构建我们将从类中获得的相同类型的接口。由于我们不依赖于类的语法糖,因此我们的代码具有更多的可预测性,并且我们最终得到了更加灵活和可扩展的接口。总的来说,我发现它是一种超级干净且易于阅读的方式来编写我的代码,我将继续以这种方式构建结构! 标签