123

土木工学アプリケーション用の構造モデリングツールを書いています。建物全体を表す1つの巨大なモデルクラスがあります。これには、カスタムクラスでもあるノード、ライン要素、荷重などのコレクションが含まれます。

モデルに変更を加えるたびにディープコピーを保存するUNDOエンジンをすでにコーディングしました。今、私は別の方法でコーディングできたかどうかを考え始めました。ディープコピーを保存する代わりに、対応する逆修飾子を使用して各修飾子アクションのリストを保存することもできます。現在のモデルに逆修飾子を適用して元に戻したり、修飾子を適用してやり直したりできるようにします。

オブジェクトのプロパティなどを変更する単純なコマンドをどのように実行するか想像できますが、複雑なコマンドはどうでしょうか。モデルに新しいノードオブジェクトを挿入し、新しいノードへの参照を保持するいくつかのラインオブジェクトを追加するようなものです。

それをどのように実装するのでしょうか?

4

22 に答える 22

94

私が見たほとんどの例では、これにCommand-Patternのバリアントを使用しています。元に戻すことができるすべてのユーザーアクションは、アクションを実行してロールバックするためのすべての情報を含む独自のコマンドインスタンスを取得します。次に、実行されたすべてのコマンドのリストを維持し、それらを1つずつロールバックできます。

于 2008-09-08T14:00:58.273 に答える
36

OPが意味するサイズとスコープのモデルを扱っている場合、mementoとcommandの両方は実用的ではないと思います。それらは機能しますが、維持および拡張するには多くの作業が必要になります。

このタイプの問題では、モデルに含まれるすべてのオブジェクトの差分チェックポイントをサポートするために、データモデルのサポートを組み込む必要があると思います。私はこれを一度行ったことがあり、それは非常にスムーズに機能しました。あなたがしなければならない最大のことは、モデル内でポインターや参照を直接使用しないようにすることです。

別のオブジェクトへのすべての参照は、何らかの識別子(整数など)を使用します。オブジェクトが必要なときはいつでも、テーブルからオブジェクトの現在の定義を検索します。このテーブルには、以前のすべてのバージョンを含む各オブジェクトのリンクリストと、それらがアクティブだったチェックポイントに関する情報が含まれています。

元に戻す/やり直しの実装は簡単です。アクションを実行して、新しいチェックポイントを確立します。すべてのオブジェクトバージョンを前のチェックポイントにロールバックします。

コードにはある程度の規律が必要ですが、多くの利点があります。モデル状態の差分ストレージを実行しているため、ディープコピーは必要ありません。使用するREDOの数または使用するメモリのいずれかによって、使用するメモリの量(CADモデルなどでは非常に重要)をスコープできます。元に戻す/やり直しを実装するために何もする必要がないため、モデルで動作する関数の非常にスケーラブルでメンテナンスが少なくて済みます。

于 2009-02-17T21:09:48.373 に答える
17

GoFについて話している場合、Mementoパターンは特にundoに対応しています。

于 2008-09-08T15:00:38.817 に答える
16

他の人が述べているように、コマンド パターンは元に戻す/やり直しを実装する非常に強力な方法です。しかし、コマンド パターンには、言及したい重要な利点があります。

コマンド パターンを使用して元に戻す/やり直しを実装する場合、データに対して実行される操作を (ある程度) 抽象化することで大量の重複コードを回避し、それらの操作を元に戻す/やり直しシステムで利用できます。たとえば、テキスト エディタでは、切り取りと貼り付けは補完的なコマンドです (クリップボードの管理は別として)。つまり、切り取りの元に戻す操作は貼り付けであり、貼り付けの元に戻す操作は切り取りです。これは、テキストの入力や削除などのより単純な操作に適用されます。

ここで重要なのは、元に戻す/やり直しシステムをエディターの主要なコマンド システムとして使用できることです。「元に戻すオブジェクトを作成し、ドキュメントを変更する」というシステムを記述する代わりに、「元に戻すオブジェクトを作成し、元に戻すオブジェクトに対してやり直し操作を実行してドキュメントを変更する」ことができます。

さて、確かに、多くの人が「まあ、コマンド パターンの要点の一部ではないか?」と考えています。はい。しかし、2 つのコマンド セットを持つコマンド システムが多すぎます。1 つは即時操作用で、もう 1 つは元に戻す/やり直し用です。即時操作と元に戻す/やり直しに固有のコマンドがなくなると言っているわけではありませんが、重複を減らすことでコードがより保守しやすくなります。

