函數語言程式設計 vs 物件導向程式設計 vs 程式式程式設計的JS演示比較 - DEV

banq發表於2021-08-11

這是一個真實的例子,展示了三種最常見的程式設計正規化的差異。我將用三種不同的方式解決一個問題。
每個示例將處理表單提交、驗證使用者輸入並將建立的使用者列印到控制檯。我還新增了儲存錯誤記錄器。
 

案例表單

<!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 ) 是最接近現實世界的,因此很容易讓您思考。我們檢視將其劃分為Object的程式碼,其中每個都只完成它的工作。在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類用於顯示警報並儲存日誌。

正如你所看到的,有更多的程式碼,看起來更復雜……那麼為什麼有人會喜歡這個?
酷的是,現在我們可以將它用於任何類似的形式,只需呼叫:

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)


正如您在函數語言程式設計中看到的,我們希望使用小的(理想情況下是純函式)函式來解決問題。這種方法也非常具有可擴充套件性,並且函式可以重用。
純函式是一種沒有難以追蹤的副作用的函式。純函式應該只依賴於給定的引數。
 

結論
沒有更好和更壞的正規化。有經驗的開發人員可以看到每個的優點,併為給定的問題選擇最好的。
程式式程式設計並不是說你不能使用函式,函數語言程式設計也不會阻止你使用“類”。這些正規化只是幫助以一種隨著程式碼增長而有益的方式來解決問題。



 

相關文章