2008-10-17 2 views
4

저는 몇 달 전에 시작한 Java의 작은 UML 편집기 프로젝트에서 작업하고 있습니다. 몇 주 후에 UML 클래스 다이어그램 편집기에 대한 작업 복사본이 있습니다.메멘토 패턴 (및 명령)을 사용하여 복잡한 객체의 상태 저장

하지만 지금은 시퀀스, 상태, 클래스 등 다이어그램의 다른 유형을 지원하기 위해 완전히 다시 디자인하고 있습니다.이 작업은 그래프 구성 프레임 워크를 구현하여 수행됩니다 (저는 Cay Horstmann에서 영감을 얻었습니다). Violet UML 편집기가있는 주제).

내 친구 중 한 명이 프로젝트에 Do/Undo 기능을 추가하는 것을 잊어 버렸다고 말할 때까지 재 설계가 원활하게 진행되고있었습니다. 제 생각에는 이것이 중요합니다.

개체 지향 디자인 과정을 기억하면서, 나는 즉시 메멘토 및 명령 패턴을 생각했습니다.

여기에 있습니다. 두 개의 ArrayLists (하나는 프로젝트에 Elements라고 함)를 저장하고 다른 하나는 Edges (프로젝트에 링크라고 함)를 저장하는 추상 클래스 AbstractDiagram을 가지고 있습니다. 이 다이어그램은 실행 취소/재실 행할 수있는 명령 스택을 유지합니다. 꽤 표준.

효율적인 방법으로 어떻게 이러한 명령을 실행할 수 있습니까? 예를 들어 노드를 이동하려고합니다 (노드는 INode라는 인터페이스 유형이 될 것이고 거기에서 파생 된 구체적인 노드가있을 것입니다 (ClassNode, InterfaceNode, NoteNode 등)).

위치 정보는 노드의 속성으로 유지되므로 노드 자체에서 해당 속성을 수정하면 상태가 변경됩니다. 디스플레이가 새로 고쳐지면 노드가 이동합니다. 이것은 패턴의 메멘토 (Memento) 부분입니다 (나는 생각합니다). 객체가 상태 그 자체라는 차이점이 있습니다.

또한 원래 노드의 복제본을 이동하기 전에 보관하면 이전 버전으로 되돌릴 수 있습니다. 동일한 기술이 노드에 포함 된 정보 (클래스 또는 인터페이스 이름, 메모 노드의 텍스트, 속성 이름 등)에 적용됩니다.

어떻게하면 다이어그램에서 노드를 실행 취소/다시 실행시 클론으로 대체 할 수 있습니까? 다이어그램에서 참조하는 원래 개체 (노드 목록에 있음)를 복제하면 복제본이 다이어그램에서 참조가 아니며 유일한 점은 명령 자체입니다. 나는 다이어그램에서 ID (예를 들어)에 따라 노드를 찾는 메커니즘을 다이어그램에 포함 시키므로 다이어그램에서 노드를 복제본 (또는 그 반대)으로 바꿀 수 있습니까? Memento와 Command 패턴에 따라 달라질 수 있습니까? 링크는 어떻습니까? 그들은 움직일 수 있어야하지만 링크 (그리고 단지 노드를위한 것)를위한 명령을 만들고 싶지 않고 명령 대상의 유형에 따라 올바른 목록 (노드 또는 링크)을 수정할 수 있어야합니다. 를 참조하고 있습니다.

어떻게 진행 하시겠습니까? 요약하면, 명령/유품 패턴에서 오브젝트의 상태를 표현하는 것이 어려워서 다이어그램 목록에 복원 된 원본 오브젝트와 오브젝트 유형 (노드 또는 링크)에 따라 효율적으로 복구 할 수 있습니다.

고맙습니다.

기ume.

피씨 : 내가 분명하지 않으면 말해주세요. 나는 항상 내 메시지를 분명히 할 것입니다.

편집

는 여기에 내가이 질문을 게시하기 전에 구현하기 시작했다, 내 실제 솔루션입니다.

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의 구체적인 유도를 사용하여 구현됩니다 :

첫째, 나는 다음과 같이 정의 된 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와 병합 될 수 있습니다. 이 수업은 현재 비어 있습니다. 아마도 수정 된 객체가 node 또는 edge (instanceof 또는 ID의 특수 표시를 통해)인지 확인하는 메커니즘을 사용하여 ID 객체를 수행 할 것입니다.

이 좋은 솔루션입니다인가?

답변

4

나는 그냥 작은 것들에 문제를 분해 할 필요가 있다고 생각합니다.

첫 번째 문제 : Q : 어떻게 기념물/명령 패턴을 사용하여 응용 프로그램의 단계를 나타내는? 먼저, 앱이 어떻게 작동하는지 정확히 알지 못하지만 잘하면 내가 어디로 갈 것인지 알게 될 것입니다. 다음과 같은 속성을 가진 다이어그램에 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를 사용

. 당신이 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를 생성하는 좋은 방법은 해시 코드를 동일하게 만드는 방식으로 노드를 복제하지 않는다는 것을 제외하고는 해시 코드 메소드를 구현하는 것입니다. 당신은 도대체이 명령을 처리하는 방법을 알아 내려고 노력하고 있기 때문에

문제의 다음 부분은 DiagramBuilder에 있습니다. 이를 위해 내가 말할 수있는 것은 추가 할 수있는 각 유형의 구성 요소에 대해 역 동작을 만들 수 있다는 것입니다.delinking을 처리하기 위해 edge-connection 속성 (내가 생각하는 코드의 링크)을보고 특정 노드와의 연결을 끊을 각 에지 연결을 알릴 수 있습니다. 연결이 끊어지면 적절하게 다시 그릴 수 있다고 가정합니다.

요약하면, 스택에 노드에 대한 참조를 유지하지 말고 그 시점에서 주어진 노드의 상태를 나타내는 일종의 토큰을 유지하는 것이 좋습니다. 이렇게하면 동일한 객체를 참조하지 않고도 여러 위치에서 실행 취소 스택의 동일한 노드를 나타낼 수 있습니다.

질문이 있으시면 게시하십시오. 이것은 복잡한 문제입니다.

1

나의 겸허 한 의견에서, 당신은 실제로보다 더 복잡한 방식으로 생각하고 있습니다. 이전 상태로 되돌리려면 전체 노드의 복제가 전혀 필요하지 않습니다. 오히려 각 * * 명령 클래스는 것 -

그것은에 작용하는 노드
  1. 참조
  2. 기념 객체 (노드에 대한 충분한 상태 변수를 갖는로 되돌릴)
  3. 실행() 메서드
  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 책 제약 해결사의 일종에 의해 처리되는 노드의 위치 변화에 링크의 움직임에 읽어 보시기 바랍니다.