RM Theory
Home Up

 

Relationship Manager Pattern - Theory

-Andy Bulka, July 2003
abulka@netspace.net.au

This page describes in more theoretical terms the scope of the relationship manager approach, and includes examples of how to code using relationship manager, covering all possible modelling scenarios. See also the original Relationship Manager design pattern.

I have implemented RM Relationship Manager for .NET using the Boo language (porting it from Python). See RM for .NET

Relationship Manager API

Basic API

Returns
Function Name
Short-hand 
void
addRelationship(f, t, id)
R(f,t)
void
removeRelationship(f, t, id)
NR(f,t)
Vector
findObjectsPointedToByMe(f, id)
PS(f)
Vector
findObjectsPointingToMe(t, id)
BS(t)
 

Extra API

Returns
Function Name
Short-hand 
void 
EnforceRelationship(id, cardinality, bidirectionality)
ER(id, c, bi)
Object
findObjectPointedToByMe(fromMe, id, cast)
P(f)
Object
findObjectPointingToMe(toMe, id cast)
B(t)
void
removeAllRelationshipsInvolving(o, id)
NRS(o)

The extra API allows you to enforce relationships e.g.

   ER("xtoy", "onetoone", "directional")

registers the relationship  as being one to many and directional, so that e.g. when you add a second relationship between the same two objects the first relationship is automatically removed - ensuring the relationship is always one to one. Alternatively, you could raise and exception.

The extra API also adds a pair of find methods that only find one object, and cast it to the appropriate type.  This is a commonly used convenience method.

Backpointer Note:  It still has me a bit vexed, but findObjectPointingToMe(toMe, id cast) can also be considered a vital part of the API since my implementation of a Composite Pattern, with back pointer - see example of my annotations to some  working composite code which required it.  I guess I could have done it with bidirectionality but it seemed neater as directional.  What's special is that the class refers to itself.  The code is a good example of how use of RM saves you from having to explicitly maintain backpointers.  proxydecorator01.zip (you also need the support files found in the basic RM code.)

Relationship Id

This is either an integer or a string.  I have chosen to use a string in the Java implementation, since you can describe relationships easily in this way rather than having to map from an integer back to some meaningful description.

    RM.addRelationship(fromObject, toObject, relationshipId)

will raise an exception if relationshipId is an empty string.  

All other functions (except for addRelationship) can pass either an empty string or "*" as the relationshipId, which means you are searching for any relationship at all.  You would usually only want to do this if there is only one relationship between class X and class Y, then your P and NR calls can specify "*" as the relationshipId in order to match any relationship between these two objects.  Alternatively, you can use relationship manager's overloaded versions of all its routines (except for addRelationship) which don't take a relationshipId where relationshipId defaults to "*".

All possible relationship scenarios

When looking at all the possibiliteis of relationships between two classes, you get one to one, one to many, many to one and many to many.  You have the variations generated by whether the relationships are directional or bi-directional.  Finally, you have variations of whether you put methods on one class or the other - for example, you could omit methods on e.g. the rhs. class, or you could go to the other extreme and provide a full range of methods on the rhs. class.  

Bi-directional yet still directional - important insight

Note that when you put an API on both classes then this automatically implies that you are implementing bi-directional.  Important insight:  However even such a  bi-directional relationship, there still remains a strong sense of directionality since the one is always the X class and the many is always the Y class. Thus adding the relationship e.g. RM.R on both API's must be always done from the X to the Y.  The same relationship id must be used for all the relationships on both sides e.g. "xtoy" (notice the sense of directionality is built into the name of the relationship!), even though it is a bidirectional relationship in the sense that there is an API on both classes allowing each class to find the other class.

Summary

Note that some combinatorial possibilities do not make sense and are left out of the table below. 

S means singular API - this makes sense for one to one relationships, or the many side (ironically) of one to many relationships.  It consists of methods like get, set, clear.

P means plural API- this makes sense where you are dealing with collections, a many concept.  It consists of methods like add, remove, getall.

  directional   bi-directional  
  one to one  
  1  -->  1  1  <-->  1 
1 S       -  
2 -       S  
3   S        S
3A S composite  
  one to many  
  1  -->  * 1  <-->  *
4 P      -  
5   P       S
  many to one  
  *  -->  1 *  <-->  1
6 -       P  
7   S         P
  many to many  
  *  -->  * *  <-->  *
8 P      -  
9 -      P  
10   P       P

How to implement them using the Relationship Manager API.

The right hand side of the below table shows python code using calls to RM (relationship manager) using the shorthand notation for the function names.

 

Relationship Scenario

Python Relationship Manager Implementation

  one to one  
  1  -->  1                       directional    
1      Singular API                 No API
 ______________        ______________
|       X      |      |       Y      |
|______________|      |______________|
|              |      |              |
|void  setY(y) |1    1|              |
|Y     getY()  |----->|              |
|void  clearY()|      |              |
|______________|      |______________|
class X:
    def __init__(self):        RM.ER("xtoy", "onetoone", "directional")
    def setY(self, y):         RM.R(self, y, "xtoy")
    def getY(self):     return RM.P(self, "xtoy")
    def clearY(self):          RM.NR(self, self.getY(), "xtoy")
    
class Y:
    pass
2        No API                      Singular API 
 ______________        ______________
|       X      |      |       Y      |
|______________|      |______________|
|              |      |              |
|              |1    1| setX(x)      |
|              |----->| getX()       |
|              |      | clearX()     |
|______________|      |______________|
class X:
    pass
    
class Y:
    def __init__(self):        RM.ER("xtoy", "onetoone", "directional")
    def setX(self, x):         RM.R(x, self, "xtoy")
    def getX(self):     return RM.B(self, "xtoy")
    def clearX(self):          RM.NR(self.getX(), self, "xtoy")
