4

私は、数か月前に開始したJavaの小さなUMLエディタープロジェクトに取り組んでいます。数週間後、UMLクラス図エディターの作業用コピーを入手しました。

しかし今、私はそれを完全に再設計して、シーケンス、状態、クラスなどの他のタイプの図をサポートしています。これは、グラフ構築フレームワークを実装することによって行われます(私は、この主題に関するCayHorstmannの作業に大きく影響を受けています。バイオレットUMLエディター)。

友人の1人が、プロジェクトにDo / Undo機能を追加するのを忘れたと言うまで、再設計は順調に進んでいました。これは、私の意見では非常に重要です。

オブジェクト指向のデザインコースを思い出して、すぐにMementoとCommandパターンについて考えました。

これが取引です。2つのArrayListを含む抽象クラスAbstractDiagramがあります。1つはノード(プロジェクトではElementsと呼ばれます)を格納するためのもので、もう1つはEdges(プロジェクトではLinksと呼ばれる)を格納するためのものです。この図は、おそらく、元に戻す/やり直すことができるコマンドのスタックを保持します。かなり標準的です。

これらのコマンドを効率的に実行するにはどうすればよいですか?たとえば、ノードを移動したいとします(ノードはINodeという名前のインターフェイスタイプになり、そこから派生した具象ノード(ClassNode、InterfaceNode、NoteNodeなど)があります)。

位置情報はノード内の属性として保持されるため、ノード自体でその属性を変更することにより、状態が変更されます。表示が更新されると、ノードが移動します。これはパターンのMemento部分です(私は思います)が、オブジェクトが状態そのものであるという違いがあります。

さらに、元のノードのクローンを(移動する前に)保持すると、古いバージョンに戻すことができます。同じ手法が、ノードに含まれる情報(クラス名またはインターフェース名、ノートノードのテキスト、属性名など)にも適用されます。

問題は、図で、元に/やり直し操作時にノードをそのクローンに置き換えるにはどうすればよいですか?ダイアグラムによって参照されている(ノードリストにある)元のオブジェクトのクローンを作成した場合、そのクローンはダイアグラム内で参照されておらず、ポイントしているのはコマンド自体だけです。シャウド私は、IDに従ってノードを見つけるためのメカニズムを図に含めているので(たとえば)、図でノードをそのクローンに置き換えることができますか(またはその逆)?それを行うのはMementoとCommandパターン次第ですか?リンクはどうですか?それらも移動可能である必要がありますが、リンク専用(およびノー​​ド専用)のコマンドを作成したくないので、コマンドのオブジェクトのタイプに応じて適切なリスト(ノードまたはリンク)を変更できる必要がありますを参照しています。

どのように進めますか?つまり、オブジェクトのタイプ(ノードまたはリンク)によっては、オブジェクトの状態をコマンド/ mementoパターンで表現して、オブジェクトを効率的に復元し、元のオブジェクトをダイアグラムリストに復元できるようにするのに問題があります。

どうもありがとう!

ギヨーム。

PS:はっきりしない場合は、教えてください。メッセージを明確にします(いつものように!)。

編集

これが私の実際の解決策であり、この質問を投稿する前に実装を開始しました。

まず、次のように定義されたAbstractCommandクラスがあります。

public abstract class AbstractCommand {
    public boolean blnComplete;

    public void setComplete(boolean complete) {
        this.blnComplete = complete;
    }

    public boolean isComplete() {
        return this.blnComplete;
    }

    public abstract void execute();
    public abstract void unexecute();
}

次に、各タイプのコマンドは、AbstractCommandの具体的な派生を使用して実装されます。

だから私はオブジェクトを移動するコマンドを持っています:

public class MoveCommand extends AbstractCommand {
    Moveable movingObject;
    Point2D startPos;
    Point2D endPos;

    public MoveCommand(Point2D start) {
        this.startPos = start;
    }

    public void execute() {
        if(this.movingObject != null && this.endPos != null)
            this.movingObject.moveTo(this.endPos);
    }

    public void unexecute() {
        if(this.movingObject != null && this.startPos != null)
            this.movingObject.moveTo(this.startPos);
    }

    public void setStart(Point2D start) {
        this.startPos = start;
    }

    public void setEnd(Point2D end) {
        this.endPos = end;
    }
}

MoveRemoveCommandもあります(オブジェクト/ノードを移動または削除するため)。instanceofメソッドのIDを使用する場合、ダイアグラムを実際のノードまたはリンクに渡して、ダイアグラムからそれ自体を削除できるようにする必要はありません(これは悪い考えだと思います)。

AbstractDiagram図; 追加可能なオブジェクト; AddRemoveTypeタイプ;

@SuppressWarnings("unused")
private AddRemoveCommand() {}

public AddRemoveCommand(AbstractDiagram diagram, Addable obj, AddRemoveType type) {
    this.diagram = diagram;
    this.obj = obj;
    this.type = type;
}

public void execute() {
    if(obj != null && diagram != null) {
        switch(type) {
            case ADD:
                this.obj.addToDiagram(diagram);
                break;
            case REMOVE:
                this.obj.removeFromDiagram(diagram);
                break;
        }
    }
}

public void unexecute() {
    if(obj != null && diagram != null) {
        switch(type) {
            case ADD:
                this.obj.removeFromDiagram(diagram);
                break;
            case REMOVE:
                this.obj.addToDiagram(diagram);
                break;
        }
    }
}

