Re: C++ design question

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

  • Next message: H. S. Lahman: "Re: For Nimmi Srivastav"
    Date: Fri, 01 Oct 2004 16:46:26 GMT
    
    

    Responding to Elliott...

    >>I think a simpler solution would be to create the Bar instance
    >>separately and initialize a pointer to it in Foo:
    >>
    >>class Foo
    >>{
    >>private:
    >>Bar* myBar;
    >>...
    >>public:
    >>void DoStuffWithBarBase ()
    >>...
    >>}
    >>
    >>fooDeriveN::DoStuffWithBarBase()
    >>{
    >>...
    >>this.myBar.doIt();
    >>...
    >>}
    >>
    >>Creator::createFooDeriveN(...)
    >>{
    >>Bar* myBar = new (barDeriveN(...));
    >>fooDerive* myFoo = new (fooDeriveN(myBar,...));
    >>...
    >>}
    >
    >
    > Is the intention here for DoStuffWithBarBase() to be virtual, and for
    > each fooDerivedN to implement its own instance of this? This is a
    > perfectly valid way of approaching my cut down example, but I don't
    > want to do this in my real-world design because barBase knows how to do
    > a significant amount of complex tasks and must maintain a significant
    > state. I don't want this to be re-implemented (or even called down to)
    > from the barDerivedN as this would add significantly to the complexity.

    I assumed that the Foo subclasses each had specialized responsibilities,
    only one of which was to invoke Bar.doIt. That was the intent of the
    "..." around the call. Whether DoStuffWithBarBase needs to be virtual
    or not depends on whether there are such specializations.

    Sorry, but I am confused by the rest of this paragraph. I assumed from
    the DoStuffWithBarBase name that there wasn't any choice about invoking
    barBase services. In any case, I don't see any added complexity. In
    the end DoStuffWithBarBase is just sending barBase a message (doIt) for
    a collaboration.

    >
    > And fooDeriveN::DoStuffWithBarBase() still only has a pointer to the
    > Bar base class to work with, so it can't access anything that
    > Foo::DoStuffWithBarBase() can't access.

    True; one is limited to polymorphic dispatch. But I thought using that
    interface was why you needed the reference to barBase in the first
    place. [If you were planning on downcasting it to the correct subclass
    in the DoStuffWithBarBase implementation, I would argue: Don't Do That!
      B-) If you need the actual specializations of the Bar subclasses as
    in my diagram, then the individual relationships need to be instantiated
    and directly navigated rather than using barBase superclass access.]

    Note, though, that doesn't preclude each Foo implementation from
    invoking different responsibilities or providing different parametric
    data to barBase through that interface:

    fooDerive1::DoStuffWithBarBase
    {
         ...
         this->mybar->doIt(15)
         ...
    }

    fooDerive2::DoStuffWithBarBase
    {
         ...
         this->mybar->doIt(87)
         this->myBar->doSomethingElse(...)
         ...
    }

    >
    > However, in your example, we don't necessarily need
    > fooDeriveN::DoStuffWithBarBase() at all because we have a Bar* in Foo*.
    > Assuming that Bar is an abstract class which defines all the public
    > methods we'll need (ie the barDerivedN classes don't expose any more
    > functionality) then we can do everything we need from this Bar*.

    To clarify, I was assuming DoStuffWithBarBase implemented other Foo
    semantics and sending a message to Bar was peripheral to the semantics.
      That is, the name was just a convenience of the example.

    >
    >
    >>DoStuffWithBarBase now navigates to the Bar with confidence that it
    >>will do the right thing simply because it is at the end of the
    >>relationship. The rules for instantiating the relationship are
    >>encapsulated in createFooDeriveN or whoever creates Foos. Note that
    >>to invoke your Foo constructor, whoever does that has to know exactly
    >>the same things to provide the initializer list.
    >>
    >>This may seem like a trivial difference in encoding the constructor
    >>code, but it actually represents a more robust approach. That's
    >>because Foo doesn't need to know anything about the relationship
    >>rules or even what sort of Bar is on the other end of the
    >>relationship. One way that is manifested is that the Foo code only
    >>needs the Bar reference. [It also reduces the complexity of the
    >>constructor so that you safety problem goes away. B-)]
    >>
    >>A more important benefit is that if things change so that Bar is
    >>created separately for some reasons (e.g., because other objects need
    >>to access it or one decides one can optimize size by eliminating
    >>redundant embedded Bar instances or whatever) it is highly unlikely
    >>that the Foo constructor will have to change nor any other internals
    >>of Foo.
    >
    >
    > I don't think it would be possible to safely uncouple things quite that
    > much via this technique. If there's a Bar* in the Foo object, there's
    > always the risk that it might not be set, and that someone will then
    > try to access it. You could set Bar* myBar to zero in the constructor,
    > and then test for zero before you did anything that accessed the
    > pointer.
    >
    > The advantage of the C++ reference is that it can't be unset: it always
    > must "point" to something.

    Now we have a real OOA/D issue. B-) In OOA/D one treats the rules and
    policies for object and relationship instantiation (object
    participation) as logically distinct from the rules and policies of
    relationship navigation (message passing for collaborations between
    objects). That is, one separates the concerns of instantiation and
    encapsulates them, often in a dedicated object like the GoF factory
    patterns.

    An OOA/D relationship is either conditional or not. If it is
    conditional one can't implement it with a reference. If it is
    unconditional, then it /must/ be instantiated within the scope that
    where the participating objects are created. [Usually that is a single
    method scope. If it is spread over multiple methods, then the developer
    has the responsibility of explicitly guaranteeing referential integrity,
    which tends to be a pain. So most developers look for ways to
    encapsulate in a single method.]

    The point here is that in OOA/D instantiating unconditional
    relationships and object instantiation are inextricably linked to the
    point of being an idiom. Explicitly encapsulating those rules and
    policies ensures that the developer thinks about them. In those rare
    cases where the relationship can't be instantiated in the same method
    scope as the participants, that is painfully obvious and the developer
    realizes some additional care is required. Therefore the concern that
    leads to using references really isn't a concern because referential
    integrity should have already been worked out in the OOA/D.

    I would also point out that encapsulation of instantiation in OOA/D has
    additional benefits. Typically relationships are navigated many times
    from different contexts compared to their instantiation. Therefore
    encapsulating instantiation supports a form of one-fact-one-place to
    eliminate redundancy. For example, in OOA/D it is considered a very bad
    practice to pass object references in message data packets during
    collaboration (other than a setter to instantiate the referential
    attribute). The relationship is between the passed object and the
    receiver, yet the message sender must know which object the message
    receiver must collaborate with. The rules and policies for that are
    usually not related to the rules and policies of collaboration, so one
    has trashed the sender's cohesion. In addition, selecting the right
    object must be done in every collaboration context for every navigation
    of the relationship.

    A final note. Consider the situation where a relationship is
    unconditional but the participants are swapped dynamically during the
    execution. This is quite common when applying parametric polymorphism
    in the form of specification objects and dynamically assigned
    relationships. In that case referential integrity depends upon the
    developer ensuring the participants are swapped _at the right time_. In
    that case it doesn't make any difference whether one uses pointers or
    references because the referential attribute is always "set".

    The OOA/D practice of encapsulation ensures that this situation is
    treated in exactly the same way as non-dynamic instantiation. That is,
    the instantiation is still done in one place; one just has to send a
    message to whoever does that whenever the context changes. The place
    where the context change prevails doesn't need to know anything about
    the relationship dynamics; all it needs to do is announce that the
    context has changed. So one also gets better logical decoupling.
    However, that consistency and decoupling is ultimately driven by the
    OOA/D paradigm of encapsulating instantiation.

    [BTW, it is considered good OOA/D practice to eliminate conditionality
    in relationships wherever feasible in the OOA/D, even at the cost of
    adding additional classes. So one ends up in the same place as using
    references. However, the reasons are quite different. Eliminating
    conditionality enforces business rules and policies in terms of static
    structure rather than the dynamic description. That reduces executable
    code size and, consequently, improves reliability.]

    >
    >
    >>If you look at the way full UML code generators work, they invariably
    >>implement relationship navigation with pointers and collections of
    >>pointers because that allows very generic, aspect-like implementation
    >>that does not depend upon the class semantics at all. (They even use
    >>naming conventions that reflect relationship discriminators.) Among
    >>other things that allows the code generator to generate code for one
    >>class without even "looking" at the head files for other classes
    >>involved in the navigation.
    >
    >
    > I've not used UML very much as yet.

    It's probably time to learn. B-)) In a conference keynote address
    around '01 Jacobson predicted that in a decade writing 3GL code would be
    as rare as writing Assembly is today. (This is somewhat ironic since
    Jacobson and the other Amigos engaged Mellor in a series of conference
    debates in the mid-'90s where he defended the position that translation
    was not feasible.) I don't see it happening quite that fast, but it is
    an inevitable automation of the computing space.

    Translation technology has been around since the early '80s but the tool
    engineering problems weren't fully overcome until the late '90s. Now
    that UML has an action semantic meta-model it is a true general purpose
    4GL for functional requirements. With the MDA initiative we have
    standardization for the tools that automate the resolution of
    nonfunctional requirements in the computing space. In addition, the
    major software houses (IBM, CA, Mentor, etc.) have been buying up
    translation vendors to establish strategic positions (Pathfinder and
    Kennedy-Carter are the only remaining independent old-timers). There
    are also new MDA tools popping up on a monthly basis now. IOW, the
    Early Adopter Stage is over and the software development paradigm is
    shifting.

    >
    >
    >>That abstraction and separation of
    >>concerns is not possible in your reference approach because Foo needs
    >>to know the specific Bar subclass type even though it actually
    >>accesses through the Bar superclass.
    >
    >
    > I don't think it does. fooBase is implemented strictly in terms of
    > barBase. Or am I missing something here?

    But you had to have a specific barDerived reference in hand to which the
    barBase reference was assigned. Providing that in the fooDerived
    subclass creates the explicit coupling in the implementation. In my
    example there was no declaration of barDerived /anywhere/ in Foo or its
    derived classes.

    *************
    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
    blog (under constr): http://pathfinderpeople.blogs.com/hslahman
    (888)-OOA-PATH


  • Next message: H. S. Lahman: "Re: For Nimmi Srivastav"

    Relevant Pages

    • Re: 7.0 wishlist?
      ... The "auto-implement" is intended mainly for the odd situation where an existing class you can't edit fits some interface and you're willing to take responsibility for it if it turns out not to actually adhere to the contract, and try using it where that interface type is expected. ... If reference declarations started showing up with the odd asterisk, bang, or other punctuation mark on it, but never primitive declarations, people would probably be able to guess what was going on, on the basis of "what other binary flag might be set on references but not primitives and would be really useful besides can be/cannot be null?" ... the compiler cannot prove by static analysis that the RHS isn't null might be a good idea. ... Object foo, bar; ...
      (comp.lang.java.programmer)
    • Re: 7.0 wishlist?
      ... This is a slight modifier that would be very common on reference declarations, so it would be good to economize on typing or visual clutter where it was used. ... which has the enum constants actually singleton instances of same-named subclasses. ... enum Foo ... The point being to allow to publish an interface ...
      (comp.lang.java.programmer)
    • Re: instanceof
      ... events provide messaging in an asynchronous environment. ... That's why OOA/D assumes all attributes are accessed via synchronous relationship navigation on an as-needed basis by the methods that actually use the data. ... Instantiation of objects and relationships is about Who but navigation of relationships for collaborations is about When and What. ... seems to be 2 schools of thought about whether such a foo object should be able to draw itself or just provide the state to enable another to adopt this role. ...
      (comp.object)
    • Re: pass by Reference/value ???
      ... > void foo ... this would be called "passing by reference". ... foo receives a local copy of s and any changes that it makes to s only ... Since s is a pointer, a variable storing the address of s is a pointer to ...
      (comp.lang.cpp)
    • Re: How to undo a bless on a GLOB
      ... The reference was blessed to something: ... bless $foo, 'Something'; ... Can you bless the glob before ... That could be a bug in POE. ...
      (comp.lang.perl.misc)