基于Python打造支持Undo/Redo的文本編輯器命令系統(tǒng)
一、引言:你的 Ctrl+Z 背后藏著什么秘密?
每天打開(kāi)代碼編輯器,你最常按的快捷鍵是什么?
對(duì)大多數(shù)開(kāi)發(fā)者來(lái)說(shuō),Ctrl+Z(撤銷(xiāo))和 Ctrl+Y(重做)可能是僅次于保存的高頻操作。寫(xiě)錯(cuò)了一段代碼?撤銷(xiāo)。誤刪了一個(gè)函數(shù)?撤銷(xiāo)。一路 Redo 找回剛才的思路?重做。
這一切看似魔法的背后,正是軟件工程中一個(gè)優(yōu)雅而強(qiáng)大的設(shè)計(jì)模式:命令模式(Command Pattern)。
初學(xué)者可能會(huì)想:撤銷(xiāo)不就是「保存歷史狀態(tài),出錯(cuò)時(shí)回滾」嗎?用一個(gè)列表記錄每次操作前的文本不就行了?
這種方案在文本量小時(shí)可行,但在復(fù)雜系統(tǒng)中代價(jià)極高——每次操作都復(fù)制整個(gè)狀態(tài),內(nèi)存消耗隨歷史深度線性增長(zhǎng),且無(wú)法區(qū)分「哪些操作可以合并」「哪些操作是原子的」。
命令模式的解法更加精妙:將每一個(gè)操作封裝成獨(dú)立的對(duì)象,對(duì)象內(nèi)同時(shí)包含「執(zhí)行」和「撤銷(xiāo)」的邏輯。 撤銷(xiāo)不是回滾狀態(tài),而是執(zhí)行「反向命令」——插入的反操作是刪除,移動(dòng)的反操作是移回原位。
本文將從零構(gòu)建一個(gè)功能完整的文本編輯器命令系統(tǒng),覆蓋基礎(chǔ)命令實(shí)現(xiàn)、命令組合、宏命令、歷史管理、以及生產(chǎn)級(jí)的持久化方案。
二、命令模式基礎(chǔ):四個(gè)核心角色
2.1 角色定義
- Command(抽象命令):定義執(zhí)行
execute()和撤銷(xiāo)undo()的接口。 - ConcreteCommand(具體命令):封裝一個(gè)具體操作及其撤銷(xiāo)邏輯,持有接收者的引用。
- Receiver(接收者):真正執(zhí)行業(yè)務(wù)邏輯的對(duì)象(此處為文本編輯器核心)。
- Invoker(調(diào)用者):持有命令對(duì)象,管理命令歷史,負(fù)責(zé) Undo/Redo 調(diào)度。
2.2 最小骨架
from abc import ABC, abstractmethod
class Command(ABC):
"""抽象命令:所有命令的契約"""
@abstractmethod
def execute(self) -> None:
"""執(zhí)行命令"""
pass
@abstractmethod
def undo(self) -> None:
"""撤銷(xiāo)命令(執(zhí)行反操作)"""
pass
@property
def description(self) -> str:
"""命令描述,用于歷史記錄展示"""
return self.__class__.__name__
三、構(gòu)建文本編輯器核心(接收者)
在動(dòng)手寫(xiě)命令之前,先構(gòu)建一個(gè)功能完備的文本編輯器核心——它是所有命令操作的「接收者」:
from dataclasses import dataclass, field
from typing import Optional
import re
@dataclass
class TextBuffer:
"""
文本緩沖區(qū):編輯器的核心數(shù)據(jù)模型
支持光標(biāo)定位、選區(qū)操作、文本增刪
"""
content: str = ''
cursor: int = 0 # 光標(biāo)位置(字符索引)
selection: Optional[tuple[int, int]] = None # 選區(qū) (start, end)
filename: str = 'untitled.txt'
modified: bool = False
def __post_init__(self):
self._validate_cursor()
def _validate_cursor(self):
self.cursor = max(0, min(self.cursor, len(self.content)))
# ===== 基礎(chǔ)操作 =====
def insert(self, pos: int, text: str) -> None:
"""在指定位置插入文本"""
pos = max(0, min(pos, len(self.content)))
self.content = self.content[:pos] + text + self.content[pos:]
self.cursor = pos + len(text)
self.modified = True
def delete(self, start: int, end: int) -> str:
"""刪除 [start, end) 范圍的文本,返回被刪除內(nèi)容"""
start = max(0, min(start, len(self.content)))
end = max(start, min(end, len(self.content)))
deleted = self.content[start:end]
self.content = self.content[:start] + self.content[end:]
self.cursor = start
self.modified = True
return deleted
def replace(self, start: int, end: int, new_text: str) -> str:
"""替換 [start, end) 范圍的文本,返回被替換內(nèi)容"""
old_text = self.delete(start, end)
self.insert(start, new_text)
return old_text
def move_cursor(self, pos: int) -> int:
"""移動(dòng)光標(biāo),返回舊位置"""
old_pos = self.cursor
self.cursor = max(0, min(pos, len(self.content)))
return old_pos
def get_line_at(self, pos: int) -> tuple[int, int, str]:
"""獲取指定位置所在行的 (行首, 行尾, 行內(nèi)容)"""
line_start = self.content.rfind('\n', 0, pos) + 1
line_end = self.content.find('\n', pos)
if line_end == -1:
line_end = len(self.content)
return line_start, line_end, self.content[line_start:line_end]
def display(self, show_cursor: bool = True) -> str:
"""渲染文本(帶光標(biāo)標(biāo)記)"""
if show_cursor:
content = (self.content[:self.cursor] + '|' +
self.content[self.cursor:])
else:
content = self.content
lines = content.split('\n')
result = [f"{'─' * 40}",
f"文件: {self.filename} {'*' if self.modified else ''}",
f"{'─' * 40}"]
for i, line in enumerate(lines, 1):
result.append(f"{i:3d} │ {line}")
result.append(f"{'─' * 40}")
result.append(f"光標(biāo): {self.cursor} | 字符數(shù): {len(self.content)}")
return '\n'.join(result)
四、實(shí)現(xiàn)完整命令集
4.1 基礎(chǔ)文本命令
class InsertCommand(Command):
"""插入文本命令"""
def __init__(self, buffer: TextBuffer, pos: int, text: str):
self.buffer = buffer
self.pos = pos
self.text = text
self._old_cursor: int = 0
def execute(self) -> None:
self._old_cursor = self.buffer.cursor
self.buffer.insert(self.pos, self.text)
def undo(self) -> None:
self.buffer.delete(self.pos, self.pos + len(self.text))
self.buffer.cursor = self._old_cursor
@property
def description(self) -> str:
preview = self.text[:20].replace('\n', '?')
return f"插入文本: 「{preview}」at pos={self.pos}"
class DeleteCommand(Command):
"""刪除文本命令"""
def __init__(self, buffer: TextBuffer, start: int, end: int):
self.buffer = buffer
self.start = start
self.end = end
self._deleted_text: str = ''
self._old_cursor: int = 0
def execute(self) -> None:
self._old_cursor = self.buffer.cursor
self._deleted_text = self.buffer.delete(self.start, self.end)
def undo(self) -> None:
self.buffer.insert(self.start, self._deleted_text)
self.buffer.cursor = self._old_cursor
@property
def description(self) -> str:
preview = self._deleted_text[:20].replace('\n', '?')
return f"刪除文本: 「{preview}」[{self.start}:{self.end}]"
class ReplaceCommand(Command):
"""替換文本命令(原子操作)"""
def __init__(self, buffer: TextBuffer, start: int, end: int, new_text: str):
self.buffer = buffer
self.start = start
self.end = end
self.new_text = new_text
self._old_text: str = ''
self._old_cursor: int = 0
def execute(self) -> None:
self._old_cursor = self.buffer.cursor
self._old_text = self.buffer.replace(self.start, self.end, self.new_text)
def undo(self) -> None:
self.buffer.replace(self.start,
self.start + len(self.new_text),
self._old_text)
self.buffer.cursor = self._old_cursor
@property
def description(self) -> str:
return (f"替換: 「{self._old_text[:15]}」→「{self.new_text[:15]}」"
f"[{self.start}:{self.end}]")
class MoveCursorCommand(Command):
"""移動(dòng)光標(biāo)命令"""
def __init__(self, buffer: TextBuffer, new_pos: int):
self.buffer = buffer
self.new_pos = new_pos
self._old_pos: int = 0
def execute(self) -> None:
self._old_pos = self.buffer.move_cursor(self.new_pos)
def undo(self) -> None:
self.buffer.move_cursor(self._old_pos)
@property
def description(self) -> str:
return f"移動(dòng)光標(biāo): {self._old_pos} → {self.new_pos}"
4.2 高級(jí)命令:查找替換
class FindReplaceCommand(Command):
"""查找并全局替換命令"""
def __init__(self, buffer: TextBuffer, pattern: str,
replacement: str, use_regex: bool = False):
self.buffer = buffer
self.pattern = pattern
self.replacement = replacement
self.use_regex = use_regex
self._original_content: str = ''
self._original_cursor: int = 0
self._replace_count: int = 0
def execute(self) -> None:
self._original_content = self.buffer.content
self._original_cursor = self.buffer.cursor
if self.use_regex:
new_content, count = re.subn(
self.pattern, self.replacement, self.buffer.content
)
else:
count = self.buffer.content.count(self.pattern)
new_content = self.buffer.content.replace(
self.pattern, self.replacement
)
self._replace_count = count
self.buffer.content = new_content
self.buffer.modified = True
print(f" [查找替換] 共替換 {count} 處")
def undo(self) -> None:
"""一鍵恢復(fù)全部替換——這就是命令模式的威力"""
self.buffer.content = self._original_content
self.buffer.cursor = self._original_cursor
self.buffer.modified = True
@property
def description(self) -> str:
mode = "正則" if self.use_regex else "普通"
return (f"全局替換({mode}): 「{self.pattern}」→「{self.replacement}」"
f"({self._replace_count}處)")
class IndentCommand(Command):
"""縮進(jìn)/反縮進(jìn)命令(對(duì)選區(qū)內(nèi)所有行生效)"""
def __init__(self, buffer: TextBuffer, start: int, end: int,
indent: bool = True, spaces: int = 4):
self.buffer = buffer
self.start = start
self.end = end
self.indent = indent
self.spaces = spaces
self._original_content: str = ''
self._original_cursor: int = 0
def execute(self) -> None:
self._original_content = self.buffer.content
self._original_cursor = self.buffer.cursor
lines = self.buffer.content.split('\n')
# 計(jì)算影響的行范圍
char_count = 0
start_line = end_line = 0
for i, line in enumerate(lines):
if char_count <= self.start:
start_line = i
if char_count <= self.end:
end_line = i
char_count += len(line) + 1
prefix = ' ' * self.spaces
for i in range(start_line, end_line + 1):
if self.indent:
lines[i] = prefix + lines[i]
else:
lines[i] = lines[i].lstrip(' ')[:len(lines[i]) - len(lines[i].lstrip(' '))]
if lines[i].startswith(prefix):
lines[i] = lines[i][self.spaces:]
else:
lines[i] = lines[i].lstrip(' ')
self.buffer.content = '\n'.join(lines)
self.buffer.modified = True
def undo(self) -> None:
self.buffer.content = self._original_content
self.buffer.cursor = self._original_cursor
self.buffer.modified = True
@property
def description(self) -> str:
action = "縮進(jìn)" if self.indent else "反縮進(jìn)"
return f"{action} {self.spaces}空格 [{self.start}:{self.end}]"
4.3 宏命令:組合多個(gè)操作為一個(gè)原子單元
class MacroCommand(Command):
"""
宏命令:將多個(gè)命令組合成一個(gè)可整體撤銷(xiāo)的原子操作
典型用途:「粘貼」= 先刪除選區(qū) + 再插入剪貼板內(nèi)容
"""
def __init__(self, commands: list[Command], macro_name: str = '宏命令'):
self.commands = commands
self.macro_name = macro_name
self._executed: list[Command] = []
def execute(self) -> None:
self._executed = []
for cmd in self.commands:
try:
cmd.execute()
self._executed.append(cmd)
except Exception as e:
# 執(zhí)行失?。夯貪L已執(zhí)行的命令
print(f" ? 宏命令子步驟失敗: {e},正在回滾...")
for executed_cmd in reversed(self._executed):
executed_cmd.undo()
self._executed = []
raise
def undo(self) -> None:
"""逆序撤銷(xiāo)所有子命令"""
for cmd in reversed(self._executed):
cmd.undo()
@property
def description(self) -> str:
sub_descs = [f" · {cmd.description}" for cmd in self.commands]
return f"{self.macro_name}({len(self.commands)}步):\n" + '\n'.join(sub_descs)
五、命令調(diào)度中心:歷史管理器(Invoker)
from collections import deque
import json
from datetime import datetime
class CommandHistory:
"""
命令歷史管理器(Invoker)
- 執(zhí)行命令并記錄到 undo 棧
- 支持多步 Undo/Redo
- 支持歷史限制(防止內(nèi)存溢出)
- 支持歷史快照導(dǎo)出
"""
def __init__(self, max_history: int = 100):
self._undo_stack: deque[Command] = deque(maxlen=max_history)
self._redo_stack: deque[Command] = deque(maxlen=max_history)
self._max_history = max_history
self._execute_log: list[dict] = []
def execute(self, command: Command) -> None:
"""執(zhí)行命令并推入撤銷(xiāo)棧"""
command.execute()
self._undo_stack.append(command)
# 執(zhí)行新命令后,清空 Redo 棧(與主流編輯器行為一致)
self._redo_stack.clear()
# 記錄執(zhí)行日志
self._execute_log.append({
'action': 'execute',
'command': command.description,
'timestamp': datetime.now().isoformat()
})
print(f" ? 執(zhí)行: {command.description}")
def undo(self) -> bool:
"""撤銷(xiāo)最近一條命令"""
if not self._undo_stack:
print(" ? 沒(méi)有可撤銷(xiāo)的操作")
return False
command = self._undo_stack.pop()
command.undo()
self._redo_stack.append(command)
self._execute_log.append({
'action': 'undo',
'command': command.description,
'timestamp': datetime.now().isoformat()
})
print(f" ? 撤銷(xiāo): {command.description}")
return True
def redo(self) -> bool:
"""重做最近一條被撤銷(xiāo)的命令"""
if not self._redo_stack:
print(" ? 沒(méi)有可重做的操作")
return False
command = self._redo_stack.pop()
command.execute()
self._undo_stack.append(command)
self._execute_log.append({
'action': 'redo',
'command': command.description,
'timestamp': datetime.now().isoformat()
})
print(f" ? 重做: {command.description}")
return True
def undo_many(self, steps: int) -> int:
"""批量撤銷(xiāo)多步"""
count = 0
for _ in range(steps):
if self.undo():
count += 1
else:
break
return count
def redo_many(self, steps: int) -> int:
"""批量重做多步"""
count = 0
for _ in range(steps):
if self.redo():
count += 1
else:
break
return count
@property
def can_undo(self) -> bool:
return len(self._undo_stack) > 0
@property
def can_redo(self) -> bool:
return len(self._redo_stack) > 0
@property
def undo_count(self) -> int:
return len(self._undo_stack)
@property
def redo_count(self) -> int:
return len(self._redo_stack)
def get_history_summary(self) -> str:
"""獲取操作歷史摘要"""
lines = [f"{'─' * 50}",
f" 操作歷史 (Undo棧: {self.undo_count} | Redo棧: {self.redo_count})",
f"{'─' * 50}"]
lines.append(" [可撤銷(xiāo)操作](從新到舊):")
for cmd in reversed(list(self._undo_stack)):
lines.append(f" ? {cmd.description}")
if self._redo_stack:
lines.append(" [可重做操作](從新到舊):")
for cmd in reversed(list(self._redo_stack)):
lines.append(f" ? {cmd.description}")
lines.append(f"{'─' * 50}")
return '\n'.join(lines)
def export_log(self, filepath: str) -> None:
"""導(dǎo)出操作日志到 JSON 文件"""
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(self._execute_log, f, ensure_ascii=False, indent=2)
print(f" 操作日志已導(dǎo)出至: {filepath}")
六、完整集成:文本編輯器門(mén)面類(lèi)
class TextEditor:
"""
文本編輯器門(mén)面類(lèi)(Facade)
將 TextBuffer(接收者)+ CommandHistory(調(diào)用者)整合為統(tǒng)一 API
"""
def __init__(self, filename: str = 'untitled.txt'):
self.buffer = TextBuffer(filename=filename)
self.history = CommandHistory(max_history=200)
self._clipboard: str = ''
# ===== 編輯操作(每個(gè)操作都封裝為命令)=====
def type_text(self, text: str) -> None:
"""在當(dāng)前光標(biāo)位置輸入文本"""
cmd = InsertCommand(self.buffer, self.buffer.cursor, text)
self.history.execute(cmd)
def insert_at(self, pos: int, text: str) -> None:
"""在指定位置插入文本"""
cmd = InsertCommand(self.buffer, pos, text)
self.history.execute(cmd)
def delete_range(self, start: int, end: int) -> None:
"""刪除指定范圍的文本"""
cmd = DeleteCommand(self.buffer, start, end)
self.history.execute(cmd)
def replace_range(self, start: int, end: int, new_text: str) -> None:
"""替換指定范圍的文本"""
cmd = ReplaceCommand(self.buffer, start, end, new_text)
self.history.execute(cmd)
def find_replace(self, pattern: str, replacement: str,
use_regex: bool = False) -> None:
"""查找并全局替換"""
cmd = FindReplaceCommand(self.buffer, pattern, replacement, use_regex)
self.history.execute(cmd)
def indent_selection(self, start: int, end: int, indent: bool = True) -> None:
"""縮進(jìn)/反縮進(jìn)選區(qū)"""
cmd = IndentCommand(self.buffer, start, end, indent)
self.history.execute(cmd)
def cut(self, start: int, end: int) -> str:
"""剪切:刪除文本并存入剪貼板"""
# 剪切 = 復(fù)制 + 刪除(兩步封裝為宏命令,整體可撤銷(xiāo))
self._clipboard = self.buffer.content[start:end]
cmd = DeleteCommand(self.buffer, start, end)
self.history.execute(cmd)
print(f" ?? 已剪切 {len(self._clipboard)} 個(gè)字符到剪貼板")
return self._clipboard
def paste(self, pos: int = None) -> None:
"""粘貼:如有選區(qū)則先刪除再插入(原子宏命令)"""
if not self._clipboard:
print(" ? 剪貼板為空")
return
target_pos = pos if pos is not None else self.buffer.cursor
cmd = InsertCommand(self.buffer, target_pos, self._clipboard)
self.history.execute(cmd)
print(f" ?? 粘貼 {len(self._clipboard)} 個(gè)字符")
def move_cursor(self, pos: int) -> None:
"""移動(dòng)光標(biāo)(不加入 Undo 歷史,與主流編輯器一致)"""
self.buffer.move_cursor(pos)
# ===== Undo/Redo =====
def undo(self, steps: int = 1) -> None:
self.history.undo_many(steps)
def redo(self, steps: int = 1) -> None:
self.history.redo_many(steps)
# ===== 顯示 =====
def show(self) -> None:
print(self.buffer.display())
def show_history(self) -> None:
print(self.history.get_history_summary())
七、完整演示:從零到成品的編輯過(guò)程
def demo_text_editor():
print("=" * 60)
print(" Python 文本編輯器 · 命令模式演示")
print("=" * 60)
editor = TextEditor('hello_world.py')
# ===== 第一階段:輸入初始代碼 =====
print("\n【階段一:輸入代碼】")
editor.type_text('def hello():\n')
editor.type_text(' print("Hello, wrold!")\n') # 故意拼錯(cuò) wrold
editor.type_text('\nhello()')
editor.show()
# ===== 第二階段:發(fā)現(xiàn)錯(cuò)誤,修復(fù)拼寫(xiě) =====
print("\n【階段二:修復(fù)拼寫(xiě)錯(cuò)誤】")
editor.find_replace('wrold', 'world')
editor.show()
# ===== 第三階段:撤銷(xiāo)修復(fù)(看看原來(lái)的錯(cuò)誤)=====
print("\n【階段三:撤銷(xiāo)查找替換】")
editor.undo()
editor.show()
# ===== 第四階段:重做修復(fù) =====
print("\n【階段四:重做修復(fù)】")
editor.redo()
editor.show()
# ===== 第五階段:插入注釋?zhuān)ê昝钛菔荆?====
print("\n【階段五:在開(kāi)頭插入文件注釋?zhuān)ê昝睿?)
header = '# -*- coding: utf-8 -*-\n# Author: Claude\n# Date: 2025-02-25\n\n'
macro = MacroCommand([
InsertCommand(editor.buffer, 0, header),
], macro_name='插入文件頭注釋')
editor.history.execute(macro)
editor.show()
# ===== 第六階段:縮進(jìn)操作 =====
print("\n【階段六:對(duì)函數(shù)體進(jìn)行縮進(jìn)調(diào)整】")
content = editor.buffer.content
body_start = content.index(' print')
body_end = body_start + len(' print("Hello, world!")')
editor.indent_selection(body_start, body_end, indent=True)
# ===== 第七階段:剪切粘貼演示 =====
print("\n【階段七:剪切并移動(dòng)代碼塊】")
content = editor.buffer.content
call_start = content.rfind('\nhello()')
call_end = len(content)
editor.cut(call_start, call_end)
editor.type_text('\n\nif __name__ == "__main__":')
editor.type_text('\n hello()')
editor.show()
# ===== 第八階段:多步撤銷(xiāo) =====
print("\n【階段八:連續(xù)撤銷(xiāo) 3 步】")
editor.undo(3)
editor.show()
# ===== 查看歷史 =====
print("\n【操作歷史摘要】")
editor.show_history()
# ===== 統(tǒng)計(jì)信息 =====
print(f"\n【統(tǒng)計(jì)信息】")
print(f" 當(dāng)前文本: {len(editor.buffer.content)} 字符")
print(f" 可撤銷(xiāo): {editor.history.undo_count} 步")
print(f" 可重做: {editor.history.redo_count} 步")
demo_text_editor()
八、進(jìn)階:命令合并優(yōu)化(減少 Undo 粒度)
真實(shí)編輯器中,連續(xù)輸入字符不會(huì)每個(gè)字符都生成一條 Undo 記錄,而是將短時(shí)間內(nèi)的連續(xù)輸入合并為一條:
import time
class MergeableInsertCommand(InsertCommand):
"""可合并的插入命令(連續(xù)輸入自動(dòng)合并)"""
MERGE_INTERVAL = 0.5 # 500ms 內(nèi)的連續(xù)輸入合并為一條
def __init__(self, buffer: TextBuffer, pos: int, text: str):
super().__init__(buffer, pos, text)
self.timestamp = time.monotonic()
def try_merge(self, other: 'MergeableInsertCommand') -> bool:
"""
嘗試與后續(xù)命令合并
條件:時(shí)間間隔短 + 位置連續(xù) + 不含換行
"""
if not isinstance(other, MergeableInsertCommand):
return False
time_ok = (other.timestamp - self.timestamp) < self.MERGE_INTERVAL
pos_ok = (self.pos + len(self.text) == other.pos)
content_ok = '\n' not in other.text
if time_ok and pos_ok and content_ok:
self.text += other.text # 合并文本
self.timestamp = other.timestamp
return True
return False
class SmartCommandHistory(CommandHistory):
"""智能歷史管理器:支持命令合并"""
def execute(self, command: Command) -> None:
# 嘗試與棧頂命令合并
if (self._undo_stack and
isinstance(command, MergeableInsertCommand) and
isinstance(self._undo_stack[-1], MergeableInsertCommand)):
top = self._undo_stack[-1]
if top.try_merge(command):
# 合并成功:直接執(zhí)行而不入棧
command.execute()
print(f" ? 合并輸入: 「{top.text}」")
return
super().execute(command)
九、最佳實(shí)踐與常見(jiàn)陷阱
最佳實(shí)踐一:命令必須是完全自包含的。 每個(gè)命令對(duì)象應(yīng)該包含執(zhí)行和撤銷(xiāo)所需的全部信息,不依賴外部可變狀態(tài)。
最佳實(shí)踐二:Undo 必須精確還原,而非近似還原。 特別是光標(biāo)位置、選區(qū)狀態(tài),都應(yīng)在執(zhí)行前保存,在 Undo 時(shí)精確恢復(fù)。
最佳實(shí)踐三:宏命令的異?;貪L。 如示例所示,宏命令中任何子步驟失敗都應(yīng)觸發(fā)已執(zhí)行步驟的逆序回滾,保證原子性。
常見(jiàn)陷阱一:Redo 棧的清理時(shí)機(jī)。 執(zhí)行新命令時(shí)必須清空 Redo 棧(主流編輯器行為),否則 Redo 操作會(huì)產(chǎn)生歧義。
常見(jiàn)陷阱二:無(wú)限歷史的內(nèi)存風(fēng)險(xiǎn)。 使用 collections.deque(maxlen=N) 限制歷史深度,或?qū)Υ蟛僮鳎ㄈ缛奶鎿Q)進(jìn)行壓縮存儲(chǔ)。
設(shè)計(jì)哲學(xué): 命令模式的核心不是「記錄狀態(tài)」而是「封裝意圖」。每條命令代表用戶的一次有意義操作,Undo 是用戶意圖的逆轉(zhuǎn),而不是時(shí)間機(jī)器。
十、總結(jié)
命令模式通過(guò)將操作封裝為對(duì)象,優(yōu)雅地解決了撤銷(xiāo)/重做這一經(jīng)典難題。本文構(gòu)建的完整系統(tǒng)覆蓋了以下核心能力:
每種命令(插入、刪除、替換、查找替換、縮進(jìn))都攜帶完整的撤銷(xiāo)信息;MacroCommand 將多步操作組合為原子單元,整體可撤銷(xiāo);CommandHistory 用雙棧模型管理 Undo/Redo,支持批量操作和歷史限制;MergeableInsertCommand 實(shí)現(xiàn)連續(xù)輸入的智能合并,貼近真實(shí)編輯器體驗(yàn)。
命令模式的應(yīng)用遠(yuǎn)不止于文本編輯器——數(shù)據(jù)庫(kù)事務(wù)、游戲操作回放、GUI 按鈕綁定、任務(wù)隊(duì)列調(diào)度,處處都是它的身影。一旦你建立起「將操作封裝為對(duì)象」的思維模式,你會(huì)發(fā)現(xiàn)很多原本復(fù)雜的需求都變得清晰而優(yōu)雅。
你在項(xiàng)目中實(shí)現(xiàn)過(guò)撤銷(xiāo)功能嗎?是用命令模式,還是有其他方案?歡迎在評(píng)論區(qū)分享你的設(shè)計(jì)思路,讓我們一起探索更優(yōu)雅的 Python 編程之道。
以上就是基于Python打造支持Undo/Redo的文本編輯器命令系統(tǒng)的詳細(xì)內(nèi)容,更多關(guān)于Python文本編輯器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python爬蟲(chóng)實(shí)例_城市公交網(wǎng)絡(luò)站點(diǎn)數(shù)據(jù)的爬取方法
下面小編就為大家分享一篇Python爬蟲(chóng)實(shí)例_城市公交網(wǎng)絡(luò)站點(diǎn)數(shù)據(jù)的爬取方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
python機(jī)器學(xué)習(xí)之神經(jīng)網(wǎng)絡(luò)(三)
這篇文章主要為大家詳細(xì)介紹了python機(jī)器學(xué)習(xí)之神經(jīng)網(wǎng)絡(luò)第三篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
flask框架配置mysql數(shù)據(jù)庫(kù)操作詳解
這篇文章主要介紹了flask框架配置mysql數(shù)據(jù)庫(kù)操作,結(jié)合實(shí)例形式詳細(xì)分析了flask框架配置mysql數(shù)據(jù)庫(kù)及連接訪問(wèn)等相關(guān)操作技巧,需要的朋友可以參考下2019-11-11
Python使用ftplib實(shí)現(xiàn)簡(jiǎn)易FTP客戶端的方法
這篇文章主要介紹了Python使用ftplib實(shí)現(xiàn)簡(jiǎn)易FTP客戶端的方法,實(shí)例分析了ftplib模塊相關(guān)設(shè)置與使用技巧,需要的朋友可以參考下2015-06-06