于 2008-09-08T17:12:38.420 に答える
8

元に戻すにはPaint.NETコードを参照することをお勧めします-彼らは本当に素晴らしい元につくシステムを持っています。それはおそらくあなたが必要とするものより少し単純ですが、それはあなたにいくつかのアイデアとガイドラインを与えるかもしれません。

-アダム

于 2008-09-08T14:05:50.883 に答える
6

これは、 CSLAが適用可能な場合である可能性があります。これは、Windowsフォームアプリケーションのオブジェクトに複雑な取り消しサポートを提供するように設計されています。

于 2008-09-08T14:10:15.160 に答える
6

Memento パターンを使用して複雑な元に戻すシステムをうまく実装しました。非常に簡単で、自然に Redo フレームワークも提供されるという利点があります。より微妙な利点は、集約アクションも 1 つの Undo 内に含めることができることです。

簡単に言えば、記念品オブジェクトの 2 つのスタックがあります。1 つは取り消し用、もう 1 つはやり直し用です。すべての操作は新しい記念品を作成します。これは理想的には、モデル、ドキュメント (またはその他のもの) の状態を変更するための呼び出しになります。これは元に戻すスタックに追加されます。元に戻す操作を行うときは、Memento オブジェクトに対して元に戻す操作を実行してモデルを元に戻すだけでなく、オブジェクトを元に戻すスタックからポップして、それを直接やり直しスタックにプッシュします。

ドキュメントの状態を変更するメソッドがどのように実装されるかは、実装に完全に依存します。単純に API 呼び出し (ChangeColour(r,g,b) など) を実行できる場合は、その前にクエリを実行して、対応する状態を取得して保存します。ただし、このパターンは、ディープ コピーの作成、メモリ スナップショット、一時ファイルの作成などもサポートします。これは単に仮想メソッドの実装であるため、すべてユーザー次第です。

集約アクション (たとえば、ユーザーが Shift キーを押しながらオブジェクトのロードを選択して、削除、名前の変更、属性の変更などの操作を行う) を行うには、コードで新しい Undo スタックを単一のメモとして作成し、それを実際の操作に渡します。個々の操作を追加します。したがって、アクション メソッドは、(a) 心配するグローバル スタックを持つ必要がなく、(b) 単独で実行されるか、1 つの集計操作の一部として実行されるかにかかわらず、同じようにコーディングできます。

多くの元に戻すシステムはメモリ内のみですが、必要に応じて元に戻すスタックを永続化することもできます。

于 2008-09-08T17:03:29.973 に答える
5

私のアジャイル開発の本でコマンドパターンについて読んでいるところですが、それは可能性があるのでしょうか?

すべてのコマンドにコマンドインターフェイス(Execute()メソッドがある)を実装させることができます。元に戻す必要がある場合は、元に戻るメソッドを追加できます。

詳細はこちら

于 2008-09-08T14:02:08.550 に答える
4

コマンド パターンを使用する必要があるという事実については、メンデルト シーベンガと一緒です。使用したパターンは Memento パターンで、時間の経過とともに非常に無駄になる可能性があります。

メモリを集中的に使用するアプリケーションで作業しているため、元に戻すエンジンが使用できるメモリの量、保存する元に戻すレベルの数、または保持するストレージを指定できる必要があります。これを行わないと、すぐにマシンのメモリ不足によるエラーに直面することになります。

選択したプログラミング言語/フレームワークで元に戻すためのモデルを既に作成したフレームワークがあるかどうかを確認することをお勧めします。新しいものを発明するのは良いことですが、実際のシナリオで既に作成、デバッグ、テストされたものを使用する方がよいでしょう。これを書いているものを追加すると、人々が知っているフレームワークを推奨できるようになります。

于 2008-09-08T15:51:54.667 に答える
3

Codeplexプロジェクト

これは、従来のコマンドデザインパターンに基づいて、アプリケーションに元に戻す/やり直し機能を追加するためのシンプルなフレームワークです。これは、アクションのマージ、ネストされたトランザクション、実行の遅延(トップレベルのトランザクションコミットでの実行)、および可能な非線形の取り消し履歴(やり直す複数のアクションを選択できる場合)をサポートします。

于 2009-06-30T06:04:43.677 に答える
2

ペグジャンプパズルゲームのソルバーを作成するときに、これを行う必要がありました。それぞれの動きに、実行または元に戻すことができる十分な情報を保持するCommandオブジェクトを作成しました。私の場合、これは開始位置と各移動の方向を保存するのと同じくらい簡単でした。次に、これらすべてのオブジェクトをスタックに保存して、プログラムがバックトラック中に必要な数の移動を簡単に元に戻せるようにしました。

