函数式编程、面向对象编程、过程式编程
简介
这是一个真实的例子,展示了三种最常见的编程范式之间的区别。我将用三种不同的方式解决同一个问题。 它基于Academind 视频 ,但最终我的解决方案略有不同。
每个示例都会处理表单提交、验证用户输入并将创建的用户信息打印到控制台。我还添加了保存错误记录器。
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<!-- <script src="procedural.js" defer></script> -->
<!-- <script src="oop.js" defer></script> -->
<!-- <script src="functional.js" defer></script> -->
</head>
<body>
<form id="user-form">
<div>
<label for="username">Username</label>
<input id="username" />
</div>
<div>
<label for="password">Password</label>
<input id="password" type="password" />
</div>
<button type="submit">Submit</button>
</form>
</body>
</html>
简单的 HTML 登录表单,它将包含三个js
不同范例的有效文件。
过程编程
过程式编程就是逐步解决问题。这是一种完全有效的编码方式,但当你希望应用程序扩展时,它会有很多缺点。
const form = document.querySelector('form')
const logs = []
form.addEventListener('submit', e => {
e.preventDefault()
const username = e.target.elements.username.value
const password = e.target.elements.password.value
let error = ''
if (username.trim().length < 3)
error = 'Username must be at least 3 characters long'
else if (!password.match(/[0-9]/))
error = 'Password must contain at least one digit'
if (error) {
logs.push(error)
alert(error)
return
}
const user = {
username,
password,
}
console.log(user)
console.log(logs)
})
这个问题的分步解决方案很简单。但它完全不可重用,也完全不可扩展。虽然它完全可以有效解决这类问题,而且你会发现它比其他方法要短得多。
面向对象编程
面向对象编程(OOP)最贴近现实世界,所以很容易理解。我们将代码划分为多个对象,每个对象只完成其各自的工作。
在OOP中学习的有用概念是SOLID。
// Class responsible only for logging
class Logger {
static logs = []
static showAlert(message) {
this.logs.push(message)
alert(message)
}
}
// Class responsible only for validating input
class Validator {
static flags = {
minLength: 'MIN-LENGTH',
hasDigit: 'HAS-DIGIT',
}
static validate(value, flag, validatorValue) {
if (flag === this.flags.minLength) {
return value.trim().length >= validatorValue
}
if (flag === this.flags.hasDigit) {
return value.match(/[0-9]/)
}
}
}
// Class responsible only for creating valid user
class User {
constructor(username, password) {
if (!Validator.validate(username, Validator.flags.minLength, 3))
throw new Error('Username must be at least 3 characters long')
if (!Validator.validate(password, Validator.flags.hasDigit))
throw new Error('Password must contain at least one digit')
this.username = username
this.password = password
}
}
// Class responsible only for from handling
class FormHandler {
constructor(formElement) {
this.form = formElement
this.form.addEventListener('submit', this.handleSubmit.bind(this))
}
handleSubmit(e) {
e.preventDefault()
const username = e.target.elements.username.value
const password = e.target.elements.password.value
try {
const user = new User(username, password)
console.log(user)
console.log(Logger.logs)
} catch (err) {
Logger.showAlert(err)
}
}
}
const form = document.querySelector('form')
new FormHandler(form)
现在你应该明白我把问题分解成Objects的意义了。FormHandler是一个独立的类,负责处理表单。User是另一个类,负责创建用户并使用Validator类验证输入。如果出现错误,Logger类会显示警告并保存日志。
正如您所看到的,代码更多,而且看起来更复杂...那么为什么有人会更喜欢它而不是Procedura范例呢?
很酷的是,现在我们只需调用它就可以将它用于任何类似的形式
new FormHandler(new_form)
因此,它可以在包含此脚本的每个文件中重用。而且它易于扩展,因为所有内容都被划分为只执行一项任务的块(单一职责原则)。
功能
最后是我最喜欢的范式。在我写这篇文章的时候,它非常流行,而且非常直观。
请注意,这并不意味着它更好。
尽管某些范例可能更适合某些问题,但使用哪种范例完全取决于您。
const FLAGS = {
minLength: 'MIN-LENGTH',
hasDigit: 'HAS-DIGIT',
}
// Function that handles validation
const validate = (value, flag, validatorValue) => {
switch(flag){
case FLAGS.minLength:
return value.trim().length >= validatorValue
case FLAGS.hasDigit:
return !!value.match(/[0-9]/)
}
}
// Function that sets submit handler
const setFormSubmitHandler = (formId, onSubmit) => {
const form = document.getElementById(formId)
form.addEventListener('submit', onSubmit)
}
// Function that returns values of required fields as object
// In this case it will return {username: "<value>", password: "<value>"}
// It might look scary but keep in mind that it's completely reusable
const getFormValues = (e, ...fields) => {
const values = Object.entries(e.target.elements)
const filteredValues = values.filter(([key]) => fields.includes(key))
return filteredValues.reduce(
(acc, [key, { value }]) => ({ ...acc, [key]: value }),
{}
)
}
// Function that creates valid user
const createUser = (username, password) => {
if (!validate(username, FLAGS.minLength, 3))
throw new Error('Username must be at least 3 characters long')
if (!validate(password, FLAGS.hasDigit))
throw new Error('Password must contain at least one digit')
return { username, password }
}
// Function that creates logger object with *logs* and *showAlert* function
const logger = (() => {
const logs = []
return {
logs,
showAlert: message => {
logs.push(message)
alert(message)
},
}
})()
// Main function
const handleSubmit = e => {
e.preventDefault()
const { username, password } = getFormValues(e, 'username', 'password')
try {
const user = createUser(username, password)
console.log(user)
console.log(logger.logs)
} catch (error) {
logger.showAlert(error)
}
}
setFormSubmitHandler('user-form', handleSubmit)
正如你在函数式编程中所看到的,我们希望使用小型(理想情况下是纯)函数来解决问题。这种方法也具有很高的可扩展性,并且函数可以重用。
纯函数是指没有难以追踪的副作用的函数。
纯函数应该只依赖于给定的参数。
结论
没有更好或更坏的范式。经验丰富的开发人员能够看到每种范式的优势,并针对特定问题选择最佳方案。
过程式编程并不禁止使用函数,函数式编程也不禁止使用Class。这些范例只是以一种随着代码增长而受益的方式帮助解决问题。
文章来源:https://dev.to/jjablonskiit/function-vs-object-oriented-vs-procedural-programming-2lc5