paint-brush
什么是把握? JavaScript 中的信息专家和创造者原则经过@serhiirubets
12,133 讀數
12,133 讀數

什么是把握? JavaScript 中的信息专家和创造者原则

经过 Serhii Rubets1m2022/06/07
Read on Terminal Reader
Read this story w/o Javascript

太長; 讀書

GRASP 缩写为 *General Responsibility Responsibility Assignment Software Patterns。这是一组非常好的建议、原则和模式,可以让我们的代码变得更好。今天,我们有许多最佳实践、原则(SOLID、DRY、GoF、KISS 等)和其他,它们都在努力帮助我们,编写好的、可维护的代码和可理解的代码。我们想要创建功能,它将计算我们订购商品的总和。我们将学习前两个原则:信息专家和创造者。

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - 什么是把握? JavaScript 中的信息专家和创造者原则
Serhii Rubets HackerNoon profile picture

今天,我们有许多好的实践、原则(SOLID、DRY、KISS)、GoF 模式等等。

他们都在努力帮助我们这些开发人员编写好的、干净、可维护和可理解的代码。


GRASP 是General Responsibility Assignment Software Patterns 的缩写。


这是一组非常好的建议、原则和模式,可以让我们的代码变得更好。让我们看一下这个列表:


  • 信息专家
  • 创作者
  • 控制器
  • 低耦合
  • 高凝聚力
  • 纯制造
  • 间接的
  • 受保护的变体
  • 多态性


今天,我们将学习前两个原则:信息专家和创造者。

信息专家

信息专家可能是所有 GRASP 模式中最重要的。该模式表示所有处理数据(变量、字段)的方法都应该位于数据(变量或字段)存在的同一位置。


我知道,我知道,这听起来不太清楚,所以让我们看一个例子。我们想创建一个功能来计算我们订购的物品的总和。


假设我们有 4 个文件:main.js、OrderList、OrderItem 和 Product。


产品可能包含 id、name 和 price(以及许多其他字段,与我们的示例无关):


 class Product { constructor(name, price) { this.name = name; this.price = price; } }


OrderItem 将是一个包含产品和计数的简单对象,如下所示:


 class OrderItem { constructor(product, count) { this.product = product, this.count = count } }


OrderList 文件将包含处理 orderItems 数组的逻辑。


 class OrderList { constructor(items) { this.items = items; } }


而且,main.js 只是那个可以包含一些初始逻辑的文件,可以导入 OrderList,并对这个列表做一些事情。


 import { OrderItem } from './OrderItem'; import { OrderList } from './OrderList'; import { Product } from './Product'; const samsung = new Product('Samsung', 200); const apple = new Product('Apple', 300); const lg = new Product('Lg', 150); const samsungOrder = new OrderItem(samsung, 2); const appleOrder = new OrderItem(samsung, 3); const lgOrder = new OrderItem(samsung, 4); const orderList = new OrderList([samsungOrder, appleOrder, lgOrder]);


应该在哪里创建计算总和的方法?至少有 2 个文件,每个文件都可以用于此目的,对吗?但是哪个地方更适合我们的目标呢?


让我们考虑一下 main.js。


我们可以这样写:


 const totalSum = orderList.reduce((res, order) => { return res + order.product.price * order.count }, 0)


它会起作用的。但是,orderItem 文件包含没有方法的数据,orderList 文件也包含没有方法的数据,主文件包含一个处理订单项目和订单列表的方法。


听起来不太好。如果我们想以某种方式添加更多与订单一起使用的逻辑,我们是否也将其放在主文件中?而且,一段时间后,我们的主文件会有很多不同的逻辑,有数千行代码,这真的很糟糕。这种反模式称为God object ,其中 1 个文件包含所有内容。


如果我们想使用信息专家的方法应该怎么做?让我们尝试重复:


所有处理数据(变量、字段)的方法都应该在数据(变量或字段)存在的地方。


