Re: implementing roles in OOP......

From: H. S. Lahman (h.lahman_at_verizon.net)
Date: 04/16/04


Date: Fri, 16 Apr 2004 21:56:15 GMT

Responding to Nicholls (Part III)...

>>>This acyclic thing mildly confuses me - Don't get me wrong, I don't
>>>like them because I like things to be standalone i.e. I should be able
>>>to talk about cars and components without worrying about them being
>>>visited and implicitly knowing all the parts that go to make up a car
>>>- which the GoF thing would imply.
>>
>>You might want to look at Robert Martin's book, "Agile Software
>>Development". He only talks about DAGs explicitly in the context of
>>packages, but the same notion is reflected in most of his Principles.
>>
>>Note that talking to and knowing about are two different things.
>>Dependency management is about minimizing what one object needs to know
>>about another object. That should be minimized and, when unavoidable,
>>unidirectional. But to talk to someone all you need to know is
>>identity, which is pretty benign. So talking can be bi-directional.
>
>
> Nope- I'm not doing well today.....to talk to them I need to know
> where they are (identity) and what I can sensibly say (interface).
> I can get that identity either by having a static relationship with
> them or by having it passed to me.

Exactly, but this may be part of the disconnect. What's important in
OOA/D is the way relationships are used.

In the OOPLs you need to know exactly who you are talking to because (a)
there is no separation of message and method and (b) the 3GL paradigm is
based upon type systems and memory addressing for identity. However, at
the OOA/D level you don't need to have identity to send a message.
(More precisely, identity can be handled so abstractly that it becomes
effectively transparent.)

That is why I keep talking about relationship instantiation and
navigation all the time. All messages in OOA/D are addressed based upon
navigating relationships in a very abstract manner. For example, in a
typical abstract action language one might have:

handle = this -> R14 -> R8
send M1 handle ....

where R14 and R8 represent relationship identifiers (in this case both
are 1:1 relationships), M1 is a message identifier, and the ellipses
represent data to be passed. Note there is no mention of class names,
explicit identity, or what will happen in response to the message. In
UML the relationships are sufficient (in this case) to get to whoever is
at the end of the relationship path.

IOW, it is not the concern of the message sender who is there. The
right class will be determined by the Class Diagram in UML. Making sure
the right instance of that class is there at the end of the path is the
responsibility of whoever instantiates the relationships, not the sender
of the message. [The developer decides where messages originate and
what paths they traverse at the Interaction Diagram level rather than at
the level of individual object implementations. (The developer also
decides who instantiates relationships at a similar level of abstraction.)]

Having the mindset that identity is implicit in relationships is crucial
to long-term robustness precisely because it separates the concerns of
relationship instantiation from relationship navigation. Since the
message sender doesn't even know who the message is going to, it becomes
tough to make the message sender implementation depend on what happens
when the message is received. Thus the paradigm for message senders in
OOA/D is I'm Done rather then Do This. I can't overemphasize how
important that paradigm is to preventing hierarchical implementation
dependencies.

At the 3GL level the abstract navigation notation must get converted to
type systems and procedural message passing. However, that becomes
relatively benign because the decoupling was already provided in the
OOA/D that eliminated implementation level dependencies. IOW, one
designed behaviors and collaborations around relationship navigation via
announcement messages rather than sequences of procedure calls invoked
through imperative messages.

