سؤال

أنا أعمل على مشروع صغير لتحرير UML، في جافا، والذي بدأته منذ بضعة أشهر.بعد بضعة أسابيع، حصلت على نسخة عمل لمحرر الرسم التخطيطي لفئات UML.

ولكن الآن، أقوم بإعادة تصميمه بالكامل لدعم أنواع أخرى من الرسوم البيانية، مثل التسلسل والحالة والفئة وما إلى ذلك.يتم ذلك من خلال تنفيذ إطار عمل لبناء الرسم البياني (لقد ألهمتني بشكل كبير أعمال كاي هورستمان حول هذا الموضوع باستخدام محرر Violet UML).

كانت عملية إعادة التصميم تسير بسلاسة حتى أخبرني أحد أصدقائي أنني نسيت إضافة وظيفة Do/Undo إلى المشروع، وهو أمر حيوي في رأيي.

تذكرت دورات التصميم الموجهة للكائنات، وفكرت على الفور في نمط Memento وCommand.

ها هي الصفقة.لدي فئة مجردة، AbstractDiagram، تحتوي على قائمتين ArrayLists:أحدهما لتخزين العقد (يُسمى العناصر في مشروعي) والآخر لتخزين الحواف (يُسمى الروابط في مشاريعي).من المحتمل أن يحتفظ الرسم التخطيطي بمجموعة من الأوامر التي يمكن التراجع عنها/إعادتها.مستوى جميل.

كيف يمكنني تنفيذ هذه الأوامر بطريقة فعالة؟لنفترض، على سبيل المثال، أنني أريد نقل عقدة (ستكون العقدة من نوع واجهة تسمى INode، وستكون هناك عقد ملموسة مشتقة منها (ClassNode، InterfaceNode، NoteNode، وما إلى ذلك)).

يتم الاحتفاظ بمعلومات الموقع كسمة في العقدة، لذلك عن طريق تعديل تلك السمة في العقدة نفسها، يتم تغيير الحالة.عندما يتم تحديث العرض، سيتم نقل العقدة.هذا هو الجزء التذكاري من النموذج (على ما أعتقد)، مع اختلاف أن الكائن هو الحالة نفسها.

علاوة على ذلك، إذا احتفظت بنسخة من العقدة الأصلية (قبل نقلها)، فيمكنني العودة إلى نسختها القديمة.تنطبق نفس التقنية على المعلومات الموجودة في العقدة (اسم الفئة أو الواجهة، والنص الخاص بعقدة الملاحظة، واسم السمات، وما إلى ذلك).

الأمر هو، كيف يمكنني استبدال العقدة في الرسم التخطيطي باستنساخها عند عملية التراجع/الإعادة؟إذا قمت باستنساخ الكائن الأصلي المشار إليه بواسطة الرسم التخطيطي (الموجود في قائمة العقد)، فلن يكون النسخ مرجعًا في الرسم التخطيطي، والشيء الوحيد الذي يشير إليه هو الأمر نفسه!هل يجب علي تضمين آليات في الرسم التخطيطي للعثور على عقدة وفقًا لمعرف (على سبيل المثال) حتى أتمكن من استبدال العقدة في الرسم التخطيطي باستنساخها (والعكس صحيح)؟هل الأمر متروك لأنماط التذكار والأوامر للقيام بذلك؟ماذا عن الروابط؟يجب أن تكون متحركة أيضًا ولكني لا أرغب في إنشاء أمر للروابط فقط (وأمر للعقد فقط)، ويجب أن أكون قادرًا على تعديل القائمة الصحيحة (العقد أو الروابط) وفقًا لنوع كائن الأمر يشير إلى.

كيف يمكنك المضي قدما؟باختصار، أواجه مشكلة في تمثيل حالة الكائن في نمط أمر/تذكار بحيث يمكن استعادته بكفاءة واستعادة الكائن الأصلي في قائمة المخططات، اعتمادًا على نوع الكائن (عقدة أو رابط).

شكرًا جزيلاً!

غيوم.

ملاحظة.:إذا لم أكن واضحا، أخبرني وسأوضح رسالتي (كما هو الحال دائما!).

يحرر

هذا هو الحل الفعلي الذي بدأت تنفيذه قبل نشر هذا السؤال.

