Re: Abstract public member variales?



Responding to Guild...

Actually, I was sloppy in the model; the relationship should have
been 1:1, not *:1. ConcreteA can save any member of the [ObjectA]
class, but it only does it one object at a time. I assumed some
other object would "walk" the collection of all [ObjectA] members,
instantiate a 1:1 R1 for each one (so ConcreteA could get at the
data), and then would invoke Deconstructor.saveIt(). The
ConcreteA.saveIt() implementation would navigate R1 and read the
right attributes to save.


Now I realize that there is something else bothering me about that diagram. Let me draw an expanded version just to be sure that I understand correctly:

1 saves R1 [ObjectA] ------------------------+
|
1 saves R3 1 | 1
[ObjectB] ----------------- [Deconstructor]
1 | + saveIt()
1 saves R4 | A
[ObjectC] -------------------+ | R2
+---------------------+-----------------+
[ConcreteA] [ConcreteB] [ConcreteC]

Since the direction of these relationships is now clear, if I am correct then it must be that every Deconstructor object has references to ObjectA, ObjectB, and ObjectC, then the various concrete deconstructors choose the appropriate reference to save so that ConcreteA saves ObjectA and ConcreteB saves ObjectB, etc.

Yes, that <almost> captures it. But that doesn't force references in [Deconstructor]. That is kind of an RDB view. As an OOA/D diagram, all it is saying is that the relationships exist. I think I already mentioned that there are three different ways to implement OOA/D relationships at OOP time. So whoever invokes [Deconstructor] could pass the ObjectX as an argument rather than using an embedded reference. But that segues to...

However, the diagram is not quite correct and there is another constraint not shown. The R1, R2, R3 relationships are conditional on the [ObjectX] side because only one relationship can be instantiated at a time when Deconstructor.saveIt() is invoked. So even if one is using references, only one relationship is instantiated at a time. In practice that means that one only needs one reference attribute at OOP time. (One will also need a cast in each [ConcreteX] to the right [ObjectX] if the OOPL is statically typed.)

The polymorphic dispatch comes in when one used [Deconstructor] to
save objects from [ObjectB] and [ObjectC] classes. Instantiating
R1 correctly also means that one has to have the right flavor of [Deconstructor] subclass object in hand when one invokes Deconstructor.saveIt().


There are three tasks involved in saving an object, call it A. The first is obtaining the Deconstructor object. This requires knowledge of the concrete class of A and probably a dynamic cast or something similar to get it.

As I mentioned above, the reference will probably be instantiated as an Object* and there will be a cast in [ConcreteX] to the right [ObjectX]. However, someone who understands the context will, indeed, need to make sure the R1/R2/R3 relationship is consistent with the flavor of [Deconstructor] used. IOW, there is another relationship:

[SaverClient]
| 1
|
| R5
|
| invokes
| 1
[Deconstructor]

Whoever understands the context will have to assign R5 as well as one of {R1, R2, R3} in a consistent manner. In this case that will probably be [SaverClient] because it is the one "walking" the objects to be saved.


The second is instantiating the relationship between A and the Deconstructor. If my diagram is correct above, it is a different relationship for each concrete class that A might have, so either this must be done at the same time the Deconstructor is being obtained or another dynamic cast is needed.

Yes, except for the constraint that only one relationship is active at a time.

The third is invoking saveIt() on the Deconstructor object, requiring nothing but the deconstructor object itself. This is where there is supposed to be polymorphism, but there cannot be real polymorphism if the concrete class of the object is still known, so this must happen in a different procedure from the first two tasks.

There is polymorphism because one gets different behaviors from Deconstructor.saveIt() depending on what relationships are instantiated. While [SaverClient] may actually be the one to instantiate the relationships in this case, that is not necessarily the case. Even when it is, that instantiation is a separate responsibility than the one triggering the actual collaboration activity of saving the object. It is the collaboration that is polymorphic, not the instantiation. (See code examples below.)


There must be some saving procedure that is independent of the concrete classes involved and just performs general saving activities, one of which is invoking saveIt() on deconstructor objects that it gets from somewhere else.

Right. That may or may not be in the same object that instantiates the relationships.

It is not clear how the separation of the first two tasks from the third task is expected to occur. Who knows the class of the deconstructor and who does not? Who is forced to deal with dynamic casts and who gets the benefit of polymorphism?

It seems rather strange that one would go to the trouble of dynamically determining the class of the object to be saved and then not simply call saveIt() immediately.

As I indicated, in this case the instantiation and the actual save collaboration would probably be in the same method, much less the same object. At some point there is a trade-off between encapsulation infrastructure, separation of concerns, and convenience. In this case both the instantiation (two relationships) and collaboration (one call to saveIt()) are trivial so it is hard to justify the infrastructure of an additional object to separate those concerns.