>
>
>>>>>Again see above this is an interpretation of OO based on the use of
>>>>>subtype, it creates a simpler model than formal concept analysis i.e.
>>>>>less dimensions, but more elements within a single dimension.
>>>>
>>>>Subclass at the OOA/D level, subtype only at the OOP level.
>>>
>>>
>>>I can't say I see a difference - I'm sure there is one.
>>
>>Subclasses are about set membership. Types are about property access.
>>
>
>
> OK, but aren't they almost the same? If they are of the same semantic
> set I should be able to access the same properties and vice versa.
> (I'm not arguing - just ignorant).

Both are based on the same set theory so they are identical at that
level. Where the differences become important is the way one approaches
software construction.

One example of that is the issue of identity vs. relationship navigation
above. Relationship instantiation is basically about constraining who
you can get to (i.e., which set members are available). IOW, if one has
properly executed the problem space rules and policies for relationship
instantiation, one always gets to the right set members. Perforce,
those set members are guaranteed to have the right properties. That
allows one to send generic messages that reflect the sender's context
without worrying about accessing properties. That is, property access
is a given.

>
>
>>>
>>>>>In fact in some sense the move away from static inheritance trees to
>>>>>object composition is a move towards categorisation and away from set
>>>>>theory.
>>>>
>>>>Visitor is just as static. It depends upon consistent property
>>>>knowledge on both sides of the relationship.
>>>
>>>
>>>via an interface - unless I've got this wrong.
>>
>>More than that. See the point about naming conventions above. The *:*
>>relationship is "hard-wired" in the Accept implementation and the
>>Visitor interface.
>>
>>
>>>
>>>>To provide a new behavior
>>>>explicit infrastructure must be added on both sides.
>>>
>>>
>>>Does it? I can add new visitors. I can add new elements and if they
>>>constitute a need to update the Visitor interface - I think is a good
>>>thing.
>>
>>The new Visitor must have the right methods for every concrete Element
>>subclass. IOW, the existing structure on the Element side must be
>>hard-wired into each new Visitor.
>
>
> I'm not with this either......it is hard wired, yes, but into the
> interface IVisitor. If I add a new concrete visitor I don't need to
> add any infrastucture.

It's not an interface issue; it's an implementation issue. Each new
Visitor must have a VisitConcreteElementX method implementation for
every Element subclass. If a new Element subclass is added, one must
add a method implementation for it to every Visitor subclass. Even if
those implementations are largely no-ops, you still have to provide them.

>>But what if there are multiple possible Clients that collaborate with
>>ObjectStructure? Now each one will have to be modified when a new
>>Visitor is added. [Which is why I would not implement literally like
>>GoF, as I indicated above. Using my approach it is much more likely
>>that the context requiring a particular 'v' can be identified in one
>>place independent of the number of Client collaborations with
>>ObjectStructure.]
>
>
> Nope... not with this. Why?

The Client provides the Visitor reference when it invokes
Element.Accept(v). That means the Client needs to know which flavor of
Visitor subclass it should be. If a new Visitor subclass is added, then
the Client has to decide when a reference to that new subclass should be
passed. IOW, somewhere there is going to be an IF that tests context to
determine which flavor of Visitor is appropriate and each flavor will
necessarily have unique rules an policies that govern whether it is
appropriate.

[I'll save time and anticipate the argument that the appropriate Visitor
instance is handed to Client by someone else. That just defers the
decision to someone else. Somewhere there is someone who knows that in
THIS context one needs to use THAT Visitor flavor. Most likely Client
will already have a relationship with the right Visitor and it just
passes it along. But whoever instantiated that relationship has to
interpret the rules (i.e., test the context) for when that flavor of
Visitor is appropriate.]

[Caveat. One of the nice things about doing relationships properly is
that one can often capture business rules and policies in the
relationships themselves. For example, it is usually a good idea to
look for relationships that will eliminate conditional ends. That saves
tests in the navigation code that would have to be executed for every
navigation (e.g., if (!NULL)...). So there doesn't necessarily have to
be an IF. However, there will be a code change somewhere to instantiate
the appropriate relationship to the new Visitor.]

>>>>There are also common sense situations when practicing IID. If one is
>>>>working on an early increment but one knows that requirements for a
>>>>later increment will require a more complex solution than needed for the
>>>>current increment, common sense says one should do it only once. But
>>>>even then, one builds only the minimum infrastructure support for the
>>>>future increment, not the future increment's solution. IOW, one solves
>>>>the current problem in a way that won't have to be scrapped and
>>>>rewritten when the future increment is done.
>>>
>>>
>>>Isn't this last paragraph what I've been saying?
>>
>>Not as I understood you. In this case the requirements are already
>>known; one just isn't implementing them on the current iteration. As I
>>interpreted your position you were using Visitor because you thought it
>>might be necessary for /future/ requirements. IOW, as I understood it,
>>you make a judgment about the likelihood of the requirements coming to pass.
>
>
> Well OK, see my message pipe example above.
>
> Joy....
>
> code....
>
> I will understand the next bit......
>
>
>>When there are multiple selection conditions by code in ComponentList
>>looks like:
>>
>>int wheelCount = 0;
>>int tireCount = 0;
>>int lugNutCount = 0;
>>...
>>int* count_table [COMPONENT_TYPE_COUNT] = {
>> &this.wheelCount,
>> &this.tireCount,
>> &this.lugNutCount,
>> ...
>> }
>>static (()*) func_table [COMPONENT_TYPE_COUNT] = {
>> Component::getWheels,
>> Component::getTires,
>> Component::getLugNuts,
>> ....
>> }
>>...
>>ComponentList::add (Component* comp)
>>{
>> (*count_table [comp->type])++; // update right count
>>
>> // do add stuff
>>}
>>
>>Component* ComponentList::selectComponent (int comp_type)
>>{
>> return func_table [comp_type](comp_type);
>>}
>>
>>Wheel* ComponentList::getWheels (int comp_type)
>>{
>> Wheel* wheels = new collectionDeJour(this.wheelCount);
>> Component* comp = this.myRoot;
>> while (comp != NULL)
>> {
>> if ((comp.type == comp_type))
>> wheels.add (comp);
>> // update comp via traversal algorithm
>> }
>> return wheels;
>>}
>>
>>
>>One key point here are that all the enumerations are captured in data so
>>that they all Just Work with one executable statement and they are all
>>defined in a single table. Perhaps more important, it is all
>>encapsulated as knowledge responsibilities in a single class.
>>
>>A similar point applies to invoking behaviors -- there is one call
>>statement that fits all selections. All one needs is to add an entry a
>>jump table for a new selection and encapsulate actual selection behavior
>>in a private method. [One can optimize away the individual iterations
>>in the methods to a single iteration. I was just keeping the type
>>management simple.]
>>
>>A much more important point is that ComponentList only does collection
>>sorts of things. It doesn't understand the semantics of Wheel in the
>>same way as the Client that collaborates with it. Similarly, the Client
>>doesn't know anything about the organization of car parts.
>>
>
>
> I understand all the above as well - yippeee..
>
> You think this is simple though? less complicated than two visitors?
> more maintanable ? less likely to go wrong ?

Count again. B-). The code I provided accounts for three enumeration
Visitors and three selection Visitors. But let's compare complexity in
detail:

Implementation | Me | You
------------------------------------------+-----------+-----------------
Number of classes | 1 | 7
------------------------------------------+-----------+-----------------
Number of methods to execute all | |
    enumerations | 1 | 3x7 = 21
------------------------------------------+-----------+-----------------
Number of executable statements to | |
    execute enumeration | 2 | visit all
------------------------------------------+-----------+-----------------
Number of methods to execute all | |
    selections | 4 | 3x7 = 21
------------------------------------------+-----------+-----------------
Number of executable statements to | |
    execute selection | same | same
------------------------------------------+-----------+-----------------
Number of lines to add a new enumeration | 2 | new class + > 6
------------------------------------------+-----------+-----------------
Number of lines to add a new selection | |
    (excluding traversal method) | 1 | new class + > 6
------------------------------------------+-----------+-----------------

So I am curious about what your criteria is for 'simple'. B-)

Also I am curious how you find the Visitor solution more maintainable,
given the last two table entries above. B-))

>
> I don't.
>
> I think your recreating the wheel (!!) here, your func_table looks
> very much like an exposed v-table - (do I now know what you mean by
> 'v' ?)

To the extent func_table and a vtable are both jump tables, yes. But in
the end it is just a look-up table. As a general rule look-up tables
are much preferred over conditional in-line logic.

>
> You have added the calculation of each count as a derived property of
> adding/removing new components - it's a different behaviour, I find
> these sort of things more prone to going wrong than an absolute
> calculation - but it doesn't really matter.

I did that to demonstrate the point I made earlier in the thread that
one can often transform behavior responsibilities into knowledge
responsibilities at a considerable savings in code -- which was he case
here.

>
> I I get a few more operators then I would claim for componentlist is
> becoming a minor deity.

Not if it sticks to what it is about -- collection management. Knowing
how many members there are and extracting subsets is pretty basic
collection stuff. That sort of thing is added trivially in my version.
  Anything more exotic would be a collaboration with the Components
themselves and ComponentList wouldn't do that. The Clients who cared
would and ComponentList would just provide the Components.

>
> I accept you will have fewer lines of code - but I think you will have
> more lines of executable code AND that you concerns are more entwined.

All of the executable code is there (except I didn't bother with the
getLugNuts and getTire implementations). The selection code that
"walks" the Component structure would be identical for you version.
[Though in your case it would be in two places. The iteration would be
in ObjectStructure or Client while the actual selection (wheels.add)
would be in your VisitConcreteElementX implementation.]

Time for another break.

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

H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions -- Put MDA to Work
http://www.pathfindermda.com
(888)-OOA-PATH



Relevant Pages

  • Re: Text terminal rendering design
    ... The client wants to later change the color and provides ... If the client is doing a multiple part conversion then the client ... On separating concerns of instantiation and data acquisition: ... A SymbolFactory always produces a Symbol of the same subclass. ...
    (comp.object)
  • Re: Abstract Method w/o Factory
    ... >>It is fairly common for a Factory to instantiate both the instance of ... >>Client and the relationship. ... >>they isolate the rules and policies of instantiation in one place. ... that wants the instantiation responsibilities encapsulated AND isolated ...
    (comp.object)
  • Re: A Design Problem
    ... there are a flock of reasons why one should be worried about the overall design and should look for another way to allocate responsibilities and/or implement relationships when managing collaborations. ... then the client needs to navigate a relationship that is directly to the ... In an OOPL that would be enforced in the type system by making the pointer type be specifically a Button type rather than a Control type. ... But to make that decision the sender method must understand its context in the overall solution. ...
    (comp.object)
  • Re: dip Notions 2 Major Errors
    ... Again though, if the interfaces are in their own package, you can alter ... client, add new client, alter implementation, add implementation (though you ... instantiation behaviour, which we must keep. ... not available it wont work. ...
    (comp.object)
  • Re: implementing roles in OOP......
    ... entity level one relies on cohesion -- the individual responsibilities ... must be clearly related at the level of abstraction. ... Car is abstracted as a monolithic entity. ... When Client collaborates with Context it invokes Context.Operation. ...
    (comp.object)