The forums have permanently moved to forum.kirupa.com. This forum will be kept around in read-only mode for archival purposes. To learn how to continue using your existing account on the new forums, check out this thread.


Results 1 to 8 of 8

Thread: Best way to code a "paired" replationship?

  1. #1

    Best way to code a "paired" replationship?

    Personally, I avoid getting myself in situations where I need two different instances (often of separate classes) to be closely linked or paired to each other.

    For example (and a prime example at that) the DisplayObject's child/parent relationship. Personally, I would consider this to be a terrible thing. (Not sure why, but gut feeling says it's bad)

    One simple rule: Each child has a property named "parent", and each parent is able to access it's child (just to simplify things) using a "child" property.

    This could create a lot of terrible errors if some rouge code decides to change the "parent" property of the child, and the parent has no idea. Perhaps the parent's child property is still pointing to that old child, which is now the child of a completely different parent, ruining that relationship.

    Personally, I wouldn't even let the child know who his parent is (yes, I'm mean), and let the child run his rule independently. Yet sometimes you really need this "paired" relationship between two or more objects.

    There are a few ways I have come up with to get around this, though (code examples abbreviated):

    1. Only the parent holds an actual "child" variable. The child's "parent" getter/setter really loops through all of the parents available and checks which one it belongs to. (Roles can also be reversed where the child is the only one with the actual saved value)
    Code:
    class Parent
    {
       static var allParents:Array;
    
       private var _child:Child;
       public function get child():Child
       {
          return _child;
       }
       public function set child(new_child:Child):void
       {
          _child = new_child;
       }
    }
    Code:
    class Child
    {
       public function get parent():Parent
       {
          for (var i:int = 0; i < Parent.allParents.length; i++)
          {
             if (Parent.allParents[i].child == this)
                { return Parent.allParents[i]; }
          }
       }
       public function set parent(new_parent:Parent):void
       {
          for (var i:int = 0; i < Parent.allParents.length; i++)
          {
             if (Parent.allParents[i].child == this)
                { Parent.allParents[i].child = null; }
    
             if (new_parent != null)
                { new_parent.child = this; }
          }
       }
    }
    2. Both the child and the parent contain references to each other, and are VERY careful to always notify and change the other object in case the value changes. NOTE: This is REALLY, REALLY dangerous if any of the getter/setters are overridden by a function that is too sloppy to make sure the other value will always be notified of changes.
    Code:
    class Parent
    {
       private var _child:Child;
       public function get child():Child
       {
          return _child;
       }
       public function set child(new_child:Child):void
       {
          if (_child != null)
          {
             _child.parent = null;
          }
    
          _child = new_child;
    
          //Make sure that this parent value wasn't set by the child now making
          //sure that it's parent value is correct
          if ((_child != null) && (_child.parent != this))
          {
             _child.parent = this;
          }
       }
    }
    Code:
    class Child
    {
       private var _parent:Parent;
       public function get parent():Parent
       {
          return _parent;
       }
       public function set parent(new_parent:Parent):void
       {
          if (_parent != null)
          {
             _parent.child = null;
          }
    
          _parent = new_parent;
    
          //Make sure that this child value wasn't set by the parent now making
          //sure that it's child value is correct
          if ((_parent != null) && (_parent.child != this))
          {
             _parent.child = this;
          }
       }
    }
    3. Keep an array of all pairs of children/parents
    Code:
    class Parent
    {
       public function get child():Child
       {
          return PC_Collection.getChild(this);
       }
       public function set child(new_child:Child):void
       {
          PC_Collection.setRelationship(this, new_child);
       }
    }
    Code:
    class Child
    {
       public function get parent():Parent
       {
          return PC_Collection.getParent(this);
       }
       public function set parent(new_parent:Parent):void
       {
          PC_Collection.setRelationship(new_parent, this);
       }
    }
    Code:
    //Stands for Parent/Child collection.
    //Was originally going to be named Child/Parent collection, but I didn't think it was the best idea...
    class PC_Collection
    {
       private static var collection:Array = new Array();
    
       public static function getParent(child:Child):Parent
       {
          for (var i:int = 0; i < collection.length; i++)
          {
             if (collection[i].child == child)
                { return collection[i].parent; }
          }
          //If no match, it has no parent, so return null
          return null;
       }
    
       public static function getChild(parent:Parent):Child
       {
          for (var i:int = 0; i < collection.length; i++)
          {
             if (collection[i].child == child)
                { return collection[i].parent; }
          }
          //If no match, it has no child, so return null
          return null;
       }
    
       public static function setRelationship(parent:Parent, child:Child):void
       {
          //Clear all relationships of the parent
          for (var p:int = 0; p < collection.length; p++)
          {
             if (collection[p].parent == parent)
             {
                collection[p].parent == null;
                collection[p].child == null;
                //TODO: Remove "[p]" from the array
             }
          }
          
          //Clear all relationships of the child
          for (var c:int = 0; c < collection.length; c++)
          {
             if (collection[c].child == child)
             {
                collection[c].parent == null;
                collection[c].child == null;
                //TODO: Remove "[c]" from the array
             }
          }
          
          //Add a new relationship
          collection.push({parent:parent, child:child});
    
       }
    }

    My question is, what is the best way? What am I supposed to do?



    Perhaps I'm just paranoid, and dream all night about rouge code destroying relationships and secretly changing variables. Am I the only one that lives my life with this constant fear? The funniest part is that I'm actually serious...
    Last edited by IQAndreas; September 13th, 2009 at 06:36 PM.
    Blog article of the month: Why My One Line 'if' Statements Are Unusual
    Twitter: @IQAndreas
    GitHub: IQAndreas

  2. #2
    I've never encountered rogue code in my dreams so far. Should it happen, I would fight back with final functions

    I know what you mean by having a bad gut feeling about parent-child relationships but I've got a similar feeling about those static functions and variables.

  3. #3
    Quote Originally Posted by _kp View Post
    I've never encountered rogue code in my dreams so far. Should it happen, I would fight back with final functions.
    I have thought about that, but is it possible to declare functions as final in AS3, or just classes? (Instead of asking, maybe I should just try it.

    I'm quite certain that for display objects, even though the parent/child specific properties should be final, they are not, as the stage is able to (and does) override them. Luckily, the stage still accesses the "super()" function (or at least has some workaround), so there are no such "unlinked" errors when adding or removing children from the stage.

    This whole thing sounds like a giant soap opera.
    Blog article of the month: Why My One Line 'if' Statements Are Unusual
    Twitter: @IQAndreas
    GitHub: IQAndreas

  4. #4
    What's the worst that could happen? You encounter a bug, find out it was rogue code changing the variables, and fix it.

    Of course, that might just bring the code's vengeful wrath upon you.

    That second scenario you posted doesn't work, by the way. Once you establish a parent/child relationship and try to change it, essentially whenever
    Code:
          if (_child != null)
          {
             _child.parent = null;
          }
    executes because _child isn't null, you create an infinite loop because then the child's parent setter has to set the parent's child to null and then we're back to the parent's child setter executing that same line of code again.

  5. #5
    Quote Originally Posted by Scythe View Post
    What's the worst that could happen? You encounter a bug, find out it was rogue code changing the variables, and fix it.
    It could be rouge code written by people who use my code, or perhaps someone trying to hack my code by passing in a class that extends and overrides my functions, but to my code looks like it's the same class, but through that class they can extract whatever information they need or do massive damage. Or it might cause bugs for a dumb developer. I just like keeping things really locked tight just in case.

    Quote Originally Posted by Scythe View Post
    executes because _child isn't null, you create an infinite loop because then the child's parent setter has to set the parent's child to null and then we're back to the parent's child setter executing that same line of code again.
    Oops, I thought I had fixed that bug with the second part of the code, but I was thinking wrong. It can be fixed by holding a temporary reference to "old_child", and then setting "_child" to the new child immediately at the start of the setter.


    Anyway, the point is still there, and the question unanswered. What is the best "OOP" and friendly and industrial standard method for coding a linked/paired objects that need access to eachother?
    Blog article of the month: Why My One Line 'if' Statements Are Unusual
    Twitter: @IQAndreas
    GitHub: IQAndreas

  6. #6
    Well the first option could potentially take valuable time to execute and the third option could potentially take up valuable space. The second option would therefore be best if you could get it to work. And functions can indeed be declared as final, which should help you sleep better

    I'm not sure what damage someone could cause by screwing up the parent/child relationships. Hackers want to make the code work for them, but that would make the code just not work at all.

    ...right?

  7. #7
    I prefer doing this by having only the parent ever store references to its children. The parent can modify child states as it sees necessary. However, instead of directly allowing the child access to the parent state, the child must request actions of the parent by dispatching events. The parent always knows who is asking what (and in what order), and the child can be notified if the request is permitted or denied.

    This also enforces code portability, as one-way linkage implies that you should only need to modify one class to change the system.

  8. #8
    Well, how about sth like this?
    Code:
    package rel {
     class Parent {
      private var _child:Child;
      public function get child():Child {
       return _child;
      }
      public function addChild(new_child:Child):Child {
       this.child.parent = null;
       this.child = new_child;
       new_child.parent = this;
       return this.child;
      }
      internal function set child(new_child:Child):void {
       if (_child) cleanUp();
       _child = new_child;
      }
      private function cleanUp() {
       //remove listeners, etc.
      }
     }
    }
    Code:
    package rel {
     class Child {
      private var _parent:Parent;
      public function get parent():Parent {
       return _parent;
      }
      internal function set parent(new_parent:Parent):void {
       if (_parent) cleanUp();
       _parent = new_parent;
      }
      private function cleanUp() {
       //remove listeners, etc.
      }
     }
    }
    Its just a draft.

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  

Home About kirupa.com Meet the Moderators Advertise

 Link to Us

 Credits

Copyright 1999 - 2012