面向对象编程与函数式编程
介绍
在开始编写代码之前,我想先快速介绍一下面向对象编程和函数式编程。
这两种编程范式在允许和禁止的技术方面有所不同。
有些编程语言只支持一种范式,例如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 属性。
现在运行这段代码会报错,因为函数getElement和factorialHandler都没有定义。
所以我们先在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 块的原因。
下一步,我们将创建两个辅助函数:getValueFromElement和displayResult。
让我们将它们添加到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