于 2008-09-08T14:13:27.073 に答える
2

私が読んだほとんどの例は、コマンドまたは memento パターンのいずれかを使用してそれを行います。しかし、単純なdeque-structureを使用すれば、デザイン パターンがなくても実行できます。

于 2008-09-08T16:08:18.357 に答える
2

参考までに、C# での元に戻す/やり直しのコマンド パターンの簡単な実装を次に示します: C# の単純な元に戻す/やり直しシステム

于 2009-03-19T18:49:24.390 に答える
2

ソフトウェアをマルチ ユーザー コラボレーションにも適したものにする undo を処理する賢い方法は、データ構造の操作上の変換を実装することです。

この概念はあまり一般的ではありませんが、明確に定義されており、便利です。定義が抽象的すぎると思われる場合、このプロジェクトは、JSON オブジェクトの運用上の変換を Javascript で定義および実装する方法の成功例です。

于 2014-09-05T12:09:40.543 に答える
1

オブジェクトの状態全体を保存および復元するための便利な形式として、「オブジェクト」のファイルの読み込みと保存のシリアル化コードを再利用しました。これらのシリアル化されたオブジェクトを、実行された操作に関する情報と、シリアル化されたデータから収集された情報が十分でない場合にその操作を元に戻すためのヒントとともに、元に戻すスタックにプッシュします。元に戻すとやり直しは、多くの場合、1 つのオブジェクトを別のオブジェクトに置き換えるだけです (理論上)。

オブジェクトへのポインター (C++) が原因で、多くのバグが発生しました。オブジェクトへのポインター (C++) は、奇妙な元に戻すやり直しシーケンスを実行しても修正されませんでした (これらの場所は、より安全な元に戻すを認識する「識別子」に更新されませんでした)。このエリアのバグはしばしば...うーん...興味深い。

一部の操作は、速度/リソース使用量の特殊なケースになる可能性があります-サイズ変更、移動など。

複数選択は、いくつかの興味深い複雑さも提供します。幸いなことに、コードにはグループ化の概念がすでにありました。サブアイテムに関するクリストファー・ジョンソンのコメントは、私たちがしていることにかなり近いものです。

于 2008-09-08T16:43:12.860 に答える
1

PostSharp で元に戻す/やり直しパターンの既製の実装を試すことができます。https://www.postsharp.net/model/undo-redo

パターンを自分で実装しなくても、元に戻す/やり直し機能をアプリケーションに追加できます。記録可能なパターンを使用してモデルの変更を追跡し、PostSharp にも実装されている INotifyPropertyChanged パターンと連携します。

UI コントロールが提供され、各操作の名前と粒度を決定できます。

于 2016-05-04T09:44:32.243 に答える
0

私はかつて、アプリケーションのモデルに対するコマンドによって行われたすべての変更(つまり、CDocument ... MFCを使用していた)が、モデル内に維持されている内部データベースのフィールドを更新することによってコマンドの最後に保持されるアプリケーションで作業しました。そのため、アクションごとに個別の元に戻る/やり直しコードを記述する必要はありませんでした。UNDOスタックは、レコードが変更されるたびに(各コマンドの最後に)、主キー、フィールド名、および古い値を単に記憶していました。

于 2008-09-08T15:17:19.133 に答える
0

最初のアイデアをパフォーマンスに変えることができます。

永続的なデータ構造を使用し、古い状態への参照のリストを保持することに固執します。(ただし、状態クラス内のすべてのデータの操作が不変であり、それに対するすべての操作が新しいバージョンを返す場合にのみ、実際に機能します。ただし、新しいバージョンはディープ コピーである必要はなく、変更された部分を置き換えるだけです 'copy -on-write'.)

于 2016-07-26T06:36:06.280 に答える
0

ここでは Command パターンが非常に役立つことがわかりました。いくつかのリバース コマンドを実装する代わりに、API の 2 番目のインスタンスで実行を遅らせてロールバックを使用しています。

このアプローチは、実装の手間を減らして保守を容易にしたい場合 (および 2 番目のインスタンス用に追加のメモリを確保できる場合) には妥当と思われます。

例については、 https ://github.com/thilo20/Undo/ を参照して ください。

于 2018-03-12T23:24:06.287 に答える
-1

私の意見では、UNDO/REDO は大きく 2 つの方法で実装できます。1. コマンド レベル (コマンド レベルの取り消し/やり直しと呼ばれる) 2. ドキュメント レベル (グローバルな取り消し/やり直しと呼ばれる)

