如何在JavaScript计算器应用中正确应用命令模式?
命令模式在计算器中的具体实现方案
命令模式的核心是将每个操作封装成独立的命令对象,让请求发送者(UI)和请求执行者(核心逻辑)解耦,同时天然支持撤销、重做等功能。结合你的计算器应用,我会一步步拆解实现方式:
1. 定义命令基类与具体命令
首先为每个运算操作(加、减、乘、除、清空)创建独立的命令类,每个命令需实现execute(执行操作)和undo(撤销操作)两个核心方法,同时保存撤销所需的状态:
// 基础命令抽象(统一接口) class Command { execute() {} undo() {} } // 加法命令 class AddCommand extends Command { constructor(core, targetValue) { super(); this.core = core; // 持有计算器核心实例 this.targetValue = targetValue; // 要加的数值 this.previousValue = null; // 记录执行前的结果,用于撤销 } execute() { this.previousValue = this.core.currentValue; this.core.currentValue += this.targetValue; } undo() { this.core.currentValue = this.previousValue; } } // 减法命令(同理实现乘、除命令) class SubtractCommand extends Command { constructor(core, targetValue) { super(); this.core = core; this.targetValue = targetValue; this.previousValue = null; } execute() { this.previousValue = this.core.currentValue; this.core.currentValue -= this.targetValue; } undo() { this.core.currentValue = this.previousValue; } } // 清空命令 class ClearCommand extends Command { constructor(core) { super(); this.core = core; this.previousValue = null; } execute() { this.previousValue = this.core.currentValue; this.core.currentValue = 0; } undo() { this.core.currentValue = this.previousValue; } }
2. 修改CalculatorCore,适配命令模式
改造核心逻辑类,让它不再直接提供add/subtract这类方法,而是改为接收命令对象,并维护命令历史栈实现撤销:
// CalculatorCore.js class CalculatorCore { constructor() { this.currentValue = 0; this.commandHistory = []; // 存储已执行的命令,用于撤销 } // 执行传入的命令 executeCommand(command) { command.execute(); this.commandHistory.push(command); } // 撤销最后一步操作 undoLast() { if (!this.commandHistory.length) return; const lastCmd = this.commandHistory.pop(); lastCmd.undo(); } // 对外暴露当前值供UI获取 getCurrentValue() { return this.currentValue; } }
3. 改造Calculator.jsx,用命令驱动UI操作
在UI组件中,不再直接调用核心类的运算方法,而是创建对应命令对象,交给核心类执行:
// Calculator.jsx import React, { useState, useRef } from 'react'; import { CalculatorCore, AddCommand, SubtractCommand, ClearCommand } from './CalculatorCore'; const Calculator = () => { // 用useRef保存计算器核心实例,避免重复创建 const calculatorRef = useRef(new CalculatorCore()); const [displayValue, setDisplayValue] = useState('0'); // 数字输入逻辑(保留原有逻辑即可) const handleNumberInput = (num) => { const newDisplay = displayValue === '0' ? num.toString() : displayValue + num; setDisplayValue(newDisplay); }; // 加法按钮点击:创建AddCommand并执行 const handleAdd = () => { const targetNum = parseFloat(displayValue); const addCmd = new AddCommand(calculatorRef.current, targetNum); calculatorRef.current.executeCommand(addCmd); setDisplayValue(calculatorRef.current.getCurrentValue().toString()); }; // 减法按钮点击:同理创建SubtractCommand const handleSubtract = () => { const targetNum = parseFloat(displayValue); const subtractCmd = new SubtractCommand(calculatorRef.current, targetNum); calculatorRef.current.executeCommand(subtractCmd); setDisplayValue(calculatorRef.current.getCurrentValue().toString()); }; // 清空按钮点击 const handleClear = () => { const clearCmd = new ClearCommand(calculatorRef.current); calculatorRef.current.executeCommand(clearCmd); setDisplayValue(calculatorRef.current.getCurrentValue().toString()); }; // 撤销按钮点击 const handleUndo = () => { calculatorRef.current.undoLast(); setDisplayValue(calculatorRef.current.getCurrentValue().toString()); }; return ( <div className="calculator"> <div className="display">{displayValue}</div> <div className="button-grid"> <button onClick={() => handleNumberInput(1)}>1</button> <button onClick={() => handleNumberInput(2)}>2</button> <button onClick={() => handleNumberInput(3)}>3</button> <button onClick={handleAdd}>+</button> <button onClick={() => handleNumberInput(4)}>4</button> <button onClick={() => handleNumberInput(5)}>5</button> <button onClick={() => handleNumberInput(6)}>6</button> <button onClick={handleSubtract}>-</button> <button onClick={() => handleNumberInput(7)}>7</button> <button onClick={() => handleNumberInput(8)}>8</button> <button onClick={() => handleNumberInput(9)}>9</button> <button onClick={handleUndo}>Undo</button> <button onClick={() => handleNumberInput(0)}>0</button> <button onClick={handleClear}>C</button> </div> </div> ); }; export default Calculator;
这样做的核心优势
- 解耦UI与逻辑:UI只需要关心创建对应命令,不用知道运算细节;核心逻辑只处理命令执行,不用管UI触发方式。
- 轻松实现撤销/重做:通过维护命令历史栈,
undo操作只需要调用最后一个命令的undo方法即可,新增重做功能也只需要再维护一个重做栈。 - 扩展性极强:如果要新增平方、开根号这类操作,只需要新增对应的
SquareCommand/SqrtCommand类,完全不需要修改现有核心逻辑和UI代码,符合开闭原则。 - 支持批量操作:可以将多个命令组合成一个宏命令,实现一键执行多步操作(比如"清空后加10")。
内容的提问来源于stack exchange,提问作者qwfrjk
相关产品推荐
相关产品推荐