这意味着: orderItem 应该包含可以计算特定项目总和的逻辑:


 class OrderItem { constructor(product, count) { this.product = product, this.count = count } getTotalPrice() { return this.product.price * this.count; } }


并且 orderList 应该包含可以计算所有订单项目总和的逻辑:


 class OrderList { constructor(items) { this.items = items; } getTotalPrice() { return this.items.reduce((res, item) => { return res + item.getTotalPrice(); }, 0); } }


而且,我们的主文件将很简单,不会包含该功能的逻辑;它将尽可能简单(除了许多导入,我们将很快删除)。


因此,任何仅与一个订单项相关的逻辑都应放置在 orderItem 中。

如果某些东西相对地使用一组 orderItems,我们应该把这个逻辑放在 orderItems 中。


我们的主文件应该只是一个入口点;做一些准备和导入,并将一些逻辑与其他逻辑联系起来。


这种分离为我们提供了代码组件之间的少量依赖关系,这就是我们的代码更易于维护的原因。


我们不能总是在我们的项目中使用这个原则,但它是一个非常好的原则。如果你可以使用它,你应该这样做。

创作者

在我们之前的示例中,我们有 4 个文件:Main、OrderList、OrderItem 和 Product。信息专家说方法应该在哪里:在数据所在的同一个地方。


但问题是:应该在谁和哪里创建对象?谁将创建 orderList,谁将创建 orderItem,谁将创建 Product?


Creator 说每个对象(类)都应该只在使用它的地方创建。还记得我们在包含许多导入的主文件中的示例吗?让我们检查:


 import { OrderItem } from './OrderItem'; import { OrderList } from './OrderList'; import { Product } from './Product'; const samsung = new Product('Samsung', 200); const apple = new Product('Apple', 300); const lg = new Product('Lg', 150); const samsungOrder = new OrderItem(samsung, 2); const appleOrder = new OrderItem(samsung, 3); const lgOrder = new OrderItem(samsung, 4); const orderList = new OrderList([samsungOrder, appleOrder, lgOrder]); const totalSum = orderList.getTotalPrice();


正如我们所见,几乎所有的导入和对象创建都在 main.js 中。


但是,让我们考虑一下它的真正使用对象和位置。


该产品仅在 OrderItem 中使用。 OrderItem 仅在 OrderList 中使用。 OrderList 用于 Main。它看起来像这样:


Main → OrderList → OrderItem → 产品


但是如果Main只使用OrderList,为什么我们要在Main中创建OrderItem呢?为什么我们还要在这里创建一个产品?此刻,我们的 Main.js 创建(和导入)几乎所有东西。这不好。


遵循造物主原则,我们应该只在使用这些对象的地方创建对象。想象一下,使用我们的应用程序,我们将产品添加到购物车。这就是它的样子:


Main.js:我们在这里只创建(和导入)OrderList:


 import { OrderList } from './OrderList'; const cartProducts = [{ name: 'Samsung', price: 200, count: 2 }, { name: 'Apple', price: 300, count: 3 }, {name: 'Lg', price: 150, count: 4 }]; const orderList = new OrderList(cartProducts); const totalPrice = orderList.getTotalPrice();


OrderList.js:我们在这里只创建(和导入)OrderItem:


 import { OrderItem } from './OrderItem'; class OrderList { constructor(items) { this.items = items.map(item => new OrderItem(item)); } getTotalPrice() { return this.items.reduce((res, item) => { return res + item.getPrice(); }, 0); } }


OrderItem.js:我们在这里只创建(和导入)产品:


 import { Product } from './Product'; class OrderItem { constructor(item) { this.product = new Product(item.name, item.price); this.count = item.count; } }


产品.js:


 class Product { constructor(name, price) { this.name = name; this.price = price; } }


我们有一个简单的依赖:


主 → OrderList → OrderItem → 产品


现在,每个对象只在使用它的地方创建。这就是造物主原则所说的。


我希望这个介绍对你有用,在下一个系列的GRASP 中,我们将介绍其他原则。


Gabriel HeinzerUnsplash上拍摄的照片