コマンド レベル: 多くの回答が指摘しているように、これは Memento パターンを使用して効率的に達成されます。コマンドがアクションのジャーナル化もサポートしている場合、やり直しは簡単にサポートされます。

制限: コマンドの範囲外になると、元に戻す/やり直しが不可能になり、ドキュメント レベル (グローバル) の元に戻す/やり直しが発生します。

多くのメモリ空間を必要とするモデルに適しているため、あなたのケースはグローバルな元に戻す/やり直しに適合すると思います。また、これは選択的に元に戻す/やり直しにも適しています。2 つのプリミティブ型があります

  1. すべてのメモリの取り消し/やり直し
  2. オブジェクト レベル 元に戻す やり直し

「全メモリ Undo/Redo」では、メモリ全体を接続されたデータ (ツリー、リスト、グラフなど) として扱い、メモリは OS ではなくアプリケーションによって管理されます。そのため、C++ の new 演算子と delete 演算子は、a などの操作を効果的に実装するためのより具体的な構造を含むようにオーバーロードされます。いずれかのノードが変更された場合、b. データの保持とクリアなど、基本的に機能する方法は、メモリ全体をコピーし(メモリ割り当てが高度なアルゴリズムを使用してアプリケーションによって既に最適化および管理されていると仮定)、スタックに保存します。メモリのコピーが要求された場合、浅いコピーまたは深いコピーの必要性に基づいてツリー構造がコピーされます。ディープ コピーは、変更された変数に対してのみ作成されます。すべての変数はカスタム割り当てを使用して割り当てられるため、アプリケーションは、必要に応じていつ削除するかを最終的に決定します。一連の操作をプログラムで選択的に元に戻す/やり直す必要がある場合に、元に戻す/やり直しを分割する必要がある場合、事態は非常に興味深いものになります。この場合、これらの新しい変数、または削除された変数または変更された変数のみにフラグが与えられるため、元に戻す/やり直しはそれらのメモリのみを元に戻す/やり直すことができます。オブジェクト内で部分的な元に戻す/やり直す必要がある場合、事態はさらに興味深いものになります。そんな時は「ビジターパターン」という新しい発想が使われます。「オブジェクト レベルの取り消し/やり直し」と呼ばれます。または、削除された変数または変更された変数にはフラグが与えられるため、元に戻す/やり直しはそれらのメモリのみを元に戻す/やり直す オブジェクト内で部分的な元に戻す/やり直す必要がある場合、事態はさらに興味深いものになります。そんな時は「ビジターパターン」という新しい発想が使われます。「オブジェクト レベルの取り消し/やり直し」と呼ばれます。または、削除された変数または変更された変数にはフラグが与えられるため、元に戻す/やり直しはそれらのメモリのみを元に戻す/やり直す オブジェクト内で部分的な元に戻す/やり直す必要がある場合、事態はさらに興味深いものになります。そんな時は「ビジターパターン」という新しい発想が使われます。「オブジェクト レベルの取り消し/やり直し」と呼ばれます。

  1. オブジェクト レベルの元に戻す/やり直し: 元に戻す/やり直しの通知が呼び出されると、すべてのオブジェクトはストリーミング操作を実装します。ストリーマーは、プログラムされた古いデータ/新しいデータをオブジェクトから取得します。乱されないデータはそのまま残されます。すべてのオブジェクトは引数としてストリーマーを取得し、UNDo/Redo 呼び出し内で、オブジェクトのデータをストリーミング/アンストリーミングします。

1 と 2 の両方に、1. BeforeUndo() 2. AfterUndo() 3. BeforeRedo() 4. AfterRedo() などのメソッドを含めることができます。これらのメソッドは、特定のアクションを取得するためにすべてのオブジェクトがこれらのメソッドを実装するように、基本的な元に戻す/やり直しコマンド (コンテキスト コマンドではない) で公開する必要があります。

優れた戦略は、1 と 2 のハイブリッドを作成することです。これらのメソッド (1 と 2) 自体がコマンド パターンを使用している点が優れています。

于 2016-05-06T13:29:54.727 に答える
-1

これがあなたに役立つかどうかはわかりませんが、私のプロジェクトの 1 つで似たようなことをしなければならなかったとき、http://www.undomadeeasy.com から UndoEngine をダウンロードすることになりました-素晴らしいエンジンですボンネットの下にあるものはあまり気にしませんでした。

于 2010-09-28T15:56:31.177 に答える