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

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

  • Next message: Isaac Gouy: "Re: Rewrite Engines - too good to be true"
    Date: Thu, 01 Apr 2004 00:28:44 GMT
    
    

    Responding to Nicholls...

    >
    > I personally do not distinguish between an an object invoking a method
    > and an object setting a public attribute - to the degree that my
    > classes and interfaces contains no reference to public attributes - to
    > me all state is internal - this view is based on the fact that how I
    > implement state within the class is no business of any other class so
    > declaring a public attribute may be misleading.
    >
    > I suspect you will disagree with this, but I am interested to know
    > why.

    You're right; I disagree. B-).

    One of the problems of learning OO development starting with writing
    OOPL code is that the OOPLs make some serious compromises with the
    Computational Model. One of those is procedural message passing.
    Calling a getter/setter looks exactly the same as invoking a behavior.
    That strongly suggests that there isn't any difference.

    [Many older OOPLs originally did make the distinction and allowed
    <object>.<attribute> syntax. The problem with that was another
    compromise: OOPLs map attributes to memory storage types. So it was
    impossible to use that notation without exposing the knowledge
    implementation. That's why most OO developers today religiously use
    getters/setters even when that syntax is available.]

    My objection is based on the fact that proper OO construction depends
    upon there being a difference; in particular that they are handled
    differently (i.e., synchronous vs. asynchronous). If you look back at
    my messages one issue keeps popping up: proper management of state
    variables. If a well-formed OO program one simply does not have the
    same sorts of surprises that occurred in procedural development when
    global data was modified unexpectedly.

    However, that safety is only achieved by several things playing together
    properly at the OOA/D level:

    (1) behaviors must be encapsulated, cohesive, and self-contained.

    (2) message and behavior are conceptually separated. The conceptual
    separation of message and response combines with the asynchronous access
    assumptions to ensure one designs methods per (1). IOW, the
    asynchronous communication model simply will not work unless behaviors
    are constructed per (1) and messages are separated from methods.

    (3) collaborations are peer-to-peer. This enables data integrity because...

    (4) behaviors access knowledge directly as they need it to ensure
    timeliness and consistency. There are no middlemen who manage other
    object's collaborations (unless such management is an intrinsic
    responsibility of a problem space entity) to separate acquiring the data
    from actually using it. However, that requires...

    (5) that the OOA/D developer can assume knowledge access is synchronous.

    At that point all five points combine to ensure that any data integrity
    problems can be resolved relatively easily in the implementation because
    the scope is very narrowly defined to be a single behavior method. And,
    for those situations where timeliness depends upon sequencing behaviors,
    the asynchronous model of matching preconditions to postconditions works
    very well at the OOA/D level.

    Though the OOPLs open the door for very poor OO practice, they are
    relatively benign IF the developer has the correct mindset when
    /constructing/ behaviors and collaborations. Then the OOPL syntax
    doesn't matter because OOA/D construction has already provided the
    robustness.

    >
    > I am interested in the cohesion debate because it may blow holes in my
    > current approach.

    One of my points above is that cohesion is only one of several pieces of
    the OO pie that come together to ensure a well-formed OO application.

    >
    >
    >>Mostly it is a matter of experience and judgment. We will need a lot of
    >>methodological advances before our craft becomes true Engineering.
    >>However, there are useful rules of thumb...
    >>
    >>Size is an obvious indicator. <snip>
    >
    >
    > Not guilty - in fact my interfaces methods and state variables seem to
    > be getting fewer and fewer.
    >
    >
    >>Complexity is another. <snip>
    >
    >
    > Also not guilty - if I had multiple (>7 say) states I would look to
    > see if I could compose them by the cross product of two or more state
    > spaces.
    >
    >
    >>Whether one uses state machines of not, large actions or methods are
    >>often a symptom of lack of cohesion. <snip>
    >>
    >
    >
    > Also not guilty- number of statements are becoming concerningly low
    > i.e. often 1!

    So far, so good. Just using these triggers for evaluation will probably
    address 70+% of the cohesion abuses.

    >
    >
    >>Decisions are another aspect of complexity to watch. A lot of decision
    >>conditions in a method can mean trouble because each condition
    >>represents a different business rule. The more there are, the more
    >>likely that future maintenance will require them to be split up to
    >>provide different collaborations. IOW, many business rules in a method
    >>implies lack of logical indivisibility. Splitting those up into
    >>individual methods then raises the property count. Again, nothing to
    >>write in stone but worth evaluating on a case-by-case basis.
    >
    >
    > Also becoming worryingly few - if I have an 'if' statement it concerns
    > me!!!!!

    But don't go overboard. B-). Many business rules are inherently
    conditional:

    CheckingAccount::postATMWithdrawal (amount)
         if (amount > this.balance)
             // send ATM a failure message
         else
             // post to General Ledger and await acknowledgment
             If (success)
                 this.balance = this.balance - amount.
                 // write CheckingAccount to DB
                 if (success)
                    // send ATM a success message
                 else
                    this.balance = this. balance + amount
                    // send ATM a failure message
             else
                 // send ATM a failure message

    Checking the balance is a pretty clear business rule that is very much a
    part of making an withdrawal. Recording the change and handshaking with
    the DB is inherently conditional and it is important to deal with that.
      Similarly, handshaking with posting a GL transaction is inherently
    conditional.

    The thing that is wrong with this example is not the IFs per se. It is
    that all the IFs are in the same behavior. That's an indivisibility
    problem (which I forgot to put in my list) because posting to the memory
    CheckingAccount, posting to the DB image, and posting to the GL are
    quite different activities with their own unique rules and policies.
    More important posting to the DB and the GL /requires/ a handshaking
    protocol (in fact more complicated than this) so one wants one method to
    send the post request and another method to process the response.

    [The advantage of indivisibility will become apparent when one adds
    something like an audit trail facility that is interposed between the
    CheckingAccount and the DB (which is probably already in the GL, so it
    won't be added there). Then all one has to to is reorganize who sends
    the messages.]

    >
    >
    >>Collaborations are another clue. When behavior responsibilities are
    >>exclusively accessed by two other classes or groups of classes that are
    >>clearly quite different contexts, that suggests the object in hand is
    >>providing disparate concerns.
    >
    >
    > I don't think so.

    That you do that or that this is a problem?

    >
    >
    >>Another clue lies in "spider" collaborations where the object
    >>collaborates with many other objects. (Note that collaboration is done
    >>over relationship /paths/ so the number of direct relationships is not a
    >>good diagnostic.) That usually indicates a god or controller object
    >>that knows far too much about how other objects collaborate. IOW, its
    >>implementation captures overall solution sequencing.
    >>
    >
    >
    > I'm not sure - my code is slowly becoming like a ball of tumbleweed
    > with each twig of weed a different concern intertwined with others
    > concerns my problem here I suspect where these concerns interact.

    The call graph for an OO application will often be superficially much
    worse than one for a procedural application. That's because more
    cohesion, peer-to-peer collaboration, and logical indivisibility of
    behaviors tends to result in a lot more messages being issued and there
    are no middlemen.

    However, that ball of tumbleweed is much less of a problem than for a
    procedural application precisely because message and method are
    separated and because behaviors are cohesive, self-contained, and
    logically indivisible. That allows one to reorganize the messages
    without touching implementations. More important, that can be done at a
    higher level of abstraction (e.g., a UML Interaction Diagram) than
    individual class method implementations.

    The procedural call graph, OTOH, typically reflects hierarchical
    decomposition where low-level functions are semantic extensions of the
    high-level functions. IOW, the specifications of high-level functions
    necessarily include the specification of any low-level functions
    decomposed from them. That means the implementations of the high-level
    functions depend upon what the low-level functions do. So one can't
    touch a low level function without affecting the clients of any of its
    parent high-level functions.

    In OO construction god objects are the equivalent of high-level
    functions in hierarchical functional decomposition. They represent
    middlemen who coordinate and, hence, depend upon what other objects do.
      In effect the god object implementation "hard-wires" part of the
    solution sequence of operations. Consider:

    A::doIt (x)
         y = Bref.doIt (x)
         this.attr1 = y

    The specification of A::doIt must include what B::doIt does. That is
    reflected in the fact that one cannot test A::doIt unless B::doIt
    exists. [Note that stubbing B::doIt in a test harness doesn't cut it.
    The result of executing A::doIt (i.e., storing a value in A.attr1) will
    then potentially be different in situ than in the test, which is a
    no-no.] That is a dependence of A::doIt's implementation on what
    B::doIt does.

    So the general rule in OO construction is that a behavior should never
    return a value used by the caller; only knowledge getters are allowed to
    do that. [Note that in this is completely consistent with the
    synchronous vs. asynchronous models for knowledge vs. behavior
    collaboration. Only a synchronous access can provide a value. Ain't it
    great when a Plan comes together?] So the OO solution to this problem is:

    A::doIt (x)
         Bref.doIt (x)

    A::doItToo (x)
         this.attr1 = x

    Now B::doIt sends a message back A via A::doItToo. [In this simple
    example A::doItToo is just a setter, but hopefully the point is clear.]

    Note that one can get the same sort of dependencies with state variables
    even without behaviors returning values:

    A::doIt (x)
        Bref.doIt (x)
        Cref.doIt (x)

    This is fine so long as B::doIt doesn't set an attribute that C::doIt
    accesses directly. If so, then A::doIt is "hard-wiring" in its
    implementation the necessary sequence of execution of B::doIt before
    C::doIt in the overall solution.

    However, this is not A::doIt's problem. Why? Because looks are
    deceiving since I am using a 3GL-like pseudo code. In fact, at the
    OOA/D level all A::doIt is doing is sending two messages. The rules of
    the asynchronous behavior model say that the order in which those
    messages will be processed is indeterminate. So if C::doIt depends upon
    B::doIt setting an attribute first, then I have incorrectly analyzed the
    processing preconditions for executing C::doIt in the overall solution
    and I have put the generation of the message to C::doIt in the wrong
    place (i.e., it should have been put in B::doIt).

    >
    > On the basis of your rundown, I'm 90% sure I am not guilty of low
    > cohestion.
    >
    > My final question in this line though is why are high cohesion and low
    > coupling supposed to be competing aims - this may be the root of my
    > confusion - to me they seem to be complementary?

    The short answer is that cohesion and coupling are apples & oranges.

    Cohesion is a means of achieving logically consistent, self-contained,
    logically indivisible, intrinsic abstractions of problem space entities
    and their individual properties. Coupling is about reducing
    dependencies between the implementations of those properties via
    encapsulation through interfaces.

    However, in the sense that high cohesion facilitates or enables low
    coupling, they are, indeed, complimentary.

    >
    >
    >>Let me try to rephrase my view. As a translationist I see automation of
    >>the computing space as inevitable. Jacobson and I may disagree about
    >>how long it will take, but the handwriting is clear that OOP developers
    >>will eventually be as rare as Assembly developers are today. However,
    >>automation has its price in performance. No 3GL program will be as fast
    >>as hand tuned Assembly and no OOPL program will be as fast as a
    >>procedural 3GL program. Nor do I expect any executable generated
    >>directly from an OOA model to be as fast as a hand-crafted OOPL program.
    >> However, optimization at each level can be as good as possible because
    >>everything plays together _at that level_.
    >
    >
    > I'm not a follower of the 'big red button' and I don't believe the
    > streets will be lined with 3GL programmers with their begging bowles -
    > to me designing in code or UML are relatively equivalent activities (I
    > know that may ring alarm bells), the problem with UML etc is that
    > it's abstraction is too high and too static to really get into the
    > nitty griity and to be able to rigourously test, and the problem with
    > 3GL code is that the semantic level is too low to be able really
    > really be productive - many of the finest UML designs have completely
    > fallen to pieces when examined rigourously in code but many an
    > expertly crafted 3GL application has disolved into a inconsistent mess
    > - to me the world will merge - there will be no 'step change'.

    You should look at the translation technology. Tools have been
    available for nearly two decades (since before UML) to execute OOA
    models and do 100% code generation for general purpose problems. It
    just wasn't possible with UML prior to v1.5 because there was no meta
    model for abstract action languages to describe method behaviors. As a
    result each vendor provided a proprietary solution for behavior description.

    [In fact, one can argue that today's round-trip tools are actually
    descendants of early translation tools where an implementation language
    (C++, Java, etc.) was used instead of an abstract action language. (We
    translation purists are quite condescending about tools like Rhapsody.)]

    In a translation environment the OOA model is fully executable.
    Standard practice is to run the same test suite against the model and
    the final code; just the test harness changes. [If you are interested,
    Leon Starr's "Executable UML: A Case Study" presents a full model of a
    high-rise elevator system and the book comes with a disk with the
    Bridgepoint model simulator (a competitor of ours). It won't generate
    code but you can make changes to the model and observe their effects by
    executing the model.]

    Note, though, that translation is about separation of concerns. It
    depends upon (a) an OOA model is sufficiently abstract to be independent
    of the implementation environment and (b) that the computing space is
    deterministic. So an OOA model only resolves functional requirements.
    A translation engine (as opposed to code generator) is required to
    address nonfunctional requirements for a given computing environment.

    This allows the solution for functional requirements to be completely
    portable across computing environments without change (analysis reuse).
      It also allows a single translation engine built for a particular
    computing environment (e.g., Java + EJB + UNIX) to generate code for all
    application models (design reuse).

    As probably far more background than you care about, in term's of OMG's
    MDA effort, an OOA model is a PIM (Platform Independent Model) while the
    particular computing environment is represented by a PSM (Platform
    Specific Model). The translation engine eats both models and generates
    code by matching PIM model elements to available implementation
    artifacts in the PSM using translation rules specific to the environment
    for optimization. What MDA provides is a suite of meta model semantics
    that allows the mapping necessary for the portability above. One reason
    the Action Language semantics was added to UML v1.5 was to make that
    meta model complete.

    >
    > I actually don't mind the 'big red button' as long as it doesn't
    > precepose that a programmer is than going to go in and tweak the code.

    That's sort of like saying one wants to be able to go tweak the Assembly
    code a good 3GL optimizing compiler writes. Been there; done that;
    never want to go back. The code an automatic code generator writes is
    pretty ugly stuff with lots of Object* arguments, type casts, nasty
    operator overrides, tons of macros, GOTOs, and other practices that
    would have code reviewers piling kindling around the base of a stake.

    As it happens, I know of one translation vendor who deliberately makes
    the code difficult to read. That's because they don't want to deal with
    customer service calls when the customer second-guesses the code
    generator and breaks something else in the process.

    You have a model that is orders of magnitude more compact than 3GL code
    and you have a model-level debugger. Why would one be tempted to even
    look at the code? [BTW, if you try a good model level debugger you
    won't want to go back to a 3GL debugger anymore than a 3GL developer
    wants to debug in an Assembly debugger.]

    >
    >
    >>Each of those levels represents a unique level of abstraction and a
    >>unique suite of generic construction paradigms. Where I have a problem
    >>is with providing local infrastructures solely to make life easier for
    >>the developer _at a particular level of abstraction_. If one is going
    >>to make life easier for the developer by raising the level of
    >>abstraction, then do it consistently across the entire level rather than
    >>piecemeal.
    >
    >
    > I agree but there is an (to abuse mathematics to an extreme) injective
    > mapping from the OOD to the OOP and from the OOA to the OOD.

    Absolutely. Each stage provides value added. In addition, the
    notations are different. (The OOA profile is a much smaller subset of
    UML than the OOD profile.) But at each level of abstraction one
    <hopefully> has an internally consistent combination of notation,
    methodology, tools, and construction practices that plays together well.
      It is tinkering with that combination that I worry about.

    >
    >
    >>If I am programming at the Assembly level, I make do with the tools I
    >>have rather than using macros to introduce procedures. There is no way
    >>I can do that as efficiently and accurately as the 3GL compiler using
    >>block structuring for scope and modern OS and hardware support (e.g., a
    >>processor stack pointer register). If I am programming in C I make use
    >>of structs and functions in physical modules without introducing logical
    >>infrastructures like classes. There is no way I can do that as well as
    >>a well-designed OOPL can. If I am programming in C++ I deal explicitly
    >>with relationship collection classes because there is no way I can fully
    >>abstract out implementation dependencies in a local infrastructure as
    >>well as an OOA MDA profile.
    >>
    >
    >
    > I really don't understand this view - why program with one hand tied
    > behind your back.
    >
    > If you're a C programmer and read Rumbaugh or Booch (as I did) you
    > immediately want to leverage this new paradigm, I can (and did) very
    > easily create OOP in C, and as I am sure you know C++ originally was a
    > preprocessor. X windows was designed in just such a way - I think.

    Actually, I did an OO preprocessor for BLISS very similar to cfront.
    But, like C++, it wasn't a very good language. I had the same problems
    as Stroustrop in that I was more focused on implementation than
    supporting OOA/D and, at that time (ca '85), I didn't know OOA/D well
    enough (i.e., it was pretty much based upon an enthusiastic reading of
    Meyer's OOSC untempered by experience or mentoring).

    In fact, I see C++ as an argument for my position. Ostensibly the goal
    was to provide a /complete/ OOP paradigm at that level of abstraction.
    Yet C++ ended up being the most technically deficient of all the popular
    OOPLs. If one fails at making the whole thing play together properly
    when building from scratch, what are the expectations of gluing
    arbitrary add-ins onto an existing paradigm without breaking something?

    The OO construction paradigm is nontrivial and there are lots of
    different pieces that /must/ play together for it to work. (Note the
    number of superficially disparate things that need to play together to
    make state variable management work that I noted in the opening.) One
    can make a good case that there is nothing really new in OO development;
    all the practices, techniques, and even notations already existed. What
    was different was the way they were packaged together in a single
    construction paradigm.

    >>If you encapsulate the traversal algorithm elsewhere, then it is none of
    >>Component's business. The navigation is explicit in the relationship
    >>instantiation, which is an external concern from Component's intrinsic
    >>semantics. So I see nothing about a Component that needs to be
    >>"visitable". (Alas, this disconnect gets worse below.)
    >>
    >
    >
    > This is where it gets good - because I don't completely understand
    > from here on, and this is stuff I think I can confidently talk about -
    > which is good :-).
    >
    > I may learn something and the mists may clear.
    >
    > I agree with you but I wouldn't read too much into 'visitable' -
    > visitable is simply a mechanism (to me at least) by which an object
    > can declare it's membership to a set of aspects or behaviours.
    >
    > To me, in the pure GoF interpretation, class CFoo declares itself to
    > be a member of class CFoo by telling the visitor (by invoking
    > VisitCFoo), I must admit I extend it's usage to allow it to declare
    > itself to be a member of all sorts of sets of classes and multiple
    > sets, i.e. a CDog class may well call VisitMammal and VisitDog, in
    > this way the visitor can interogate the object for all sorts of
    > 'aspects' of it's behaviour and process it accordingly.

    I still just don't understand what you are trying to accomplish. Who
    "owns" VisitMammal and VisitDog? Why would one want to call one or both
    of them? How does "visiting" affect collaboration?

                 1 accesses *
    [Zoologist] ---------------------- [Mammal]
                                           A
                                           |
                               +-----------+-----------...
           * chases 1 | |
    [Cat] ----------------- [Dog] [Anteater]

    The Zoologist may only care about Mamallian properties while a Cat only
    cares about Dog properties. The intrinsic properties of the calls
    abstraction don't change with context (i.e., who accesses them). Nor do
    the conditions in Zoologist or Cat that cause a message to be generated
    depend on the properties of Mammal or Dog.

    That's why I need some kind of concrete example to demonstrate (a) what
    your notion of "visiting" is about and (b) why it is a better approach
    than existing nuts & bolts OOA/D techniques.

    >
    >
    >>>
    >>>>However, I think there is a more fundamental issue here. We basically have:
    >>>>
    >>>> *
    >>>>[Car] <#>--------------- [Component]
    >>>>
    >>>>That 1:* relationship is going to be implemented as a collection class.
    >>>> Whoever needs to know how many Components a particular Car has will
    >>>>navigate to Car then, through Car, to that collection. It will then ask
    >>>>the collection object because that's pretty much what it is about.
    >
    >
    > This is good because I want car to be a Composite from day 1 - and you
    > don't.
    >
    > I want to combine the two roles in some manner and you don't.

    In this context I was talking about a simple association (i.e.,
    Component is a single class and a Car is composed of 1 or more instances
    of that class). That doesn't work if there are lots of different sorts
    of parts and some parts are nested (e.g., Door parts have Handle parts).
      To handle different kinds of parts one might do subclassing:

    [Car] <#>------------------- [Component]
                                       A
                                       |
                            +----------+----------+
                            | |
                         [PartA] [PartX]

    To handle the nesting one needs break down Component with the Composite
    pattern:

                                * *
    [Car] <#>------------------- [Component] --------------------+
                                       A |
                                       | |
                           +-----------+------------+ |
                           | | | 1 |
                        [PartA] [PartX] [Assembly] -------+

    There is no combination of roles of Composite organization with Car.
    The Composite pattern is self-contained in [Component], [Part],
    [Assembly], and their relationships. That is, [Car] doesn't care if it
    is composed of instances of one kind of part (original above), instances
    of multiple parts (middle above), assemblies, or some combination
    thereof (bottom). The properties of [Car] are unaffected by those
    contexts because they are intrinsic to what a Car /is/.

    [But the Composite pattern does force Car to understand the structure
    navigation. If that gets complicated, then one wants to separate it
    out. (More below.)]

    >
    > But what I don't see, in general, is how to keep these roles seperate?

    Don't combine them?

    >
    > What does your collection class contain?

    In all three cases it is <probably> just a simply collection of pointers
    to [Components].

    >
    > Would it hold 4 references to CWheels and they in turn implement
    > GetComponentList and this GetComponentList is declared in an interface
    > to provide polymorphic behaviour so that my traversing object does not
    > need to know exactly what object it's talking.

    You are providing a detailed solution to a specific problem whose
    requirements are not yet in evidence. Tell me why one needs to access a
    Car's parts and I can describe the collaborations. But let's assume
    someone needs a list of Wheels on this particular car.

    The reason I introduced [ComponentList] into my model in the original
    message was because I /suspected/ that the answer to that question would
    require specific knowledge of the Composite structure (e.g., one would
    need some sort of Find operation for groups of parts). If that were the
    case, I would want to keep that knowledge out of Car because it is
    effectively an implementation issue. That is, the Composite structure
    is really implementing the original simple 1:* relationship.

    Therefore I would separate the concerns via:

           1 1 1 *
    [Car] ------------ [ComponentList] ------------ [Component] ----...
                                                          A
                                                          |
                                                         ...

    Now [ComponentList] has a method getPartList (type). It navigates the
    Composite structure looking for Components whose type (an attribute of
    [Component]) is WHEEL (or a wheel part number) and returns them as a
    collection of handles. Note that [ComponentList] can encapsulate other
    special information about the structure, such as all Parts of a
    particular type will be siblings at the same level in the Composite,
    which allows the search to be terminated once they are found. It is
    exactly that sort of list management stuff that one wants to encapsulate
    separately from both [Car] and [Component].

    The client who needs a list of a particular Car's Wheels navigates from
    the Car in hand to its ComponentList and invokes getPartList.

    >
    > I would suggest at this point that both CWheel and CCar are now
    > composites! (GetComponentList is not a million miles from
    > 'GetChild(i)' GoF) and are thus now implementing 2 roles.

    <aside>
    Unfortunately the GoF book uses OMT as a notation, which is notoriously
    ambiguous. Note that GetChild can only be a behavior of a [Composite].
      It can't be a behavior of the [Component] superclass in an OO is-a
    relation because it is not shared by [Leaf]. The only common property
    is Operation. So the Composite pattern does not represent a true is-a
    relation; it is more like a Data Modeling parent/child relation.

    In reality, [Client] is responsible for doing the navigation and it must
    be able to recognize a Leaf from a Composite. That's why I introduced
    [ComponentList] as a buffer to encapsulate that and introduced a type
    attribute in [Component] to deal with identity. (GoF gets around this
    by introducing GetComposite to query a Component whether it is actually
    a Composite.)
    </aside>

    >
    > IF your references in your collection class does not contain a direct
    > link to CWheel but to the ComponentList associated with wheel I have a
    > pretty meaningless list of lists with no way of navigating to the
    > meaningful object - CWheel or CCar.
    >
    > (from my perspective) they would still at this point need to implement
    > Visitable or at give access to something that is Visitable so that
    > they can declare themselves to be members of their own class, either
    > that or we get what I consider to be horrendous i.e. reflexion on the
    > object followed by a switch case list - bluuueeegh - also note I that
    > is generally all I do in a AcceptVisitor method - I do not generally
    > embed traversal of children (but I don't think this really matters).
    >
    > So lets say it returns an object in some manner - lets say it creates
    > it - that is Visitable and it's sole role is to declare its
    > creator/owner as a CCar via a VisitCar method on the visitor.

    I can't really respond to these issues because they aren't relevant to
    my solution. (Though ComponentList.getPartList is close to your first
    case in that it returns a collection of direct Wheel references.)

    >
    > Fine.
    >
    > But this is where I started - this is the scenario (apart from syntax)
    > and the solution that I originally proposed.
    >
    > CCar has three ways of providing support for Visitable,Car and
    > Composite either.
    >
    > i) it creates a new object on request to implement that role
    > ii) it implements that role itself.
    > iii) it returns an 'owned' object that implements that role.

    None of the above. B-)

    [Car], [ComponentList], [Component], [Composite] and assorted Parts and
    their relationships are all instantiated by whoever understands what
    goes into a Car. Those are fixed regardless of who wants to know what
    about a Car's parts. (If parts are added or removed over the life of a
    Car, as in an assembly line, then [ComponentList] deals with that.)

    [Car] has responsibilities intrinsic to being a Car. Those are fixed
    regardless of its parts.

    [ComponentList] has responsibilities intrinsic to managing the Composite
    structure. That is basically: add, remove, find sorts of things.
    Semantically it needs to know nothing about Components other than
    whether whether they are Leaf vs. Composite or, possibly, what type of
    thing they are.

    [Component] exists as an abstraction for navigation purposes. It has no
    intrinsic semantics except what sort of thing it it.

    [PartA], [PartX] have the intrinsic semantics of individual parts.

    [Assembly] has the semantics of a collection of [Components].

    So there are seven distinct roles here, of which six are invariant and
    have nothing to do with the way a Car is constructed. The only class
    that deals with that semantics is [ComponentList]. I can deal with any
    client request for access to a Car's parts purely within the
    [ComponentList] class. For a particular set of requirements those
    requests will be fixed.

    >
    > Here it would seem that Car implements car via ii)
    > Implements Composite via iii)
    > And implements Visitable via i)
    >
    > And it's not completely fine, actually Car still supports 3
    > polymorphic interfaces i.e. Car, GetComponentList,GetVisitable -
    > though I would argue that these interfaces do not constitute explicit
    > roles but are simply a mechanism for navigation.
    >
    > This is NOT the text book implementation - the text book would
    > 'extend' the class by implementing those roles itself directly and
    > navigation via casting between each interface.
    >
    > From the OOA point of view of abstraction I have no problem with the
    > thought that Car and Wheel in some way satisfies these three roles,
    > but from an OOD/P perpspective I do.
    >
    > Even this solution makes me worried - I don't like the circular
    > reference between a Car and the reference the ComponentList must have
    > back to the Car - so actually when the traverser gets the
    > ComponentList I would probably create an object that holds and exposes
    > the reference to the Car/Wheel and delegates to a hidden component
    > list.

    That circular reference is, indeed, reason for concern. The dependency
    management people would not care for it at all. But that's more of a
    coupling problem than an abstraction problem. The abstraction problem
    is giving Car too much to do.

    >
    > I am also now worried that I have several objects potentially
    > accessing the same shared state.

    As long as that state is in only one place, that's not a problem per se.
      It is quite common for different functionalities to process the same data.

    >
    > What if car does something that effects the ComponentList i.e. void
    > Crash() - which destroys some elements of the ComponentList.

    If Car does something that [ComponentList] is interested in, it should
    announce that. Let [ComponentList] figure out the appropriate response,
    though.

    >
    > My current solution is to only return ComponentLists by value! thus if
    > some traversing object is navigating the ComponentList and Crash()
    > happens the navigator only has a consistent possible out of date
    > snapshot rather than risk inconsistent state.

    This gets into a quite different area of data integrity that may confuse
    things here. The basic issue is whether one wants snapshots at a moment
    in time or the most recent data.

    Most of the time it is not an issue because methods access data as they
    need it. When one extracts a subset of objects over a relationship path
    it is only "live" for the method that needed it. IOW, from an OOA/D
    viewpoint one does not pass around such subsets, regardless of whether
    they are references or by-value. That allows the implementation to
    enforce integrity (e.g., nobody adds, removes, or changes the objects)
    in only the scope where the collection is formed.

    [The abstract action languages for OOA modeling only provide syntax for
    extracting subsets in terms of relationship navigation. The returned
    set's scope is limited to the calling method and one can't pass it in an
    event data packet. That ensures that it is safe to treat it like a
    stack variable and delete it to prevent memory leaks, etc. when the
    method terminates. Whether one employs references of by-value copies
    really doesn't matter then.]

    >>Note the quoted sentence above and the quoted paragraph below relative to...
    >
    >
    > I couldn't find the quoted sentence!

    I meant the sentence in /your/ quoted text of my statement (i.e., the
    statement immediately above where I stuck the comment).

    >
    >
    >>Car is not the Composite pattern, Component is. ComponentList
    >>understands the navigation of the Composite pattern because we don't
    >>want to burden Car with that; Car has its own set of concerns, like
    >>Drive(...).
    >
    >
    > see above, it either does it itself i.e. IS a composite component, or
    > navigates to GetComponentList.

    The latter. But relationship navigation is the most routine processing
    in all OO applications. One always has to navigate to the owner of the
    responsibility, whether it is knowledge or behavior.

    >
    >
    >>What does this Visitable role actually do? I think I need some words
    >>around this because I really have no idea what it could be doing in this
    >>example context.
    >>
    >
    >
    > see above - in my generlised usage it declares the class to be a
    > member of some set (possibly dynamically) without the use of reflexion
    > and switch/case.

    Alas, that doesn't help. My example above doesn't do those things
    either. IOW, I can't visualize what Visitable means in terms of
    classes, class responsibilities, relationships, and collaborations.

    >
    >
    >>The rules and policies of navigating the Composite's relationships are
    >>encapsulated in ComponentList. An individual Component doesn't need to
    >>know it is even in a Composite pattern or even that there are other
    >>Components or a Car or a ComponentList.
    >
    >
    > Though if it doesn't it needs a way of navigating to this information.

    It has a relationship instantiated between it and ComponentList for that.

    >
    >
    >>>it now satisfies 3 roles Composite,Visitable and Car all of which are
    >>>unencapsulated from each other even though they are mostly orthogonal
    >>>- though they do have some shared state variables.
    >>>
    >>>To me a Car should not know how to walk it subcomponents (it doesn't
    >>>in real life) - why should it? why make it a god? why not create a
    >>>visitor called EnumeratingMechanicVisitor to count them as I would in
    >>>the real world.
    >>
    >>Car doesn't. Separating those concerns is why we have ComponentList.
    >>
    >
    >
    > And navigation.

    I am sensing a pattern. B-)

    What is the problem you have with relationship navigation? In OO
    development /every/ message is addressed by navigating some relationship
    path in the Class Diagram.

    >>The relationships that define a
    >>Composite pattern require someone external to construct it. (Note
    >>that's one reason why constructors are static class methods; one can't
    >>ask the instance to create itself.) Similarly, it must be someone
    >>external to the components that understands how to navigate the
    >>/overall/ structure.
    >
    >
    > But you still need to be able to navigate from Car to its Components?
    > (and Components to Car)

    /Somebody/ needs to navigate from Car to its components. That will
    probably be Car is some situations but it could be virtually any object
    in the application that has a need. Any method anywhere that needs data
    from a particular Car's component will navigate the relationship. To
    invoke a behavior in a Car's component a message will have to be
    addressed to it, which means navigating the relationship.

    >
    >
    >>>>From the point of view of a visitor - it is only through the Composite
    >>>interface - it knows of nothing else.
    >>>>From the point of view of client code it doesn't need to know - all it
    >>>wants is the subcomponents to be enumberated - how that gets done is
    >>>the visitors business - all it knows is a car is Visitable.
    >>
    >>The visitor here is some arbitrary object who has the responsibility of
    >>understanding special rules and policies for navigating the overall
    >>structure. In the example above, ComponentList is the visitor. But all
    >>it needs to do is navigate the existing relationships. That requires no
    >>semantic participation by individual Components.
    >
    >
    > As above - it doesn't BUT if I am navigating the list I need to be
    > able to get to the CCar and CWheel.
    > And if I have a CWheel I want ot navigate to it's list of components.

    That's a new requirement. But to deal with it one adds
    ComponentList.getAssemblyParts(assembly)

    Whoever already has an Assembly in hand navigates to ComponentList and
    invokes the interface.

    >
    > I personally want to externalise the navigation - but I don't think
    > this is a sticking point - in this context I agree it doesn't matter.

    Relationship navigation is already externalized in OOA/D. It is the
    OOPLs that muddy the waters. In OOA relationship navigation is done
    purely in terms of Class Diagram relationships; one doesn't even need to
    specify the class names. More to the point, one determines where
    messages originate and where they go at the level of a UML Interaction
    Diagram rather than in individual method implementations.

    [Though it is rarely done that way, there is nothing to prevent writing
    all of the class' methods without including a single behavior method
    call. One can even unit test them that way. One can then do the
    Interaction Diagram and backfill the methods with the messages that they
    have to generate.]

    >
    >
    >>Conversely ComponentList knows nothing about Component semantics (even
    >>identity may be implicit); it just "walks" (and manages) relationships.
    >> Any client (e.g., Car) who has a need to interact with a particular
    >>Component (or group of Components) just asks ComponentList to find it
    >>and the client then collaborates with it directly.
    >
    >
    > Ahhh, we agree then there needs to be navigation.

    Good.

    >
    >
    >>Therefore neither Car nor Component needs to have any special properties
    >>other then what they intrinsically are. IOW, there is no Visitable
    >>property here.
    >>
    >
    >
    > as above I want Visitable because I want the class to be able to
    > declare it's nature before being processed in a typesafe manner and
    > without reflexion and switch case.

    But that's exactly what my example above does without any such declaration.

    >
    >
    >>That's a problem. B-) In /my/ analysis Car has only one role so far:
    >>Drive. ComponentList has the collection management role for the
    >>Composite "collection" (add, remove, find sorts of things). And
    >>Components are just assemblies or ordinary parts.
    >>
    >
    >
    > You have split them - yippeee :-)
    >
    > And I want to.

    Good.

    >
    > But my iceberg is looming (as above) - I still need polymorphic
    > interfaces to allow the client code to navigate between CCar/CWheel
    > and CComponentList and I want each CCar/CWheel to implement IVisitable
    > so that it can declare it's nature in an extensible manner.

    Not so Good. B-(

    We agree on separating the concerns.

    We agree on navigating relationships.

    At this point I argue: that's all we need to solve the problem in hand!

    So where does IVisitable come from? Where is the need for polymorphic
    interfaces when the client asks for what it needs?

    Whoops, maybe I have a small insight into your concern. I am thinking
    like a code generator. My getPart(type)above returns a [Component] that
    happens to be a Wheel because the type argument requested that. The
    caller expects a Wheel type. If one is in a statically typed language
    like C++, that requires a cast of the return in the caller. (It's not a
    problem for a dynamically typed language like Smalltalk.)

    So to be typesafe (i.e., no casts) in a statically typed (manually
    coded) language one would need an interface based on the available
    Component types: Wheel* getWheelPart(), Tire* getTirePart(), etc. I
    agree, that would be tedious.

    >
    > In conclusion
    >
    > i) my state is worryingly being accessed by different people at
    > different times i.e. walking the list and Crash - I'll go for copying
    > the component list for the moment.
    > ii) the solution is quite complex - much more complex that simply
    > implementing ICar,IVisitable,IComposite and delegating, but state
    > management seems easier.
    > iii) I would prefer to stylelise the model.
    >
    > Either
    >
    > i) I'm going in the right direction and I just need to have the
    > confidence of my convictions and carry on.
    > ii) I'm disappearing into a cul-de-sac.
    > iii) My fundamental paradigm is flawed in some manner and I should do
    > some plumbing.

    I don't think we are close enough to identifying our disconnects to
    address this.

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


  • Next message: Isaac Gouy: "Re: Rewrite Engines - too good to be true"
    Loading