面向對象與函數式編程的簡單案例

做者:Jakob Klamser

翻譯:瘋狂的技術宅javascript

原文:https://dev.to/jakobkit/oop-v...css

未經容許嚴禁轉載html

介紹

先簡要介紹一下面向對象和函數式編程。前端

二者都是編程範式,在容許和禁止的技術上有所不一樣。java

有僅支持一種範式的編程語言,例如 Haskell(純函數式)。程序員

還有支持多種範式的語言,例如 JavaScript,你能夠用 JavaScript 編寫面向對象的代碼或函數式代碼,甚至能夠將二者混合。面試

建立項目

在深刻探究這兩種編程範式之間的差別以前,先建立一個階乘計算器項目。編程

首先建立所需的全部文件和文件夾,以下所示:bootstrap

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

接下來在 index.html 內建立一個簡單的表單。segmentfault

<!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,應該是這樣的:

factorial-calc

如今這個表單尚未任何操做。

咱們的目標是實現一種邏輯,在該邏輯中你能夠輸入一個最大爲 100 的數字。單擊「Calculate」按鈕後,結果應顯示在 result-div 中。

下面分別以面向對象和函數式的方式來實現。

函數式實現

首先爲函數式編程方法建立一個文件。

$ cat functional.js

首先,須要一個在將此文件加載到瀏覽器時要調用的函數。

該函數先獲取表單,而後把咱們須要的函數添加到表單的提交事件中。

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

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

首先聲明一個名爲 addSubmitHandler 的函數。

這個函數有兩個參數,第一個是要在 HTML 中查找的標籤,第二個是要綁定到 Elementcommit-event 的函數。

接下來,經過傳入#factorial-form 和函數名 factorialHandler 來調用此函數。

標籤前面的 # 代表咱們正在尋找 HTML 中的 id 屬性。

若是如今嘗試運行該代碼,則會拋出錯誤,由於在任何地方都尚未定義函數 getElementfactorialHandler

所以,首先在 addSubmitHandler 函數前面定義 getElement ,以下所示:

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

這個函數很是簡單,只返回經過傳入的標記找到的 HTML元素。可是稍後咱們將重用此功能。

如今添加 factorialHandler 函數來建立核心邏輯。

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

  const inputNumber = getValueFromElement('#factorial');

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

把事件傳回後當即調用 preventDefault

這將阻止 Submit 事件的默認行爲,你能夠試試不調用 preventDefault 時單擊按鈕後會發生什麼。

以後,經過調用 getValueFromElement 函數從輸入字段中獲取用戶輸入的值。在獲得數字後,用函數 calculateFactorial 計算階乘,而後經過將結果傳遞給函數 displayResult 將結果展現到頁面。

若是該值的格式不正確或者數字大於 100,將會拋出錯誤並彈出 alert。

下一步,建立另外兩個輔助函數: getValueFromElementdisplayResult,並將它們添加到 getElement 函數後面。

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

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

這兩個函數都使用咱們的 getElement 函數。這種可重用性是爲何函數式編程如此有效的一個緣由。

爲了使它更加可重用,能夠在 displayResult 上添加名爲 tag 第二個參數。

這樣就能夠動態設置應該顯示結果的元素。

可是在本例中,我用了硬編碼的方式。

接下來,在 factoryHandler 前面建立 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'
    );
  }
}

接着建立一個名爲 validate 的函數來驗證參數 number 是否爲空且不大於 100,且類型爲 number。若是檢查經過,就調用 factorial 函數並返回其結果。若是沒有經過,則拋出在 factorialHandler 函數中捕獲的錯誤。

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 聲明的前面添加實際的 factor 函數。這是最後一個函數。

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

最終的 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 文件的腳本標籤中的 src 更改成如下內容。

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

而後建立 oop.js 文件。

$ cat oop.js

對於面向對象方法,咱們要建立三種不一樣的類,一種用於驗證,一種用於階乘計算,另外一種用於處理表單。

先是建立處理表單的類。

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();

在構造函數中獲取 form-elementinput-element 並將其存儲在類變量(也稱爲屬性)中。以後將方法 factorialHandler 添加到 Submit-event 中。在這種狀況下須要把類的 this 綁定到方法。若是不這樣作,將會獲得一個引用錯誤,例如調用 this.numberInput.value 將會是 undefined。以後以事件爲參數建立類方法 factorialHandler

該方法的代碼看起來應該有點熟悉,例如 if 語句檢查輸入值是否有效,就像在 calculateFactorial 函數中所作的那樣。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;
    }
  }
}

這個類內部的全部內容都是靜態的,因此咱們不須要任何構造函數。

這樣作的好處是不須要在每次使用它時都初始化該類。

validatevalidate 函數與咱們的 functional.js 幾乎徹底相同。

接下來在 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;
  }
}

在初始化這個類的實例後,咱們得到 resultElement 並將其存儲爲屬性以及咱們傳入的數字。

以後調用方法 calculate 並將其返回值存儲在屬性中。calculate 方法包含與 functional.js 中的 factor 函數相同的代碼。最後是 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();

咱們建立了三個類來處理程序的三個不一樣的功能:

  • 驗證: Validation
  • 階乘處理: Factorial
  • 表單處理: InputForm

總結

兩種方法都是編寫代碼的有效方法。我喜歡在本身不一樣項目中嘗試最有效的方法。在不少狀況下,甚至不可能如此清晰地分離這兩種範式。

但願這篇文章可使你對不一樣的方法有一個基本的瞭解。

173382ede7319973.gif


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索