最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Python打造支持Undo/Redo的文本編輯器命令系統(tǒng)

 更新時(shí)間:2026年02月26日 08:47:25   作者:銘淵老黃  
這篇文章主要介紹了如何使用命令模式(Command Pattern)在Python中實(shí)現(xiàn)支持撤銷(Undo)和重做(Redo)功能的文本編輯器系統(tǒng),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下

一、引言:你的 Ctrl+Z 背后藏著什么秘密?

每天打開代碼編輯器,你最常按的快捷鍵是什么?

對大多數(shù)開發(fā)者來說,Ctrl+Z(撤銷)和 Ctrl+Y(重做)可能是僅次于保存的高頻操作。寫錯(cuò)了一段代碼?撤銷。誤刪了一個(gè)函數(shù)?撤銷。一路 Redo 找回剛才的思路?重做。

這一切看似魔法的背后,正是軟件工程中一個(gè)優(yōu)雅而強(qiáng)大的設(shè)計(jì)模式:命令模式(Command Pattern)。

初學(xué)者可能會(huì)想:撤銷不就是「保存歷史狀態(tài),出錯(cuò)時(shí)回滾」嗎?用一個(gè)列表記錄每次操作前的文本不就行了?

這種方案在文本量小時(shí)可行,但在復(fù)雜系統(tǒng)中代價(jià)極高——每次操作都復(fù)制整個(gè)狀態(tài),內(nèi)存消耗隨歷史深度線性增長,且無法區(qū)分「哪些操作可以合并」「哪些操作是原子的」。

命令模式的解法更加精妙:將每一個(gè)操作封裝成獨(dú)立的對象,對象內(nèi)同時(shí)包含「執(zhí)行」和「撤銷」的邏輯。 撤銷不是回滾狀態(tài),而是執(zhí)行「反向命令」——插入的反操作是刪除,移動(dòng)的反操作是移回原位。

本文將從零構(gòu)建一個(gè)功能完整的文本編輯器命令系統(tǒng),覆蓋基礎(chǔ)命令實(shí)現(xiàn)、命令組合、宏命令、歷史管理、以及生產(chǎn)級的持久化方案。

二、命令模式基礎(chǔ):四個(gè)核心角色

2.1 角色定義

  • Command(抽象命令):定義執(zhí)行 execute() 和撤銷 undo() 的接口。
  • ConcreteCommand(具體命令):封裝一個(gè)具體操作及其撤銷邏輯,持有接收者的引用。
  • Receiver(接收者):真正執(zhí)行業(yè)務(wù)邏輯的對象(此處為文本編輯器核心)。
  • Invoker(調(diào)用者):持有命令對象,管理命令歷史,負(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:
        """撤銷命令(執(zhí)行反操作)"""
        pass

    @property
    def description(self) -> str:
        """命令描述,用于歷史記錄展示"""
        return self.__class__.__name__

三、構(gòu)建文本編輯器核心(接收者)

在動(dòng)手寫命令之前,先構(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 高級命令:查找替換

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)命令(對選區(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è)可整體撤銷的原子操作
    典型用途:「粘貼」= 先刪除選區(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:
        """逆序撤銷所有子命令"""
        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í)行命令并推入撤銷棧"""
        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:
        """撤銷最近一條命令"""
        if not self._undo_stack:
            print("  ? 沒有可撤銷的操作")
            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"  ? 撤銷: {command.description}")
        return True

    def redo(self) -> bool:
        """重做最近一條被撤銷的命令"""
        if not self._redo_stack:
            print("  ? 沒有可重做的操作")
            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:
        """批量撤銷多步"""
        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("  [可撤銷操作](從新到舊):")
        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}")

六、完整集成:文本編輯器門面類

class TextEditor:
    """
    文本編輯器門面類(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ù)制 + 刪除(兩步封裝為宏命令,整體可撤銷)
        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())

七、完整演示:從零到成品的編輯過程

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ù)拼寫 =====
    print("\n【階段二:修復(fù)拼寫錯(cuò)誤】")
    editor.find_replace('wrold', 'world')
    editor.show()

    # ===== 第三階段:撤銷修復(fù)(看看原來的錯(cuò)誤)=====
    print("\n【階段三:撤銷查找替換】")
    editor.undo()
    editor.show()

    # ===== 第四階段:重做修復(fù) =====
    print("\n【階段四:重做修復(fù)】")
    editor.redo()
    editor.show()

    # ===== 第五階段:插入注釋(宏命令演示)=====
    print("\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【階段六:對函數(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()

    # ===== 第八階段:多步撤銷 =====
    print("\n【階段八:連續(xù)撤銷 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"  可撤銷: {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í)踐與常見陷阱

最佳實(shí)踐一:命令必須是完全自包含的。 每個(gè)命令對象應(yīng)該包含執(zhí)行和撤銷所需的全部信息,不依賴外部可變狀態(tài)。

最佳實(shí)踐二:Undo 必須精確還原,而非近似還原。 特別是光標(biāo)位置、選區(qū)狀態(tài),都應(yīng)在執(zhí)行前保存,在 Undo 時(shí)精確恢復(fù)。

最佳實(shí)踐三:宏命令的異?;貪L。 如示例所示,宏命令中任何子步驟失敗都應(yīng)觸發(fā)已執(zhí)行步驟的逆序回滾,保證原子性。

常見陷阱一:Redo 棧的清理時(shí)機(jī)。 執(zhí)行新命令時(shí)必須清空 Redo 棧(主流編輯器行為),否則 Redo 操作會(huì)產(chǎn)生歧義。

常見陷阱二:無限歷史的內(nèi)存風(fēng)險(xiǎn)。 使用 collections.deque(maxlen=N) 限制歷史深度,或?qū)Υ蟛僮鳎ㄈ缛奶鎿Q)進(jìn)行壓縮存儲。