In addition, once one has an object like [SaverClient], it is at such a high level of abstraction in the overall saving process that its most complex task is instantiating the two relationships. In addition, the precondition of executing Deconstructor.saveIt() in the overall flow of control happens to be that the two relationships have been instantiated properly. So it is not only convenient, it is also logical in this situation that whoever is instantiating the relationships should send the message triggering the actual save operation.

IOW, in OOA/D we always want to think of instantiation as conceptually a separate concern from collaboration. Consequently we will look for ways to isolate them to provide better maintainability. But in some cases it simply doesn't make sense to do so (i.e., the maintainability benefits are not justified by the development costs). But that should be a conscious decision on the part of the developer.

That is essentially
the same as inheriting Serializer's saveIt() for the [ObjectA],
[ObjectB], and [ObjectC] classes. One invokes the same interface
but gets different implementations depending on which class one is
saving.


It is nice in the Serializer pattern because we are not forced to know which class we are saving. It seems to defeat the purpose of polymorphism if we must to use a dynamic cast to get it.

To invoke serializer's saveIt() one needs to know what object is being saved. In a statically typed language the invocation code is going to look something like:

Caller::saveObject()
{
ObjectA* myObjectA; // must be assigned by somebody
...
myObjectA->saveIt(); // not a polymorphic dispatch
}

Now the same caller method using [Deconstructor] would look like:

Caller::saveObject()
{
Deconstructor* myDeconstructor; // must be assigned by somebody

myDeconstructor->saveIt(); // true polymorphic dispatch
}

The only real difference (other than the dispatch) between the two cases is what goes on inside the SaveIt() method. For the Serializer that looks like (oversimplified for the actual persistence write):

ObjectA::saveIt()
{
Persistor* myPersistor;
DataPacket* myDataPacket = new DataPacket;

myDataPacket->value1 = this->attribute1;
myDataPacket->value2 = this->attribute2;
...
myPersistor->save(myDataPacket);
}

while in [ConcreteA] it looks like:

ConcreteA::saveIt()
{
Persistor* myPersistor;
DataPacket* myDataPacket = new DataPacket;
Object* myObject; // must be assigned by somebody

myDataPacket->value1 = (ObjectA*)myObject->attribute1;
myDataPacket->value2 = (ObjectA*)myObject->attribuee2;
...
myPersistor->save(myDataPacket);
}


In fact, I think this entire technique for persistence could be simply described as the Serializer pattern, but with the implementations of the 'Serializable' interface removed from the actual Serializable class and into another object by using the Strategy pattern.

Bingo. Give that man a kewpie doll.


Actually, I was wrong! I think that saying this technique uses the Strategy pattern is misleading. The Strategy pattern is defined to include a reference from the Context object to the Strategy object, but that is lacking in this case, so technically it is not the Strategy pattern. In addition, it seems very tempting to put a reference from ObjectA to ConcreteA and if we do not make it clear that this is not actually the Strategy pattern, people will innocently put that reference in thinking that they are following this thread's advice.

The reference is an OOP implementation detail. The essence of the pattern is that one delegates multiple implementations of a single behavior from [Context] to [Strategy] and then substitutes them based upon context.


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



.



Relevant Pages

  • Re: Abstract public member variales?
    ... Since the direction of these relationships is now clear, if I am correct then it must be that every Deconstructor object has references to ObjectA, ObjectB, and ObjectC, then the various concrete deconstructors choose the appropriate reference to save ... Both instantiation and the communications of collaboration are dynamic issues. ... at the OOA/D level there aren't any significant dependencies across the relationship because message is separated from response method in OOA/D and all messages are announcements of what the sender did. ...
    (comp.object)
  • Re: Abstract public member variales?
    ... correct then it must be that every Deconstructor object has ... if there is no reference to ... that instantiation is a separate responsibility ... the Strategy pattern is misleading. ...
    (comp.object)
  • Re: C++ design question
    ... >>and policies for object and relationship instantiation (object ... way to do that in OOA/D is via separation of concerns and encapsulation. ... >>conditional one can't implement it with a reference. ... >>policies ensures that the developer thinks about them. ...
    (comp.object)
  • Re: Singletons
    ... There are four basic ways to implement a relationship: embedding an object in the implementation of another object; employing a referential pointer; passing an object reference as a message argument; and using an RDB-style search of instances by explicit identifier. ... Relationships are always implemented and navigated when addressing collaboration messages. ... By modifying the context I meant that one defines the solution flow of control differently so that the instantiation can be done in one place rather than in several. ...
    (comp.object)
  • Re: Managing multiple instances
    ... That will probably mean that you have to have some sort of lookup table somewhere in the implementation. ... You will also need some way to describe the client context so that it can be mapped to the actual object identity (e.g., an index into the lookup table that yields a reference). ... All you have to provide is an enumeration variable that is named for the models. ... Unless instantiation is trivial and unlikely to become more complicated, it is generally good practice to encapsulate the rules and policies for instantiation away from the rules and policies of collaboration. ...
    (comp.object)