元に戻すとやり直し-分析と実装

こんにちは、Habr! 私の実際のタスクに関連して、いわゆる「戻る」(元に戻す)と「進む」(やり直し)の実装のためのQtと.NETの機能を分析するためにそれぞれアクションキャンセルし、キャンセルキャンセルすることで、すべての考え、アイデア、アイデアを展開することにしましたこの記事は、それらが部分的または完全に間違っている場合でも(したがって、可能であれば、興味がある場合は、コメントにコメントを記入してください)。 インターネット上では良い(そうではない)ライブラリと実装の例を簡単に見つけることができますが、これらのことのより一般的なアイデアはすぐには見つかりませんでしたが、それはStackOverflowにのみ対応していましたが、 それでは十分ではありませんでした。 見つかったものすべてに、私を喜ばせる瞬間があります。 恐らくすべての悲しみと喜びをキャンセルする価値があるでしょう...再び彼らに戻るために...「未来へ...」









面白い? ようこそ





リサーチ



赤か青か アプリケーションに元に戻す/やり直しを実装することを決めた後、この種の質問を考え出す必要があります。 私は説明します:ステップバイステップのアンドゥを実装する2つの主な方法があります。それには、 操作指向 指向という名前を割り当てました。 最初の方法は、操作(またはトランザクション)の作成に基づいています。これには2つの方法があります。つまり、すべてを実行し、すべてを返します。 2番目の方法では、操作は保存されません。特定の時点で変更された値のみが記録されます。 最初と2番目の方法には両方とも長所と短所があります。



UPD:今後の質問を少なくするために、Undo / Redoは、 編集中にドキュメントの以前のバージョン(たとえば)からの情報を保存することを目的としていることを思い出させてください。 データベースまたはディスクにデータを書き込むには時間がかかりますが、これはUndo / Redoの目標とはほとんど関係ありません。 ただし、本当に必要な場合は-それを行いますが、しない方が良いです。



方法1:操作指向



「コマンド」パターンに基づいて実装されます。

この方法は、操作を特別なスタックに保存することです。 スタックには、最後の操作を示す位置(イテレーターと言うことができます)があります。 スタックに操作が追加されると、その操作が実行(やり直し)され、位置が増分されます。 操作をキャンセルするには、スタックは最後の操作から元に戻すコマンドを呼び出し、最後の操作の位置を下に移動します(移動しますが、削除はしません)。 アクションを返す必要がある場合-上記のシフト、やり直しの実行。 キャンセル後に新しい操作が追加された場合、つまり2つの解決策:位置の上の操作を新しいものに置き換えるか(前の操作に戻ることはできません)、スタックで新しい「ブランチ」を開始しますが、問題が発生します-どのブランチに行くべきですか? ただし、プログラムの要件に依存するため、この質問に対する回答は検索する必要がなくなりました。



Undo / Redo自体には、以下が必要です:純粋な仮想(抽象)関数undo()およびredo()を持つ基本クラス(インターフェイス)、基本クラスから派生したオブジェクトへのポインタを格納するクラス、そしてもちろん、クラス自体、undo()およびredo()関数が再定義されます。 また、(場合によっては、非常に必要な場合もあります)、たとえば、各文字を個別にキャンセルするのではなく、単語や文、文字がそのようになったときなどに、操作を結合する機能を1つにすることができます。 したがって、各操作に特定のタイプを割り当てることをお勧めしますが、その違いは操作を接着することはできません。



そして、長所:



短所:



また、このUndo / Redoメソッドの実装に使用されるCommand Patternに関するこのWiki記事 、およびHabrahabrに関するこの記事も読むことができます。



方法2:価値志向



「キーパー」パターン( Memento )に基づいて実装されます。

このメソッドの原則は、変更可能なすべての変数を把握し、可能な変更の開始時にスタックを「記録」し、最後に変更をコミットすることです。



ただし、すべての変更を記録する必要があります。 ユーザーが行った変更のみが記録され、依存関係の変更が記録されていない場合、キャンセル/リターンしても依存関係は変更されません。 もちろん、毎回依存関係の再計算をトリッキーな方法で呼び出すことができますが、これは最初の方法に似ており、より便利です。 実装方法を以下に説明しますが、ここでは利点と欠点を見てみましょう。



長所:



短所:





Guardianパターン(Memento)に関するこのWiki記事も読むことができます。



悪い方法3:完全なスナップショット



