js 函數如何實現策略模式與狀態模式

来源:https://www.cnblogs.com/beileixinqing/archive/2023/10/16/17767827.html
-Advertisement-
Play Games

前言 有關設計模式的學習資料中,大部分都是以 java 語言實現的,畢竟 java 作為老牌面向對象的語言最能說明設計模式的核心概念,所以 js 的相關設計模式的學習資料也大多使用 class 類實現,本文記錄下 js 使用函數實現策略模式和狀態模式設計模式的方式,更有助於理解策略模式和狀態模式如何 ...


前言

有關設計模式的學習資料中,大部分都是以 java 語言實現的,畢竟 java 作為老牌面向對象的語言最能說明設計模式的核心概念,所以 js 的相關設計模式的學習資料也大多使用 class 類實現,本文記錄下 js 使用函數實現策略模式和狀態模式設計模式的方式,更有助於理解策略模式和狀態模式如何在實際工作中運用。

策略模式

定義一系列的演算法,把它們一個一個封裝起來,並且使它們可以相互替換。

目的:將演算法的使用和演算法實現分離開來

優點:

  • 利用組合、委托、多態等思想,可以解決多重條件選擇語句問題
  • 策略模式提供了對開放—封閉原則的完美支持,將演算法封裝在獨立的strategy中,使得它們易於切換,易於理解,易於擴展
  • 策略模式中的演算法也可以復用在系統的其他地方,從而避免許多重覆的複製粘貼工作
  • 在策略模式中利用組合和委托來讓 Context 擁有執行演算法的能力,這也是繼承的一種更輕便的替代方案

缺點:

  • 代碼會增加許多策略類和策略對象
  • 需要全面瞭解各種 stragety, stragety要向客戶暴露它的所有實現,違反最少知識原則

狀態模式

允許一個對象在其內部狀態改變時改變它的行為,對象看起來似乎修改了它的類。

優點:

  • 狀態模式定義了狀態與行為之間的關係,並將它們封裝在一個類里。通過增加新的狀態類,很容易增加新的狀態和轉換
  • 避免 Context 無限膨脹,狀態切換的邏輯被分佈在狀態類中,也去掉了 Context 中原本過多的條件分支
  • 用對象代替字元串來記錄當前狀態,使得狀態的切換更加一目瞭然
  • Context 中的請求動作和狀態類中封裝的行為可以非常容易地獨立變化而互不影響

缺點:

  • 會在系統中定義許多狀態類,而且系統中會因此而增加不少對象

         性能優化點

    • 1、僅當 state 對象被需要時才創建並隨後銷毀,用於節省記憶體,但不常變動的
    • 2、一開始就創建好所有的狀態對象,並且始終不銷毀它們,用於狀態經常變動的
  • 由於邏輯分散在狀態類中,雖然避開了不受歡迎的條件分支語句,但也造成了邏輯分散的問題,我們無法在一個地方就看出整個狀態機的邏輯

相同點

  • 都有一個上下文、一些策略或狀態類
  • 上下文把請求委托給這些類來執行

區別

狀態模式

  • 【不同事情 】狀態模式各狀態之間的切換,做不同的事情;
  • 【不能互相替換 】狀態模式各個狀態的同一方法做的是不同的事,不能互相替換,狀態和狀態行為是早已被封封裝好的,狀態之間的切換也早被規定完成,改變模式這個行為發生在狀態內部,使用者不需要瞭解改變的細節;
  • 【封裝狀態】狀態模式封裝了對象的狀態;
  • 【狀態不可重用】因為狀態是跟對象密切相關的,它不能被重用;
  • 【持有context 】在狀態模式中,每個狀態通過持有Context的引用,來實現狀態轉移;。

策略模式

  • 【同樣的事情】策略模式更側重於根據具體情況選擇策略,做同樣的事情;
  • 【可替換】策略模式各個策略之間是可替換的,平等又平行,互相之間沒有任何聯繫,需熟知各個策略、各類的作用,以便隨時切換演算法;
  • 【封裝演算法和策略】策略模式封裝演算法或策略;
  • 【策略可重用】策略模式通過從Context中分離出策略或演算法,我們可以重用它們;
  • 【不持有context】但是每個策略都不持有Context的引用,它們只是被Context使用。context持有對某個策略對象的引用。
狀態模式關註於對象的狀態轉換,而策略模式關註於將演算法或行為封裝到策略對象中,以便在運行時動態替換。

聯繫

狀態模式和策略模式都是為具有多種可能情形設計的模式,把不同的處理情形抽象為一個相同的介面,符合對擴展開放,對修改封閉的原則。(優化多個條件判斷語句的業務邏輯)

實踐

策略模式

var S = function( salary ){ 
  return salary * 4; 
}; 
var A = function( salary ){ 
  return salary * 3; 
}; 
var B = function( salary ){ 
  return salary * 2; 
}; 
// 這裡的 context 是 calculateBonus
var calculateBonus = function( func, salary ){ 
  return func( salary ); 
}; 
calculateBonus( S, 10000 ); // 輸出:40000
// 定義各個策略對象,每個策略對象都不持有 context 的引用,僅僅被 context 使用
const add = {
  calculate: function (a, b) {
    return a + b;
  },
};

const subtract = {
  calculate: function (a, b) {
    return a - b;
  },
};

const multiply = {
  calculate: function (a, b) {
    return a * b;
  },
};

// 策略管理器,這裡 Calculator 代表了 context,context 擁有執行不同演算法的能力,傳參為要執行的策略,context 持有對某個策略對象的引用
function Calculator(strategy) {
  this.strategy = strategy;

  this.setStrategy = function (newStrategy) {
    this.strategy = newStrategy;
  };

  this.calculate = function (a, b) {
    return this.strategy.calculate(a, b);
  };
}

