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
. |