最後に、ノードまたはリンクの情報(クラス名など)を変更するために使用されるModificationCommandがあります。これは、将来、MoveCommandとマージされる可能性があります。このクラスは今のところ空です。私はおそらく、変更されたオブジェクトがノードであるかエッジであるかを判断するメカニズムを使用してIDを実行します(IDのinstanceofまたは特別な表記を介して)。

これは良い解決策ですか?

4

2 に答える 2

4

問題をより小さなものに分解する必要があるだけだと思います。

最初の問題: Q: memento/command パターンを使用してアプリ内のステップを表現するにはどうすればよいですか? まず、あなたのアプリがどのように機能するか正確にはわかりませんが、うまくいけば、これでどこに行くのかがわかります。次のプロパティを持つダイアグラムに ClassNode を配置したいとします。

{ width:100, height:50, position:(10,25), content:"Am I certain?", edge-connections:null}

これは、コマンド オブジェクトとしてラップされます。それがDiagramControllerに行くとしましょう。次に、ダイアグラム コントローラーの役割は、そのコマンドを記録し (スタックにプッシュするのが私の賭けです)、コマンドをたとえば DiagramBuilder に渡すことです。実際には、DiagramBuilder が表示の更新を担当します。

DiagramController
{
  public DiagramController(diagramBuilder:DiagramBuilder)
  {
    this._diagramBuilder = diagramBuilder;
    this._commandStack = new Stack();
  }

  public void Add(node:ConditionalNode)
  {
    this._commandStack.push(node);
    this._diagramBuilder.Draw(node);
  }

  public void Undo()
  {
    var node = this._commandStack.pop();
    this._diagramBuilderUndraw(node);
  }
}

そのようなことはそれを行うべきであり、もちろん、整理する必要のある詳細がたくさんあります. ちなみに、ノードのプロパティが多いほど、より詳細な Undraw が必要になります。

ID を使用して、スタック内のコマンドを描画された要素にリンクすることをお勧めします。それは次のようになります。

DiagramController
{
  public DiagramController(diagramBuilder:DiagramBuilder)
  {
    this._diagramBuilder = diagramBuilder;
    this._commandStack = new Stack();
  }

  public void Add(node:ConditionalNode)
  {
    string graphicalRefId = this._diagramBuilder.Draw(node);
    var nodePair = new KeyValuePair<string, ConditionalNode> (graphicalRefId, node);
    this._commandStack.push(nodePair);
  }

  public void Undo()
  {
    var nodePair = this._commandStack.pop();
    this._diagramBuilderUndraw(nodePair.Key);
  }
} 

この時点では、ID を持っているので、必ずしもオブジェクトを持っている必要はありませんが、やり直し機能も実装することにした場合に役立ちます。ノードのIDを生成する良い方法は、ハッシュコードが同一になるような方法でノードを複製しないという保証がないという事実を除いて、それらのハッシュコードメソッドを実装することです.

問題の次の部分は、これらのコマンドを処理する方法を理解しようとしているため、DiagramBuilder 内にあります。そのために私が言えることは、追加できるコンポーネントの種類ごとに逆アクションを作成できることを本当に確認することだけです。デリンクを処理するには、エッジ接続プロパティ (コード内のリンクだと思います) を見て、特定のノードから切断することを各エッジ接続に通知します。切断すると、適切に自分自身を再描画できると思います。

要約すると、スタック内のノードへの参照を保持するのではなく、その時点での特定のノードの状態を表す一種のトークンのみを保持することをお勧めします。これにより、同じオブジェクトを参照することなく、元に戻すスタック内の同じノードを複数の場所で表すことができます。

Qがあれば投稿してください。これは複雑な問題です。

于 2008-10-17T04:47:40.600 に答える
1

私の謙虚な意見では、あなたは実際よりも複雑な方法でそれを考えています. 以前の状態に戻すために、ノード全体のクローンを作成する必要はまったくありません。むしろ、各* *Command クラスには -

  1. 作用しているノードへの参照
  2. memento オブジェクト (ノードが戻るのに十分な状態変数を持つ)
  3. execute() メソッド
  4. undo() メソッド。

コマンド クラスにはノードへの参照があるため、ダイアグラム内のオブジェクトを参照するための ID メカニズムは必要ありません。

あなたの質問の例では、ノードを新しい位置に移動したいと考えています。そのために、NodePositionChangeCommand クラスがあります。

public class NodePositionChangeCommand {
    // This command will act upon this node
    private Node node;

    // Old state is stored here
    private NodePositionMemento previousNodePosition;

    NodePositionChangeCommand(Node node) {
        this.node = node;
    }

    public void execute(NodePositionMemento newPosition) {
        // Save current state in memento object previousNodePosition

        // Act upon this.node
    }

    public void undo() {
        // Update this.node object with values from this.previousNodePosition
    }
}

リンクはどうですか?それらも移動可能である必要がありますが、リンク専用のコマンド (およびノー​​ド専用のコマンド) を作成したくありません。

ノードの位置の変更を伴うリンクの移動は、ある種の制約ソルバーによって処理されるという GoF の本 (メモパターンのディスカッション) を読みました。

于 2011-05-02T18:26:43.780 に答える