Re: Using static factories to create two objects with bidirectional linking
- From: kelvSYC <kelvSYC@xxxxxxxxx>
- Date: 29 Apr 2007 16:54:49 -0700
On Apr 29, 9:38 am, "H. S. Lahman" <h.lah...@xxxxxxxxxxx> wrote:
Responding to KelvSYC...
I seem to have trouble building a static factory due to the need to
build a two-way association:
Right now I'm making some kind of manager class, which, for a static
factory, takes two maps: one <A, B> and the other <B, C>. The manager
manages two types of objects: one is a wrapper for A (call it A'), and
the other is a wrapper for B (call it B'). A and A' have a one-to-one
relationship, as does B and B'. The manager has a one-to-many
relationship with A' and B'. (The two input maps are intended to be
discarded after the manager is built - for example, A and B are
"primitive input" like strings)
My pushback is: what is "manager" managing? Generally objects like
XxxManager and YyyController will cause OO reviewers to bring out stake,
kindling, and matches. To quote Mellor and Balcer in Executable UML,
"The first rule of partitioning control is that you do not make
controller objects. The second rule of partitioning control is that YOU
DO NOT MAKE CONTROLLER OBJECTS!". Such objects usually represent higher
level nodes in a functional decomposition tree that hard-wire in their
implementations the way other objects collaborate.
The idea is that the <A, B> map defines a partition over the A entries
within (so A is many-to-one with B). The <B, C> map is a simple
mapping (which we can assume to be one-to-one). Essentially, this is
managing the composition of two maps with a twist.
To think of this, suppose A is a string, representing specific coinage
(eg. "Canadian penny", "Canadian nickel", "US Silver Dollar", etc). B
is also a string, representing the currency the coinage belongs to
(eg. "CAD", "USD", "EUR", etc). In essence, A and B both represent
raw data, to be constructed into proper objects. C represents a class
of delegate objects (previously constructed), converting quantities of
B to a common fixed currency. Continuing along, B' then would
represent some currency object, where it uses C to convert to another
currency. A' then, would represent some amount of in currency B'.
What I want to do, then, would be a way to manage A' (the coinage
objects) and B' (the currency objects) given the two maps - hence the
manager (which we assume is immutable). This manager would then be
used to get all the A' (coins) and B' (currencies) for any number of
purposes (for example, given an <A', int> map, determine how much of
that common fixed currency you have). The reason why the manager
itself is not a singleton is that, for example, you have a manager for
"present-day currency conversion" and another for "1920 currency
conversion".
I suspect this question is related to your other question, so "manager"
may just be a factory object. If so, that's fine but I would use a more
conventional terminology. B-) BTW, if this the same problem, shouldn't
<B, C> be <A, C>?
No, this is a completely different problem from the last one.
One issue is the need for a two-way relationship: A's and B's are
associated with their manager, so in order to create the A' for an A
the manager must first exist. But for the manager to exist it needs
all of the A's. This is a chicken-and-egg problem that I am unsure of
how to solve: If X and Y are two classes that are one-to-one with each
other, and an X and Y object are associated with each other, then
clearly both objects must be created at the same time. However, this
can only be done if one object was partially built, the other object
was built and linked to the partial object, and the partial object was
completed - which would imply that on one end there would be a public
interface to change the one-to-one association (making said
association no-longer one-to-one). How, then do I do this so that
this does not happen?
I've seen situations where the problem space demanded that referential
integrity be managed explicitly at the OOA/D level, but it is so rare
and was so long ago that I can't even remember a context for a plausible
example. In particular, the X/Y example sounds really fishy. To
instantiate an object all you need are values for its attributes
(including referentials). Having those ready is a precondition on being
able to instantiate it. To instantiate two objects all you need are two
sets of values and having those ready is a precondition to instantiating
them.
So my pushback is that I would bet something else is wrong and this
difficulty is only a symptom. Without knowing anything about the
specific problem it is difficult to speculate on how to resolve the
difficulty. So the following are just some generic possibilities.
It is a symptom, which is in the fact that raw data needs to be
processed first.
The chicken-and-egg problem may only exist within the scope of a factory
method, which is fine. In reality, whenever any object with multiple
unconditional relationships is instantiated there is going to be some
period of time between instructions when referential integrity is not
satisfied simply because instructions require finite time and are
executed sequentially. But that is one reason we encapsulate
instantiation at the method level in OOA/D. That makes it much easier to
ensure that no one accesses the object during that time. (This comes for
free in a synchronous, single-threaded application because only one
procedure can execute at a time; in a concurrent application one has
standard techniques for things like thread safety that work fine so long
as the instantiation scope is a single procedure.)
[Note that it is fair when one has lots of objects and relationships to
instantiate to, say, have a private method to instantiate the objects
and another private method to instantiate their relationships once the
objects are instantiated. So long as those private methods are invoked
with the scope of a single public create() responsibility, one can still
easily manage referential integrity at the OOP level around that public
method scope.]
What of the objects themselves? In the context of a static factory,
you'd need a public method on the objects to set the relationships,
but which needs to be somewhat "hidden" so that the relationships
cannot be altered once instantiated (like ugly default-access in
Java). Based on this, I have an idea:
Instead of A' and B' being classes, let them be interfaces,
implemented in AImpl and BImpl respectively. Then the factory creates
AImpls from the As and BImpls from the Bs (here, also using the Cs and
the <B, C> map), then set the relationships between the AImpls and the
BImpls using the <A, B> map. Then, the manager would have methods
returning A' and B' so as to hide the association-setting methods and
all means of instantiation. Is that a good OO way of adressing this?
It's not pretty (from what I have to do), but on the good side the A's
and B's no longer needs to be associated to the manager - the manager
needs only to keep the collection of A's and B's.
Note that one way to deal with the <very rare> situation where one needs
multiple create() responsibilities separated in execution time to
complete the instantiation is for all of those responsibilities to be in
the same factory. Then that factory can keep track of "partially"
created objects internally in its private implementation. Then no other
objects in the solution can access them until they are completed (i.e.,
the last create() method instantiates relationships to them). Then the
developer is effectively managing referential integrity in the OOA/D by
ensuring that the multiple create() responsibilities always get invoked
in the right order.
The above still works in this case, right?
The other issue is in the construction of A' and B': beyond wrapping A
and B, I'd like them to have functionality that takes advantage of the
map - in A' I'd like to get the B's associated with it (determined
from whether the A is associated with the B), while in B' I'd like to
test whether an A is associated with this B' (which is done, of
course, through the manager link) and get the C associated with the
B' (determined from what C the original B is associated with). The
issue lies in how this is done, which I am stuck on.
Another basic question: why do you need to "wrap" A and B? Is this a 3GL
dependency management issue, an OOA/D issue, or a problem space issue?
As I've put it above, if A and B represent some raw data, then the
wrapping is necessary to create proper objects out of them.
.
- Follow-Ups:
- Re: Using static factories to create two objects with bidirectional linking
- From: H. S. Lahman
- Re: Using static factories to create two objects with bidirectional linking
- References:
- Prev by Date: Re: compilation mechanisms (Was: Dependency Management)
- Next by Date: Re: Using static factories to create two objects with bidirectional linking
- Previous by thread: Re: Using static factories to create two objects with bidirectional linking
- Next by thread: Re: Using static factories to create two objects with bidirectional linking
- Index(es):
Relevant Pages
|