发布于 2026-01-06 7 阅读
0

面向对象编程与函数式编程

面向对象编程与函数式编程

介绍

在开始编写代码之前,我想先快速介绍一下面向对象编程和函数式编程。
这两种编程范式在允许和禁止的技术方面有所不同。
有些编程语言只支持一种范式,例如Haskell(纯函数式)。而像JavaScript
这样支持多种范式的语言,则可以使用 JavaScript 编写面向对象或函数式代码,甚至可以混合使用两者。

设置

在深入探讨这两种范式之间的差异之前,我们需要先搭建项目环境。
为此,我们首先创建所有需要的文件和文件夹,如下所示:

$ mkdir func-vs-oop
$ cd ./func-vs-oop
$ cat index.html
$ cat functional.js
$ cat oop.js 

我使用cat命令是因为它在 Linux 系统和 Windows PowerShell 中都能正常工作。

接下来,我们需要在index.html中创建一个简单的阶乘计算器表单

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
  <script src="functional.js" defer></script>
</head>
<body>
  <div class="container mt-5">
    <div class="container mt-3 mb-5 text-center">
      <h2>Functional vs OOP</h2>
    </div>
    <form id="factorial-form">
      <div class="form-group">
        <label for="factorial">Factorial</label>
        <input class="form-control" type="number" name="factorial" id="factorial" />
      </div>
      <button type="submit" class="btn btn-primary">Calculate</button>
    </form>
    <div class="container mt-3">
      <div class="row mt-4 text-center">
        <h3>Result:</h3>
        <h3 class="ml-5" id="factorial-result"></h3>
      </div>
    </div>
  </div>
</body>
</html>

为了让这个表单看起来更美观,我们使用了Bootstrap作为CSS 框架
如果您在浏览器中显示这段 HTML 代码,它应该看起来像这样:
阶乘计算

目前这个表单没有任何功能。
我们的目标是实现一个逻辑,允许用户输入一个 100 以内的数字。点击“计算”按钮后,结果应该显示在结果框 (result-div)中。
我们将分别采用面向对象和函数式编程的方式来实现这个功能。

功能实现

首先,我们将创建一个用于函数式编程方法的文件。

$ cat functional.js

首先,我们需要一个函数,该函数会在浏览器加载此文件时被调用。
此函数应获取表单,然后将我们需要的功能添加到表单的提交事件中。

function addSubmitHandler(tag, handler) {
  const form = getElement(tag);
  form.addEventListener('submit', handler);
}

addSubmitHandler("#factorial-form", factorialHandler);

首先,我们声明一个名为addSubmitHandler 的函数
该函数接受两个参数,第一个参数是我们要在 HTML 中查找的标签,第二个参数是我们要绑定到元素提交事件的函数。