設(shè)計(jì)哲學(xué): 命令模式的核心不是「記錄狀態(tài)」而是「封裝意圖」。每條命令代表用戶的一次有意義操作,Undo 是用戶意圖的逆轉(zhuǎn),而不是時(shí)間機(jī)器。

十、總結(jié)

命令模式通過將操作封裝為對象,優(yōu)雅地解決了撤銷/重做這一經(jīng)典難題。本文構(gòu)建的完整系統(tǒng)覆蓋了以下核心能力:

每種命令(插入、刪除、替換、查找替換、縮進(jìn))都攜帶完整的撤銷信息;MacroCommand 將多步操作組合為原子單元,整體可撤銷;CommandHistory 用雙棧模型管理 Undo/Redo,支持批量操作和歷史限制;MergeableInsertCommand 實(shí)現(xiàn)連續(xù)輸入的智能合并,貼近真實(shí)編輯器體驗(yàn)。

命令模式的應(yīng)用遠(yuǎn)不止于文本編輯器——數(shù)據(jù)庫事務(wù)、游戲操作回放、GUI 按鈕綁定、任務(wù)隊(duì)列調(diào)度,處處都是它的身影。一旦你建立起「將操作封裝為對象」的思維模式,你會(huì)發(fā)現(xiàn)很多原本復(fù)雜的需求都變得清晰而優(yōu)雅。

你在項(xiàng)目中實(shí)現(xiàn)過撤銷功能嗎?是用命令模式,還是有其他方案?歡迎在評論區(qū)分享你的設(shè)計(jì)思路,讓我們一起探索更優(yōu)雅的 Python 編程之道。

以上就是基于Python打造支持Undo/Redo的文本編輯器命令系統(tǒng)的詳細(xì)內(nèi)容,更多關(guān)于Python文本編輯器的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • python使用百度文字識別功能方法詳解

    python使用百度文字識別功能方法詳解

    在本篇文章里小編給大家整理的是關(guān)于python怎么使用百度文字識別功能的相關(guān)知識點(diǎn),有興趣的朋友們參考下。
    2019-07-07
  • Python爬蟲實(shí)例_城市公交網(wǎng)絡(luò)站點(diǎn)數(shù)據(jù)的爬取方法

    Python爬蟲實(shí)例_城市公交網(wǎng)絡(luò)站點(diǎn)數(shù)據(jù)的爬取方法

    下面小編就為大家分享一篇Python爬蟲實(shí)例_城市公交網(wǎng)絡(luò)站點(diǎn)數(shù)據(jù)的爬取方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • python機(jī)器學(xué)習(xí)之神經(jīng)網(wǎng)絡(luò)(三)

    python機(jī)器學(xué)習(xí)之神經(jīng)網(wǎng)絡(luò)(三)

    這篇文章主要為大家詳細(xì)介紹了python機(jī)器學(xué)習(xí)之神經(jīng)網(wǎng)絡(luò)第三篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • 基于Pytorch SSD模型分析

    基于Pytorch SSD模型分析

    今天小編就為大家分享一篇基于Pytorch SSD模型分析,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-02-02
  • python中metaclass原理與用法詳解

    python中metaclass原理與用法詳解

    這篇文章主要介紹了python中metaclass原理與用法,結(jié)合具體實(shí)例形式分析了Python中metaclass的功能、原理及使用metaclass動(dòng)態(tài)創(chuàng)建類相關(guān)操作技巧,需要的朋友可以參考下
    2019-06-06
  • flask框架配置mysql數(shù)據(jù)庫操作詳解

    flask框架配置mysql數(shù)據(jù)庫操作詳解

    這篇文章主要介紹了flask框架配置mysql數(shù)據(jù)庫操作,結(jié)合實(shí)例形式詳細(xì)分析了flask框架配置mysql數(shù)據(jù)庫及連接訪問等相關(guān)操作技巧,需要的朋友可以參考下
    2019-11-11
  • python遞歸算法(無限遞歸,正常遞歸,階乘)

    python遞歸算法(無限遞歸,正常遞歸,階乘)

    本文主要介紹了python遞歸算法,包含無限遞歸,正常遞歸,階乘等,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-02-02
  • Python使用ftplib實(shí)現(xiàn)簡易FTP客戶端的方法

    Python使用ftplib實(shí)現(xiàn)簡易FTP客戶端的方法

    這篇文章主要介紹了Python使用ftplib實(shí)現(xiàn)簡易FTP客戶端的方法,實(shí)例分析了ftplib模塊相關(guān)設(shè)置與使用技巧,需要的朋友可以參考下
    2015-06-06
  • python如何操作mysql

    python如何操作mysql

    這篇文章主要介紹了python如何操作MySQL,幫助大家更好的理解和學(xué)習(xí)python,感興趣的朋友可以了解下
    2020-08-08
  • Python自帶的IDE在哪里

    Python自帶的IDE在哪里

    在本篇內(nèi)容里小編給大家分享的是關(guān)于如何找到Python自帶的IDE的相關(guān)內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。
    2020-07-07

最新評論

新昌县| 抚松县| 兰考县| 江孜县| 陈巴尔虎旗| 紫云| 龙游县| 蒙阴县| 濮阳市| 河南省| 驻马店市| 山阳县| 澄城县| 贵南县| 竹北市| 镇平县| 郎溪县| 开封县| 巴彦县| 长岭县| 镇原县| 琼中| 革吉县| 区。| 华容县| 宿迁市| 华亭县| 福州市| 奉贤区| 会理县| 楚雄市| 绥德县| 潞城市| 苍溪县| 眉山市| 屯留县| 临颍县| 宣威市| 卫辉市| 凤翔县| 四川省|