3A

 

                          Singular API 
     Composite pattern, with back pointer.
          ___________________
         |       X           |<-------,
         |___________________| 1      |
       1 |                   |        |
  |----->| void  _setX(x)    |        |
  |      | X     _getX(x)    |        |
  |______| X     _getBack()  |........|
 back    |___________________|    x
 

see example of notations to working composite code and comments above.

class X:
    def __init__(self):          RM.ER("xtox", "onetoone", "directional")
    def _setX(self, x):          RM.R(self, thing, "xtox")
    def _getX(self):      return RM.P(self, "xtox")
    def _getBack(self):   return RM.B(self, "xtox")
    x = property(_getThing, _setThing)
    back = property(_getBack)
  1  <-->  1                     bi-directional    
3      Singular API                 Singular API
 ______________        ______________
|       X      |      |       Y      |
|______________|      |______________|
|              |      |              |
|void  setY(y) |1    1| setX(x)      |
|Y     getY()  |<---->| getX()       |
|void  clearY()|      | clearX()     |
|______________|      |______________|
class X:
    def __init__(self):        RM.ER("xy", "onetoone", "bidirectional")
    def setY(self, y):         RM.R(self, y, "xy")
    def getY(self):     return RM.P(self, "xy")
    def clearY(self):          RM.NR(self, self.getY(), "xy")
    
class Y:
    def __init__(self):        RM.ER("xy", "onetoone", "bidirectional")
    def setX(self, x):         RM.R(self, x, "xy")
    def getX(self):     return RM.P(self, "xy")
    def clearX(self):          RM.NR(self, self.getX(), "xy")
  one to many  
  1  -->  *                       directional    
4      Plural  API                             No API
  _____________        ______________
 |      X      |      |       Y      |
 |_____________|      |______________|
 |             |      |              |
 |addY(y)      |1    *|              |
 |getAllY()    |----->|              |
 |removeY(y)   |      |              |
 |_____________|      |______________|
class X:
    def __init__(self):        RM.ER("xtoy", "onetomany", "directional")
    def addY(self, y):         RM.R(self, y, "xtoy")
    def getAllY(self):  return RM.PS(self, "xtoy")
    def removeY(self, y):      RM.NR(self, y, "xtoy")
    
class Y:
    pass
  1  <-->  *                      bi-directional   
5      Plural  API                         Singular API
 _____________        ______________
|      X      |      |       Y      |
|_____________|      |______________|
|             |      |              |
|addY(y)      |1    *| setX(x)      |
|getAllY()    |<---->| getX()       |
|removeY(y)   |      | clearX()     |
|_____________|      |______________|
class X:
    def __init__(self):        RM.ER("xtoy", "onetomany", "bidirectional")
    def addY(self, y):         RM.R(self, y, "xtoy")
    def getAllY(self):  return RM.PS(self, "xtoy")
    def removeY(self, y):      RM.NR(self, y, "xtoy")
    
class Y:
    def setX(self, x):         RM.R(x, self, "xtoy")
    def getX(self):     return RM.P(self, "xtoy")
    def clearX(self):          RM.NR(self, self.getX(), "xtoy")
  many to one  
  *  -->  1                                directional    
6  No API                              Plural  API
 ______________        ______________
|     X        |      |       Y      |
|______________|      |______________|
|              |      |              |
|              |*    1|addX(x)       |
|              |----->|getAllX()     |
|              |      |removeX(x)    |
|______________|      |______________|
DRAFT
---- X methods ----
None
---- Y methods ----
void  addX(x)    RM.R(x, this, "xtoy")
list  getAllX()  RM.BS(this, "xtoy")
void  removeX(x) RM.NR(x, this, "xtoy")

 

  *  <-->  1                           bi-directional   
7  Singular API                       Plural  API
 ______________        ______________
|     X        |      |       Y      |
|______________|      |______________|
|              |      |              |
|void  setY(y) |*    1|addX(x)       |
|Y     getY()  |----->|getAllX()     |
|void  clearY()|      |removeX(x)    |
|______________|      |______________|
DRAFT
---- X methods ----
void  setY(y)    RM.R(this, y, "xtoy")
Y     getY()     RM.P(this, "xtoy")
void  clearY()   RM.NR(this, getY(), "xtoy")
---- Y methods ----
void  addX(x)    RM.R(x, this, "xtoy")
list  getAllX()  RM.BS(this, "xtoy")
void  removeX(x) RM.NR(x, this, "xtoy")

 

  many to many  
  *  -->  *                                directional    
8      Plural  API                             No API
  _____________        ______________
 |      X      |      |       Y      |
 |_____________|      |______________|
 |             |      |              |
 |addY(y)      |*    *|              |
 |getAllY()    |----->|              |
 |removeY(y)   |      |              |
 |_____________|      |______________|
To be filled in...
9        No API                         Plural  API
 ______________        ______________
|     X        |      |       Y      |
|______________|      |______________|
|              |      |              |
|              |*    *|addX(x)       |
|              |----->|getAllX()     |
|              |      |removeX(x)    |
|______________|      |______________|
To be filled in...
  *  <-->  *                           bi-directional   
10  Plural  API                         Plural  API
 ______________        ______________
|     X        |      |       Y      |
|______________|      |______________|
|              |      |              |
| addY(y)      |*    *| addX(x)      |
| getAllY()    |----->| getAllX()    |
| removeY(y)   |      | removeX(x)   |
|______________|      |______________|
To be filled in...

Working code - Proof

Working code which proves the above theory is provided here in this python program.

A java implementation is in progress.  Contact me for a preview copy.

I have implemented RM Relationship Manager for .NET using the Boo language (porting it from Python). See RM for .NET .