接下来,我们通过传入#factorial-form和函数名factorialHandler来调用此函数。
标签前的井号 (#) 表示我们正在 HTML 中查找 id 属性。

现在运行这段代码会报错,因为函数getElementfactorialHandler都没有定义。
所以我们先在addSubmitHandler函数上方定义getElement,如下所示:

function getElement(tag) {
  return document.querySelector(tag);
}

这个函数非常简单,它只返回我们根据传入的标签找到的 HTML 元素。
不过,我们稍后会再次使用这个函数。

现在让我们通过在addSubmitHandler上方添加factorialHandler函数来开始创建核心逻辑

function factorialHandler(event) {
  event.preventDefault();

  const inputNumber = getValueFromElement('#factorial');

  try {
    const result = calculateFactorial(inputNumber);
    displayResult(result);
  } catch (error) {
    alert(error.message);
  } 
}

我们传入事件并立即调用preventDefault
这将阻止提交事件的默认行为,您可以尝试在不调用preventDefault 的情况下单击按钮会发生什么。

之后,我们调用getValueFromElement函数从输入框中获取用户输入的值。获取到该数值后,我们使用calculateFactorial
函数计算阶乘,然后将结果传递给displayResult函数,并将结果渲染到页面上

如果值格式不正确或数字大于 100,我们将抛出错误并以警告框的形式显示。
这就是为什么在这种情况下使用 try-catch 块的原因。

下一步,我们将创建两个辅助函数:getValueFromElementdisplayResult
让我们将它们添加到getElement函数下方。

function getValueFromElement(tag) {
  return getElement(tag).value;
}

function displayResult(result) {
  getElement('#factorial-result').innerHTML = result
}

这两个函数都使用了我们的`getElement`函数。这种可重用性是函数式编程如此高效的原因之一。
为了进一步提高可重用性,我们可以给`displayResult`添加第二个参数,名为 `tag`。
这样我们就可以动态地设置要显示结果的元素。
但在这个例子中,我选择了硬编码的方式。

接下来,我们在factorialHandler 的正上方创建calculateFactorial函数

function calculateFactorial(number) {
  if (validate(number, REQUIRED) && validate(number, MAX_LENGTH, 100) && validate(number, IS_TYPE, 'number')) {
    return factorial(number);
  } else {
    throw new Error(
      'Invalid input - either the number is to big or it is not a number'
    );
  }
}

我们验证参数“number”是否不为空、不大于100且类型为数字。
为此,我们使用一个名为validate的函数,该函数需要稍后创建。
如果检查通过,则调用factorial函数并返回其结果。
如果检查未通过,则抛出在factorialHandler函数中捕获的错误。

首先,让我们在displayResult下方创建validate函数以及三个常量 MAX_LENGTH、IS_TYPE 和 REQUIRED。

const MAX_LENGTH = 'MAX_LENGTH';
const IS_TYPE = 'IS_TYPE';
const REQUIRED = 'REQUIRED';

function validate(value, flag, compareValue) {
  switch (flag) {
    case REQUIRED:
      return value.trim().length > 0;
    case MAX_LENGTH:
      return value <= compareValue;
    case IS_TYPE:
      if (compareValue === 'number') {
        return !isNaN(value);
      } else if (compareValue === 'string') {
        return isNaN(value);
      }
    default:
      break;
  }
}

在这个函数中,我们使用一个 switch 语句来决定要执行哪种类型的验证。
确定验证类型后,就只是一个简单的值验证。

现在,我们将在`calculateFactorial`声明的正上方添加实际的阶乘函数。 这将是我们这种方法中的最后一个函数。

function factorial(number) {
  let returnValue = 1;
  for (let i = 2; i <= number; i++) {
    returnValue = returnValue * i;
  }
  return returnValue;
}

计算阶乘的方法有很多种,我选择了迭代法。
如果你想了解更多不同的方法,我建议你看看这篇来自 geeksforgeeks 的文章:

最终生成的functional.js文件应该如下所示:

const MAX_LENGTH = 'MAX_LENGTH';
const IS_TYPE = 'IS_TYPE';
const REQUIRED = 'REQUIRED';

function getElement(tag) {
  return document.querySelector(tag);
}

function getValueFromElement(tag) {
  return getElement(tag).value;
}

function displayResult(result) {
  getElement('#factorial-result').innerHTML = result
}

function validate(value, flag, compareValue) {
  switch (flag) {
    case REQUIRED:
      return value.trim().length > 0;
    case MAX_LENGTH:
      return value <= compareValue;
    case IS_TYPE:
      if (compareValue === 'number') {
        return !isNaN(value);
      } else if (compareValue === 'string') {
        return isNaN(value);
      }
    default:
      break;
  }
}

function factorial(number) {
  let returnValue = 1;
  for (let i = 2; i <= number; i++) {
    returnValue = returnValue * i;
  }
  return returnValue;
}

function calculateFactorial(number) {
  if (validate(number, REQUIRED) && validate(number, MAX_LENGTH, 100) && validate(number, IS_TYPE, 'number')) {
    return factorial(number);
  } else {
    throw new Error(
      'Invalid input - either the number is to big or it is not a number'
    );
  }
}

function factorialHandler(event) {
  event.preventDefault();

  const inputNumber = getValueFromElement('#factorial');

  try {
    const result = calculateFactorial(inputNumber);
    displayResult(result);
  } catch (error) {
    alert(error.message);
  } 
}

function addSubmitHandler(tag, handler) {
  const form = getElement(tag);
  form.addEventListener('submit', handler);
}

addSubmitHandler("#factorial-form", factorialHandler);

在这种方法中,我们完全使用函数。每个函数都有其单一用途,并且大多数函数都可以在应用程序的其他部分复用。
对于这个简单的 Web 应用程序来说,函数式编程有点过于复杂了。接下来,我们将使用面向对象的方式实现相同的功能。

面向对象实现

首先,我们需要将index.html文件中 script 标签的 src 属性更改为以下内容。

<script src="oop.js" defer></script>

现在我们创建oop.js文件。

$ cat oop.js

对于面向对象编程(OOP)方法,我们需要创建三个不同的类:一个用于验证,一个用于阶乘计算,一个用于处理表单。
我们首先创建处理表单的类。

class InputForm {
  constructor() {
    this.form = document.getElementById('factorial-form');
    this.numberInput = document.getElementById('factorial');

    this.form.addEventListener('submit', this.factorialHandler.bind(this));
  }

  factorialHandler(event) {
    event.preventDefault();

    const number = this.numberInput.value;

    if (!Validator.validate(number, Validator.REQUIRED) 
      || !Validator.validate(number, Validator.MAX_LENGTH, 100)
      || !Validator.validate(number, Validator.IS_TYPE, 'number'))
      {
        alert('Invalid input - either the number is to big or it is not a number');
        return;
      }

      const factorial = new Factorial(number);
      factorial.display();
  }
}

new InputForm();

在构造函数中,我们获取表单元素和输入元素,并将它们存储在类变量(也称为属性)中。之后,我们将factorialHandler
方法添加到提交事件中。 在这种情况下,我们需要将类的 this 属性绑定到该方法。 如果不这样做,就会出现引用错误,例如调用this.numberInput.value将返回 undefined。之后,我们创建factorialHandler 类方法,并将事件作为参数传递。


该方法的代码应该看起来有些眼熟,例如,if 语句会检查输入值是否有效,就像我们在calculateFacttorial函数中所做的那样。Validator.validate
调用的是Validator类中的一个静态方法,而我们还需要创建这个类。
如果使用静态方法,我们就不需要初始化对象的新实例。
验证通过后,我们会创建一个Factorial 类的新实例,传入输入值,然后将计算结果显示给用户。

接下来,我们将在InputForm 类的正上方创建Validator 类

class Validator {
  static MAX_LENGTH = 'MAX_LENGTH';
  static IS_TYPE = 'IS_TYPE';
  static REQUIRED = 'REQUIRED';

  static validate(value, flag, compareValue) {
    switch (flag) {
      case this.REQUIRED:
        return value.trim().length > 0;
      case this.MAX_LENGTH:
        return value <= compareValue;
      case this.IS_TYPE:
        if (compareValue === 'number') {
          return !isNaN(value);
        } else if (compareValue === 'string') {
          return isNaN(value);
        }
      default:
        break;
    }
  }
}

如您所见,该类中的所有内容都是静态的,包括`validate`方法以及三个属性。
因此,我们不需要构造函数。
这样做的好处是,我们无需每次使用时都初始化该类。`validate`方法与functional.js中的`validate`
函数基本相同,区别在于我们使用了`this.REQUIRED``this.MAX_LENGTH``this.IS_TYPE` ,而不是仅仅使用变量名。

接下来,我们在Validator 类下方创建Factorial 类

class Factorial {
  constructor(number) {
    this.resultElement = document.getElementById('factorial-result');
    this.number = number;
    this.factorial = this.calculate();
  }

  calculate() {
    let returnValue = 1;
    for (let i = 2; i <= this.number; i++) {
      returnValue = returnValue * i;
    }
    return returnValue;
  }

  display() {
    this.resultElement.innerHTML = this.factorial;
  }
}

初始化此类实例时,我们会获取结果元素并将其与传入的数字一起存储为一个属性。
之后,我们会调用`calculate`方法并将其返回值存储到另一个属性中。`calculate`
方法包含与functional.js中的阶乘函数相同的代码 最后,我们还有 ` display`方法,它将结果元素的`innerHTML`设置为计算出的阶乘值。

完整的oop.js文件应该如下所示。

class Validator {
  static MAX_LENGTH = 'MAX_LENGTH';
  static IS_TYPE = 'IS_TYPE';
  static REQUIRED = 'REQUIRED';

  static validate(value, flag, compareValue) {
    switch (flag) {
      case this.REQUIRED:
        return value.trim().length > 0;
      case this.MAX_LENGTH:
        return value <= compareValue;
      case this.IS_TYPE:
        if (compareValue === 'number') {
          return !isNaN(value);
        } else if (compareValue === 'string') {
          return isNaN(value);
        }
      default:
        break;
    }
  }
}

class Factorial {
  constructor(number) {
    this.resultElement = document.getElementById('factorial-result');
    this.number = number;
    this.factorial = this.calculate();
  }

  calculate() {
    let returnValue = 1;
    for (let i = 2; i <= this.number; i++) {
      returnValue = returnValue * i;
    }
    return returnValue;
  }

  display() {
    this.resultElement.innerHTML = this.factorial;
  }
}

class InputForm {
  constructor() {
    this.form = document.getElementById('factorial-form');
    this.numberInput = document.getElementById('factorial');

    this.form.addEventListener('submit', this.factorialHandler.bind(this));
  }

  factorialHandler(event) {
    event.preventDefault();

    const number = this.numberInput.value;

    if (!Validator.validate(number, Validator.REQUIRED) 
      || !Validator.validate(number, Validator.MAX_LENGTH, 100)
      || !Validator.validate(number, Validator.IS_TYPE, 'number'))
      {
        alert('Invalid input - either the number is to big or it is not a number');
        return;
      }

      const factorial = new Factorial(number);
      factorial.display();
  }
}

new InputForm();

我们创建了三个不同的类,分别处理应用程序的三个不同方面:

  • 验证:验证类
  • 阶乘处理:阶乘类
  • 表单处理:InputForm 类

结论

两种方法都是有效的代码结构方式。
我个人喜欢在不同的项目中尝试哪种方法效果最好。
大多数情况下,这两种范式很难如此清晰地区分开来。
我希望这个简单的比较能让你对不同的方法有一个基本的了解。

和往常一样,你可以在我的GitHub上找到这个项目的代码

文章来源:https://dev.to/klamserdev/oop-vs-function-programming-39ij