Re: Using static factories to create two objects with bidirectional linking



Responding to KelvSYC...

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.

I'm still pushing back that if A, B, and C are objects, then those "maps" implement that relationship and would be owned by A rather than some other object. [But A and B do not seem to be objects below, so they wouldn't have relationships at all.]

Alas, I am sorry but I really don't see the relevance of the following example to the problem you have been describing. The mapping you describe (e.g., A' and B') seem superfluous to me. I inserted comments on some of the things that bother me. But I think the real problem is that I would have abstracted the problem space in a much different manner, as I indicate below.


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

Then A and B aren't objects. What object is being constructed from them? That is the one whose relationships one needs to worry about.

Also, it seems to me A and B are redundant; the coinage already defines the currency. [Even if they are arbitrary codes, one can still have a table lookup to figure out the currency and that lookup table can be initialized from external configuration data. IOW, the mapping to currency that B provides can be provided without dedicated problem space objects.]

of delegate objects (previously constructed), converting quantities of
B to a common fixed currency. Continuing along, B' then would

So C needs to convert one currency to another, which is a collaboration rather than an instantiation. I would assume C collaborates with whatever object one creates from A and B.

represent some currency object, where it uses C to convert to another
currency. A' then, would represent some amount of in currency B'.

This where things really break down for me. Why do you need B'? Why can't C do its thing and convert the currency in a collaboration?

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

What this example suggests to me is that one has something like the following in the problem space:

[ConversionSpec]
+ multiplier
+ timePeriod
| 0..1
|
| R1
|
| specifies conversion of
| 0..*
[Coinage]
+ denomination
+ quantity
+ currency
+ convert()
| *
|
| R2
|
| converted to
| 1
[CommonCurrency]
+ amount

The [Coinage] objects capture the raw data and an object is presumably instantiated from some external message data packet (e.g., from a UI). If the data arrives in the form of {coinID, number}, then the factory that creates a Coinage would do a table lookup on coinID to get denomination and currency values.

A Coinage is assigned a ConversionSpec (R1) based upon the currency and desired time period. The R1 relationship is conditional because it cannot be instantiated until one knows the desired time period, which I assume is provided later (but necessarily before convert() is invoked). (The R1 relationship is conditional on the *-side because only one such specification is associated with a Coinage, presumably at most one Coinage is alive at a time, and the time period may not be known when Coinage is created.) IOW, R1 is fully dynamic in its instantiation.

[ConversionSpec] contains a conversion factor to the base currency for a particular time period. The [ConversionSpec] objects are dumb data holders that can be instantiated at startup from external configuration data for conversion rates. The convert() behavior employs the ConversionSpec multiplier and denomination to compute the amount of the designated common currency. (The convert() method could create a CommonCurrency since it is trivial, in which case R2 is also conditional on the 1-side.)

The only trickiness here for instantiation is that the R1 relationship needs to be instantiated dynamically based on the the value of Coinage.currency and the desired time period. If the desired time period is not known when Coinage is instantiated, then R1 is necessarily conditional and the developer must ensure that it is instantiated before convert() is invoked.

[Note that convert() could create the CommonCurrency object since it seems to be trivial. But then R2 needs to be conditional on the 1-side and it probably has 1 multiplicity on the Coinage side.]

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:

Unfortunately I can't respond to that because I think static factories, "managers", Singleton, and external maps are overkill, especially after the example above. My pushback is that I think the problem space may need to be abstracted in a simpler fashion.

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.

That was my point above about A and B not being objects. They are simply inputs to some object, presumably some sort of factory, that creates some other object. If so, there aren't any relationships to them so one doesn't need to "wrap" them with interfaces; they are just data.

What I suspect is happening here is that A and B /are/ dumb data holder objects in the form of descriptors (e.g., {number, coinID}) that you are getting from some place else. If so, they either correspond to [Coinage] in my model above or a Coinage object can be created from them. But I don't think that instantiation of [Coinage] would present any special problems insofar as its relationships are concerned.


*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@xxxxxxxxxxxxxxxxx for your copy.
Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



.