أولاً، لدي فئة 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 (ل...نقل أو إزالة كائن/عقدة).إذا استخدمت معرف طريقة المثيل، فلن أحتاج إلى تمرير الرسم التخطيطي إلى العقدة أو الرابط الفعلي حتى يتمكن من إزالة نفسه من الرسم التخطيطي (وهي فكرة سيئة على ما أعتقد).

ملخص الرسم التخطيطي؛كائن قابل للإضافة؛نوع 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;
        }
    }
}

أخيرًا، لدي أمر تعديل يُستخدم لتعديل معلومات العقدة أو الرابط (اسم الفئة، وما إلى ذلك).قد يتم دمج هذا في المستقبل مع MoveCommand.هذا الفصل فارغ في الوقت الراهن.من المحتمل أن أفعل شيئًا المعرف باستخدام آلية لتحديد ما إذا كان الكائن المعدل عبارة عن عقدة أم حافة (عبر مثيل أو إشارة خاصة في المعرف).

هل هذا حل جيد؟

هل كانت مفيدة؟

المحلول

أعتقد أنك تحتاج فقط إلى تقسيم مشكلتك إلى مشكلات أصغر.

المشكلة الأولى:س:كيف تمثل الخطوات في تطبيقك باستخدام نمط التذكار/الأمر؟أولاً، ليس لدي أي فكرة عن كيفية عمل تطبيقك بالضبط، ولكن آمل أن ترى إلى أين أتجه بهذا.لنفترض أنني أريد وضع 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 أكثر تفصيلاً.

قد يكون استخدام معرف لربط الأمر الموجود في مكدسك بالعنصر المرسوم فكرة جيدة.قد يبدو ذلك كالتالي:

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);
  }
} 

في هذه المرحلة لا تفعل ذلك تمامًا يكون لديك الكائن نظرًا لأن لديك المعرف ولكنه سيكون مفيدًا إذا قررت أيضًا تنفيذ وظيفة الإعادة.تتمثل إحدى الطرق الجيدة لإنشاء المعرف الخاص بالعقد الخاصة بك في تنفيذ طريقة رمز التجزئة لها باستثناء حقيقة أنه لن نضمن لك عدم تكرار العقد الخاصة بك بطريقة قد تتسبب في تطابق رمز التجزئة.

الجزء التالي من المشكلة موجود داخل DiagramBuilder الخاص بك لأنك تحاول معرفة كيفية التعامل مع هذه الأوامر.لذلك كل ما يمكنني قوله هو التأكد من أنه يمكنك إنشاء إجراء عكسي لكل نوع من المكونات التي يمكنك إضافتها.للتعامل مع فك الارتباط، يمكنك إلقاء نظرة على خاصية اتصال الحافة (الروابط الموجودة في الكود الخاص بك على ما أعتقد) وإخطار كل اتصالات الحافة بأنه يجب قطع الاتصال بالعقدة المحددة.أفترض أنه عند قطع الاتصال يمكنهم إعادة رسم أنفسهم بشكل مناسب.

للتلخيص نوعًا ما، أوصي بعدم الاحتفاظ بمرجع إلى العقد الخاصة بك في المكدس ولكن بدلاً من ذلك مجرد نوع من الرمز المميز الذي يمثل حالة عقدة معينة في تلك المرحلة.سيسمح لك هذا بتمثيل نفس العقدة في مكدس التراجع الخاص بك في أماكن متعددة دون الإشارة إلى نفس الكائن.

انشر إذا كان لديك أسئلة.هذه مسألة معقدة.

نصائح أخرى

في رأيي المتواضع، أنت تفكر في الأمر بطريقة أكثر تعقيدًا مما هو عليه في الواقع.من أجل العودة إلى الحالة السابقة، ليس من الضروري استنساخ العقدة بأكملها على الإطلاق.بل كل ** فئة القيادة سوف يكون -

  1. الإشارة إلى العقدة التي تعمل عليها،
  2. كائن تذكاري (يحتوي على متغيرات حالة كافية للعقدة للعودة إليها)
  3. طريقة التنفيذ ().
  4. طريقة التراجع ().

نظرًا لأن فئات الأوامر لها إشارة إلى العقدة، فإننا لا نحتاج إلى آلية معرف للإشارة إلى الكائنات الموجودة في الرسم التخطيطي.

في المثال من سؤالك، نريد نقل العقدة إلى موضع جديد.لهذا، لدينا فئة 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 (في مناقشة نمط التذكار) أن نقل الارتباط مع التغيير في موضع العقد يتم التعامل معه بواسطة نوع من أدوات حل القيود.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top