尝试将功能转换为更实用的样式

时间:2019-01-31 06:27:08

标签: javascript functional-programming refactoring

我正在学习函数式编程,我想知道什么是重构波纹管功能,查看最佳实践的最佳方法

我正在重构我制作的面向对象的粒子系统,所以这里首先是我作为类制作的代码:

submitToFields(fields) {
    let totalAccelerationX = 0;
    let totalAccelerationY = 0;
    fields.forEach(field => {
      const vectorX = field.position.x - this.position.x;
      const vectorY = field.position.y - this.position.y;
      const force = Particle.calculateForce(field, vectorX, vectorY);
      totalAccelerationX += vectorX * force;
      totalAccelerationY += vectorY * force;
    });
    this.acceleration = new Vector(totalAccelerationX, totalAccelerationY);
  }
  • 该方法属于具有位置,速度,加速度等的粒子对象
  • 领域是一组字段对象,其结构类似于{position:{x:0,y:0},mass:140,...}
  • vector是{x:0,y:0}

  • 之类的对象
  • 该方法计算粒子受到粒子系统上的场(重力)影响的扰动加速度

好吧,这就是我到目前为止所取得的成就,但是我想对功能进行更多的了解,并尝试使功能编程可以为我提供更多的免费积分和其他好处。


const add = a => b => a + b;
const propX = R.prop('x');
const propY = R.prop('y');
const addVectors = (vectorA, vectorB) =>
  vector(
    add(propX(vectorA))(propX(vectorB)),
    add(propY(vectorA))(propY(vectorB)),
  );
const vector = (a, b) => ({ x: a, y: b });
const square = x => x ** 2;
const difference = a => b => a - b;
const position = item => R.prop('position', item) || 0;
const posX = item => R.prop('x', position(item)) || 0;
const posY = item => R.prop('y', position(item)) || 0;
const propMass = item => R.prop('mass', item) || 0;

const calculateForce = (mass, vectr) => {
  return mass / (square(vectr.x) + square(vectr.y) + mass) ** 1.5;
};


const disturbanceAcceleration = (part, fields) => {
  const partX = posX(part);
  const partY = posY(part);
  return fields.reduce((acc, curr) => {
    const fieldX = difference(posX(curr))(partX);
    const fieldY = difference(posY(curr))(partY);
    const force = calculateForce(propMass(curr), vector(fieldX, fieldY));
    const newVector = vector(fieldX * force, fieldY * force);
    return addVectors(acc, newVector);
  }, vector(0, 0));
};

这是一个测试:

给出:

const part = {position:{x:0,y:0}};

const fields = [
 {position:{x:100,y:100}, mass:140},
 {position:{x:200,y:100}, mass:140} , 
 {position:{x:150,y:300}, mass:140}];

执行:

disturbanceAcceleration(part,fields)

输出:

{x: 0.007947635786319896, y: 0.007256173830876778}

1 个答案:

答案 0 :(得分:4)

Vector.js

从基本的Vector模块开始,我们使用对象来保存xy属性,但要确保Vector操作adddifference,并且scale不对对象进行变异;相反,总是返回一个新的Vector-

const Vector = (x = 0, y = 0) =>
  ({ x, y })

Vector.add = (a = Vector (), b = Vector ()) =>
  Vector
    ( a.x + b.x
    , a.y + b.y
    )

Vector.difference = (a = Vector (), b = Vector ()) =>
  Vector
    ( a.x - b.x
    , a.y - b.y
    )

Vector.scale = ({ x, y } = Vector (), k = 1) =>
  Vector
    ( x * k
    , y * k
    )

// ...

export default Vector

Field.js

然后,我们构建一个Field模块,该模块取决于Vector。同样,我们确保不使用可变操作-

const Vector =
  require ('./Vector')

const Field = (mass = 0, position = Vector ()) =>
  ({ mass, position })

Field.calculateForce = ({ mass, position:{ x, y } } = Field ()) =>
  mass / (x ** 2 + y ** 2 + mass) ** 1.5

Field.disturbanceAcceleration = (origin, fields = []) =>
  fields.reduce
    ( (acc, { mass, position }) =>
      { const v =
          Vector.difference (position, origin)

        const force =
          Field.calculateForce (Field (mass, v))

        return Vector.add
          ( acc
          , Vector.scale (v, force)
          )
      }
    , Vector ()
    )