記憶に対する厳格さについて話す場合、この方法は多くを消費します。 1文字だけを入力すると、ドキュメント全体が保存される状況を想像してください。 そして毎回。 提示? 今、この方法を忘れて、もう覚えていない、それは元に戻す/やり直しではなく、バックアップだから。



UPD:いいえ、ここではMementoパターンを意味しませんでした。これは、変更/値の完全なスナップショットに加えて、部分的に保存することもできます。 これは、2、3の値のみが変更された場合にドキュメント全体のスナップショットを保存することはお勧めできないことを意味します。 それでもこれを回避できない場合は、 vl-orの可能性が高く、状況によっては、ドキュメント全体が非常にまれに複雑な方法で変更される場合、そのような変更の記録を拒否できます(この操作後の変更のロールバックは使用不可になることをユーザーに伝えます) )






実装方法



C ++:Qt



操作指向



ここでは、開発者が最善を尽くしました。 Qtを使用すると、Undo / Redoを簡単かつ簡単に実装できます。 レシピを書き留めます。 QUndoStackQUndoCommand 、およびQUndoViewQUndoGroupが必要です。 まず、Undo()とredo()を再定義する必要があるQUndoCommandから独自のクラスを継承します。また、id()を再定義して操作のタイプを決定することをお勧めします。 その後、クラスQUndoStackのオブジェクトを作成し、すべての新しい操作をその中に入れます。 便宜上、スタック関数からQAction * undoおよびQAction * redoを取得できます。これらはメニューに追加したり、ボタンに追加したりできます。 複数のスタックを使用する必要がある場合、操作のリストを表示する必要がある場合は、QUndoGroupが役立ちます:QUndoView。



また、QUndoStackでは、クリア状態をマークできます。たとえば、ドキュメントをディスクなどに保存するかどうかを意味できます。 op-or undo / redoの非常に便利な実装。



Qtで最も単純な例を実装しました。

見たい!
ここに私が来たクラス図があります(ほとんどの場合、矢印の方向について非常に間違っています...):



また、特定の「サーバー」についても説明します。これは、彼がクライアントアプリケーションにも存在し、対話する場合に備えています。 そして、ここにソースがあります (すべてが「膝の上に」書かれていると考えてください)。



価値志向



おっと... Qtはそのようなオプションを提供しませんでした。 「Qt memento」のキーワード検索でも何も得られませんでした。 まあ、大丈夫、そこには十分な量があります。十分でない場合は、ネイティブメソッドを使用できます。



C ++:ネイティブ



Qtは、価値志向のUndo / Redoを追加する必要がないと考えていたため、既成の実装(「Memento」という魔法の言葉が見つかる場所)を探すか、自分で実装する必要があります。 基本的に、すべてはテンプレートに基づいて実装されます。 これらはすべて問題なく見つけることができます。 たとえば、 このプロジェクトはGitHubで見つけました。 ここで、2つのアイデアがすぐに実装されます。テストして、確認してください。



C#:.NET



私にとって、C#と.NETはまだ遠いシベリアの暗い森ですが、それにもかかわらず、本当に必要です。 したがって、少なくともGoogleでなんとか管理できたことを伝える価値はあります。



操作指向



私にとって最高の例は次のとおりです。





すぐに、そのような古い記事が見つかりました



おそらくあなたは何かを見つけることができ、おそらくこれに基づいて、 自転車用の独創的なコードを取得して書くことができます。 どうぞ



価値志向



一般に、.NETのこの種のタスクにはIEditableObjectインターフェイスがありますが、MSDNに直接実装例がありますが、多くのことを最初から実装する必要があります。 それでも、私はDejaVuライブラリが本当に好きでした。そのために、 記事全体でもHabrahabr 書かれていました。 読んで、恋に落ちて、書いてください。



さらに2つの例がありますが、私はそれらがまったく好きではありませんでした。








おわりに



それで、2つの実装方法のうち1つだけを選択するために何を知る必要がありますか? まず、プロジェクトの実装は、コマンドに基づいて書かれていますか(そうですか?)、または値のセットの変更に基づいています(どちらか一方でない場合-プロジェクトを書き直す方が良いと思います)。 第二に、メモリとパフォーマンスの要件。これらの理由により、あるオプションを放棄して別のオプションを優先する必要があるためです。 第三に、何をどのように保存し、何を保存しないのかを正確に知る必要があります。 それは基本的にそれです。



幸運を祈ります!



All Articles