// 使用
// 傳入或設置不同的策略,執行結果函數得到結果
const calculator = new Calculator(add);
console.log(calculator.calculate(2, 3)); // 5
calculator.setStrategy(subtract);
console.log(calculator.calculate(2, 3)); // -1
calculator.setStrategy(multiply);
console.log(calculator.calculate(2, 3)); // 6

狀態模式

/ 狀態對象
// 每個狀態對象持有對傳入狀態的引用,以便流轉至下一個狀態
const redLight = {
  name: '紅燈',
  nextState: function () {
    return greenLight;
  },
};

const yellowLight = {
  name: '黃燈',
  nextState: function () {
    return redLight;
  },
};

const greenLight = {
  name: '綠燈',
  nextState: function () {
    return yellowLight;
  },
};

// 狀態管理器
function TrafficLightManager(initialState) {
  this.state = initialState;

  this.changeState = function () {
    this.state = this.state.nextState();
  };
}

// 使用
// 傳入初始狀態
const trafficLight = new TrafficLightManager(redLight);
console.log(trafficLight.state.name); // 紅燈
// 通過狀態流轉方法切換下一個狀態
trafficLight.changeState();
console.log(trafficLight.state.name); // 綠燈
trafficLight.changeState();
console.log(trafficLight.state.name); // 黃燈
// 封裝執行狀態的請求
var delegate = function (client, delegation) {
  return {
    buttonWasPressed: function () { // 將客戶的操作委托給 delegation 對象
      return delegation.buttonWasPressed.apply(client, arguments);
    }
  }
};

// 定義狀態機
var FSM = {
  off: {
    buttonWasPressed: function () {
      console.log('關燈');
      this.button.innerHTML = '下一次按我是開燈';
      this.currState = this.onState;
    }
  },
  on: {
    buttonWasPressed: function () {
      console.log('開燈');
      this.button.innerHTML = '下一次按我是關燈';
      this.currState = this.offState;
    }
  }
};

// 這裡 Light 代表了 context,context 持有各個狀態對象的引用,根據狀態的改變執行不同的行為
var Light = function () {
  this.offState = delegate(this, FSM.off);
  this.onState = delegate(this, FSM.on);
  this.currState = this.offState; // 設置初始狀態為關閉狀態
  this.button = null;
};

Light.prototype.init = function () {
  var button = document.createElement('button'),
    self = this;
  button.innerHTML = '已關燈';
  this.button = document.body.appendChild(button);
  this.button.onclick = function () {
    self.currState.buttonWasPressed();
  }
};

var light = new Light()

 

本文大部分總結自書《javascript設計模式與開發實踐》,僅供學習使用,如有錯誤,望大家多多指正。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目錄構建riscv64-unknown-linux-musl編譯工具鏈直接下載官方工具鏈嘗試自己編譯T-head Gcc下載編譯binutils編譯交叉gcc編譯musl手動合成fip.bin和boot.sd編譯u-boot生成cvi_board_memmap.h,cvipart.h和imgs.h繼 ...
  • 網橋的概念 在生活中,橋是一種結構,用於連接兩個地方,允許行人、車輛等安全地跨越障礙物(如河流或高速公路) 在電腦網路技術中,網橋是一種工作在數據鏈路層的物理或邏輯設備,可以用於連接兩個或多個區域網段。它基於MAC地址來轉發或過濾幀,從而有效地劃分廣播域。 在Linux中,網橋是一個邏輯設備,用於 ...
  • 1. 關閉正在運行的MySQL服務。【Win + r】,之後輸入【SERVICES.MSC】然後回車,會打開服務列表,在服務列表輸入【mysql】即可選中mysql對應服務,找到自己的mysql服務,我的是MYSQL57選中它【右鍵】單擊,然後點擊【停止】選項即可停止mysql服務。 2. 找到本地 ...
  • 近來在工作中處理JSON處理較多,深入研究了一下jq,之前對jq的使用一直停留在JSON數據格式化的層面,實際它的能力遠不止於此。 在處理JSON數據時,我們經常需要在命令行中進行過濾、查詢和編輯的操作。jq是一個強 ...
  • 本文介紹了MongoDB複製集的架構和特點,強調了使用複製集提供數據的高可用性和冗餘性的重要性。複製集由Primary節點和Secondary節點組成,確保數據一致性。複製集還具有數據分發、讀寫分離和異地容災等附加功能。使用MongoDB複製集可以提供穩定可靠的數據存儲和高可用性。 ...
  • 康師傅yyds MySQL的索引包括普通索引、唯一性索引、全文索引、單列索引、多列索引和空間索引等。 從 功能邏輯 上說,索引主要有 4 種,分別是普通索引、唯一索引、主鍵索引、全文索引。 按照 物理實現方式 ,索引可以分為 2 種:聚簇索引和非聚簇索引。 按照 作用欄位個數 進行劃分,分成單列索引 ...
  • tv屏中,最難處理的就是焦點問題,而複雜的焦點處理要屬應用列表模塊了 根據展示的列表,可以翻頁,預設焦點處於左上角第一個,此時通過遙控器上下左右可以控制焦點移動位置 焦點所在應用需要有個黃色邊框標識,往右移動到邊界,自動到下一行,繼續往右移動到邊界底部自動翻頁,往下移動到底部自動翻頁 長按應用彈出編 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 很多時候在工作中會碰到完全由前端導出word文件的需求,因此特地記錄一下比較常用的幾種方式。 一、提供一個word模板 該方法提供一個word模板文件,數據通過參數替換的方式傳入word文件中,靈活性較差,適用於簡單的文件導出。需要依賴: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...