// ...

export default Field

现在为我们提供解决方案-

import Vector from './Vector'
import Field from './Field'

const fields =
  [ Field (140, Vector (100, 100))
  , Field (140, Vector (200, 100))
  , Field (140, Vector (150, 300))
  ]

Field.disturbanceAcceleration (Vector (), fields)
// { x: 0.007947635786319896, y: 0.007256173830876778 }

使用默认参数使我们可以非常方便地使用数据类型。这些是我们不认为理所当然的事情-

Vector ()         // { x: 0, y: 0 }
Vector () .x      // 0
Vector () .y      // 0
Vector (1, 2) .x  // 1
Vector (1, 2) .y  // 2

Field ()                                 // { mass: 0, position: { x: 0, y: 0 } }
Field () .mass                           // 0
Field () .position                       // { x: 0, y: 0 }
Field (100) .mass                        // 100
Field (100) .position                    // { x: 0, y: 0 }
Field (100, Vector (1, 2)) .mass         // 100
Field (100, Vector (1, 2)) .position .x  // 1
Field (100, Vector (1, 2)) .position .y  // 2

myparticle.js

您可能会得到许多子模块,这没关系。您可能希望将子模块组合成一个更大的模块,也许我们将其命名为 myparticle -

import Vector from './Vector'
import Field from './Field'
// ...

export { Vector, Field /*, ... */ }

然后,当我们在解决方案中使用它时-

import { Vector, Field /*, ... */ } from ('myparticle')

// ...

代码演示

展开以下代码段,以在您自己的浏览器中验证结果-

// Vector -------------------------------------------
const Vector = (x = 0, y = 0) =>
  ({ x, y })

Vector.add = (a = Vector (), b = Vector ()) =>
  Vector
    ( a.x + b.x
    , a.y + b.y
    )

Vector.difference = (a = Vector (), b = Vector ()) =>
  Vector
    ( a.x - b.x
    , a.y - b.y
    )

Vector.scale = ({ x, y } = Vector (), k = 1) =>
  Vector
    ( x * k
    , y * k
    )

// Field -------------------------------------------
const Field = (mass = 0, position = Vector ()) =>
  ({ mass, position })

Field.calculateForce = ({ mass, position:{ x, y } } = Field ()) =>
  mass / (x ** 2 + y ** 2 + mass) ** 1.5

Field.disturbanceAcceleration = (origin, fields = []) =>
  fields.reduce
    ( (acc, { mass, position }) =>
      { const v =
          Vector.difference (position, origin)

        const force =
          Field.calculateForce (Field (mass, v))
            
        return Vector.add
          ( acc
          , Vector.scale (v, force)
          )
      }
    , Vector ()
    )

// Demo -------------------------------------------
const fields =
  [ Field (140, Vector (100, 100))
  , Field (140, Vector (200, 100))
  , Field (140, Vector (150, 300))
  ]

const result =
  Field.disturbanceAcceleration (Vector (), fields)
  
console .log (result)
// { x: 0.007947635786319896, y: 0.007256173830876778 }


普通对象模块

在上面我们制作了一个函数 Vector,然后附加adddifferencescale作为函数的属性。这在JavaScript中有效,因为函数是对象,我们可以选择任何方式分配属性。这种设计使我们可以使用Vector作为构造函数来构造矢量,然后调用Vector.addVector.scale等进行与矢量相关的操作。

另一种设计是使Vector成为普通对象。可以将向量运算adddifferencescale添加为对象的属性。然后可以将构造函数添加为属性make或您选择的任何名称-

// Vector.js
const make = (x = 0, y = 0) =>
  ({ x, y })

const add = ...

const difference = ...

const scale = ...

export { make, add, difference, scale }

Vector不再是一个功能。而是将构造函数导出为make。现在我们使用Vector.make-

构造向量
import Vector from './Vector'

const u =
  Vector.make (1, 1) // must call Vector.make

const v =
  Vector.make (2, 3) // must call Vector.make

Vector.add (u, v)    // { x: 3, y: 4 }

无论您决定采用哪种方式打包模块,只要确保其一致性即可。