Re: implementing roles in OOP......
From: Mark Nicholls (Nicholls.Mark_at_mtvne.com)
Date: 04/21/04
- Next message: EventHelix.com: "Re: Is UML good enough for packet processing and forwarding design?"
- Previous message: Robert C. Martin: "Re: Aggregation vs composition"
- In reply to: H. S. Lahman: "Re: implementing roles in OOP......"
- Next in thread: H. S. Lahman: "Re: implementing roles in OOP......"
- Reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Date: 21 Apr 2004 05:38:21 -0700
>
> The key element of the mindset is to avoid thinking procedurally (i.e.,
> starting with a preconceived sequences of operations and then design
> objects and behaviors around that sequence). As I indicated in the Part
> II response, everything should be driven by abstracting the problem
> space. Only when that is finished does one worry about connecting the
> dots in the overall solution sequence.
I think I do, my draft interfaces define what sort of thing I would
expect to able to do to an abstraction - order comes last.
>
> However, if one separates the concerns of relationship instantiation
> from relationship navigation, one no longer cares about verification
> _when navigating_. A model simulator or an OOPL's type system will
> still get very upset when the relationship is not correctly instantiated
> and one tries to navigate it. However, that verification is of the
> instantiation, not the navigation.
I think I need to see this in 3GL world.
>
> This seems like a pretty arcane distinction but it is still important to
> how one /thinks/ about construction. When navigating, one doesn't care
> about getting the right properties because that is the responsibility of
> whoever did the instantiation. One is completely confident that whoever
> receives the message will be able to process it. That is what allows
> one to think of messages as pure announcements. It removes any concern
> about who is at the other end of the line and what they will do in
> response. So there is no temptation to be dependent upon particular
> properties being there when constructing the message-sending method.
>
>
> >>>>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-))
> >
> >
> > AHA - hard data eh!
> >
> > That puts the cat among the pigeons, before I start let me say I would
> > expect that the two versions should be similar if they do the same
> > thing - I claim that visitor should have slightly less dangerous code
> > because the if is encapsulated (though now my head is above the
> > parapet).
> >
> > We need to be careful about how we set up the 'experiment' and also
> > note at the beginning that I have a position and so do you, so neither
> > of us are going to be objective - but lets have a go.....
> >
> > My 1st observation is that your code does NOT do what mine would - for
> > me given any element I can assess how many Wheels, nuts etc there are.
>
> Huh? The way Visitor works to, say, enumerate the Wheels in the
> structure one would have to visit every element.
Every element starting with the element you have, there is no
assumption that you need to have the root element i.e. Car.
Your solution (I think at the moment) counts the number of elements
that are directly held in the componentlist.
i.e if
ComponentList A;
ComponentList B;
A.Add(&B);
Tyre C;
B.Add(&C);
how many tryres are then in A?
I think (unless I've got it wrong), A will say 0.
I think it should say 1.
for it to say 1, B has to be able to tell A - am I talking rubbish? (a
question).
>
> > My 2nd is that your structure is NOT a composite structure, it is a
> > list - I am able to effectively put a ComponentList in a ComponentList
> > and it all works out of the box.
>
> Yes it is. Note the comments, "// do add stuff" and "// update comp via
> traversal algorithm". That's where the navigation code lives for the
> Composite (which can be navigated fully at the Component superclass
> level). That code would understand subassemblies (e.g., where to put a
> Wheel).
Can you add ComponentLists to ComponentLists? (a question).
>
> >
> > For you to do the same I assume you would have to implement some sort
> > of double linkage so that adding a nut to a door would trickle back up
> > the chain - multos nastios codeos.
>
> No. All I need is the Composite relationships to navigate, starting at
> the root assembly related 1:1 to ComponentList. [You need exactly the
> same thing. As I comment below, your code will not correctly enumerate
> unless the AcceptVisitor call is made against the root component in the
> hierarchy.]
Yes it will, it will tell you how many wheels a car has if it is given
a car.
It will tell you how many wheels a nut has, if it's passed a nut.
I don't see anything special about car - it happens to be the root,
but if I have a car transporter and add cars to it, then car would not
be the root.
>
> >
> > Now to look at your metrics......
> >
> > I'm not convinced by some of them.
> > "Number of classes" - are you claiming that the maintainablity of code
> > is dependent on number of classes ? The least mainanable code in my
> > experience (!!!) is usually large bloated god classes.
> > Again I'm not convinced by - "Number of methods" as a good indicator
> > of maintanability - but again based on experience only.
>
> ComponentList is hardly a god class; it has very limited collection
> semantics.
OK, it's higher up the deity stakes than my composite, because it
knows about counting and retrieval.
>
> Number of methods is an indicator of overall complexity. Each method
> represents access to a logically indivisible behavior responsibility.
> Therefore more methods => more identifiable behaviors => more complexity.
I have multple seperate function with no parameters - you have a
single parametised function that returns values based on a lookup.
I'm not sure whether we are comparing cows with potatoes - I can argue
that your function is logically divisible - because I have divided it.
and
is
int Increment(int)
{
.....
}
int Decrement(int)
{
}
int Square()
{
}
etc etc
more complex than
int ApplyOperator(int,operatorflag)
{
.... implement via table lookup
}
I feel this is sort of thing your code does - is it more complex ?
(not convinced).
> >
> > We need to be careful about lines like;
> > (*count_table [comp->type])++; // update right count
> >
> > does this really constitute a single line of code - hmmmmm - not to me
> > - I would say probably 3 things are happening here (many people would
> > put it on 3 lines + 1 for a variable declaration).
>
> It's one line and one executable statement in C++. And its not doing
> anything sneaky like an operator overload; it is just a vanilla C++
> statement.
Again I don't like your metric.
i.e. is
(*count_table [comp->type])++;
less complex.
int x = *count_table [comp->type];
int x=x+1;
*count_table [comp->type] = x;
it would appear so - but is it less maintainable - if it is, then is;
count_table [(*count_table [comp->type])++] = count_table
[(*count_table [comp->type]++)--] - 1;
more maintainable than pulling it to bits.
I would prefer it pulled to bits.
but your metric would imply that that would make more lines => more
complexity => less maintainable.
>
> >
> > We need to be careful about your
> >
> > int* count_table [COMPONENT_TYPE_COUNT] = {
> > &this.wheelCount,
> > &this.tireCount,
> > &this.lugNutCount.....
> >
> > They are not executable but they are certainly a sign of naked
> > explicit complexity in your code (which I think the visitor hides).
>
> The complexity lies in the fact that to add a new enumeration I only
> have to add a single line in a table. To do a true GoF Visitor you have
> to add a new Visitor subclass and that subclass must have a method
> implementation for every existing Element. As it is, you provide a
> method for each operation plus the corresponding Accept implementation.
>
> I don't agree that is hidden in Visitor at all; it is explicit Visitor
> code that must be written. It is also code that any maintainer is going
> to have to understand when contemplated the Element.Accept implementation.
your table is order specifc, accidentally delete a line from you array
and LOTS of code breaks - mine is dependent on the explicit name of a
method - I prefer the latter.
accidentally delete the 1st entry (and insert one at the end when the
compiler warns you), ALL your code breaks - that's a lot of accidental
coupling between the behaviour of your code and the order of your
array.
I have NO array, if I mess up 1 accept function it is decoupled from
every other one - that's good.
>
> >
> > I think you need to include the code that sets "comp.type = ???" in
> > the element class - I'm not sure you have.
>
> That's in the "// do add stuff". That is necessary for the Composite
> pattern and its traversal; it has nothing to do the Visitor vs. my
> alternative.
>
> >
> > I think you have assumed that I would implement a visitor to enumerate
> > each type, I would probably implement an EnumerationVisitor that would
> > enumerate all element types.
>
> Then I misunderstood what you said earlier in this thread about adding a
> visitor for each enumeration as they were needed.
Just as you have (validly) bent the interpretation of indivisible
method to parametise your GetCount, I have bent the definition of
operator to mean Count(type) rather than CountWheel.
If we wish to compare code - it's best if we do roughly the same
thing.
>
> >
> > I don't mind "Number of executable statements to execute selection" -
> > and unsuprisingly these are the same.
> > I don't mind "Number of lines to add a new enumeration" if we only
> > count unenforced lines i.e. not declaration enforced by interface
> > association but entries in arrays, array declaration (if not
> > explicitly enforced in some manner) and executable code.
>
> The issue here is maintainability. Which is better: adding one table
> entry or an entire Visitor subclass? I'll take the table entry any day.
> B-))
hmmmm,
1 entry to table versus 1 method to visitor.
errrrm answer.....
1 method to visitor - there is no less (none I can see) accidental
coupling.
>
> BTW, I don't see the superiority of my enumeration solution as being
> even remotely debatable for maintainability, reliability, or
> performance. It just makes no sense at all to implement such things
> with Visitor. However, that's less about Visitor than it is about a
> knowledge implementation vs. a behavior implementation; knowledge is
> always better when one can do it reasonably.
I believe you have data to back up the statement, I'm not sure about
your interpretation of that data, in the same way I don't believe
maintainability has a direct relationship number of lines,
irrespective of the contents of those lines of code - otherwise we'd
all be removing our test code and assertions.
>
> > I also don't mind "Number of lines to add a new selection (excluding
> > traversal method)" with the above proviso's. Why are we excluding
> > traversal?
>
> Because traversal is implicit in Composite. Somebody, somewhere has to
> traverse the structure. I think it is better to do that in
> ComponentList because it encapsulates that responsibility -- but
> somebody has to do it so those code lines are a wash for all the
> alternatives.
>
> >
> > my solution would look something like.... (I'll make all components
> > potentially composite).
> >
> > interface ICarComponent
> > {
> > void AddSubComponent(ICarComponent);
> > IEnumerator GetSubComponents();
> > void AcceptVisitor(ICarComponentVisitor)
> > }
>
> What's an IEnumerator? I ask because it seems to me it must reflect the
> Composite somehow (i.e., it can't be a vanilla collection of Components;
> it must distinguish somehow between CCompositeComponent and CCar, etc.).
> [Hmmm. That gets answered below because you are doing a straight
> hierarchical list rather than Composite.]
IEnumerator is an interface defined by my environment, we can put,
ICarComponent[] it makes little difference.
>
> I already have a problem with composition here. B-) AddSubComponent is
> an instance method. That means that if a client wants to add a lug nut
> to a wheel, it must navigate the structure to find the right Wheel and
> then invoke AddSubComponent for that Wheel instance.
Yep, that's because our solution do different things.
I am trying to model the real world more closely than you (an
observation).
If I want to add a nut to a wheel in the real world, 1st I go to car,
then 1 traverse until I find wheel and then I add nut.
It's more complex. but that because it does more (an observation).
I can say that a nut is part of the wheel, you can't (an
observation)..
>
> This breaks both encapsulation and cohesion because the client must
> understand the traversing algorithm plus other rules and policies for
> adding elements to the structure (e.g., a Wheel must have exactly N
> LugNuts). To see why that is bad, imagine an application where ClientA
> adds Wheel assemblies (Wheels, LugNuts), ClientB adds drive train
> assemblies (Engine, Transmission, etc.), and ClientC adds body
> assemblies (Fender, Door, DoorHandle).
>
> The problem is that ClientA, ClientB, and ClientC must all understand
> the traversal algorithm. They must all execute it as well. So if one
> changes the traversal algorithm, say from your simple hierarchy to a
> true Composite, then each of those Clients will have to be modified.
No, I would hopefully encapsulate the traversal algorithm (of the tree
structure which is different from simply traversing the children) -
it's an orthogonal concern to adding nuts i.e. I need to find the
correct wheel - I don't care how.
There may be cases in which different clients want to traverse in
different ways!
>
> It is to avoid exactly this sort of problem that I encapsulated the
> traversal algorithm in a separate class, ComponentList. Now any client
> that wants to add, remove, or find a particular CarComponent asks
> ComponentList regardless of what its context is. More important, that
> client is completely indifferent to the actual structure for organizing
> CarComponents and traversal redundancy has been eliminated.
>
If you used simple recursive tree traversal (i.e. depth 1st) in your
componentlist, then you have embedded it in the componentlist, this
may or may not be sensible.
I actually can't see it in your code, because I don't believe your
structure is recursive.
> [You mentioned above that you can execute your behaviors at any member
> of the structure. That is not true as you have written the code, but
> I'll have more to say about that below. I will say you could do that
> here but it would require that AddSubComponent navigate parent
> relationships to get to the root CarComponent and then traverse the tree
> downwards using VisitChildren. Since the decision for where to add a
> given sort of component will vary with the component type, that
> traversal will have to be included in CarComponent's implementation of
> AddSubComponent -- which is a lot of redundancy.]
>
> >
> > interface ICarComponentVisitor
> > {
> > void VisitCar(ICarComponent);
> > void VisitWheel(ICarComponent);
> > void VisitTyre(ICarComponent);
> > void VisitLugNuts(ICarComponent);
> > }
> >
> > private CCompositeComponent : ICarComponent
> > {
> > ArrayList marrChildren = new ArrayList();
> >
> > void AddSubComponent(ICarComponent)
> > {
> > marrChildren,Add(carcomponent);
> > }
>
> You create a new marrChildren when each component is added?!?
>
> >
> > IEnumerator GetSubComponents()
> > {
> > return marrChildren.GetEnumerator();
> > }
> >
> > void AcceptVisitor(ICarComponentVisitor)
> > {
> > throw exception.
> > }
> > }
> >
> > class CCar : CCompositeComponent
> > {
> > void AcceptVisitor(ICarComponentVisitor)
> > {
> > CarComponentVisitor.VisitCar(this);
> > }
> > }
> >
> > class CWheel : CCompositeComponentt
> > {
> > void AcceptVisitor(ICarComponentVisitor)
> > {
> > CarComponentVisitor.VisitWheel(this);
> > }
> > }
> >
> > class CTyre : CCompositeComponentt
> > {
> > void AcceptVisitor(ICarComponentVisitor)
> > {
> > CarComponentVisitor.VisitTyre(this);
> > }
> > }
> >
> > class CLugNuts : CCompositeComponentt
> > {
> > void AcceptVisitor(ICarComponentVisitor)
> > {
> > CarComponentVisitor.VisitLugNuts(this);
> > }
> > }
>
> This is not a Composite pattern. In Composite one would have
>
> [CComponent] -------------------------------+
> A |
> | |
> +-------+-------+----+----+-------+ |
> | | | | | |
> [CCar] [CWheel] [Ctyre] [CLugNut] [CCompositeComponent] ----+
>
No. they both are !
I have defined all components to be composite role.
p165 Collaborations GoF
If the recipient is a composite, then it usually forwards requests to
it's child components, ****possibly performing additional operations
before and/or after forwarding****
p166 Consequences
newly defined ***composite*** or leaf classes work automatically.
i.e. the composite can be a 1st class component, there can be 1 or
more of them - I have no leaves - so what? There is nothing to say I
have to
p167 defining child management operations - goes on at length about
potentially defining them at the root - that's what I've done - KISS.
>
> That is, a Composite is an assembly of some sort that is a peer with the
> non-assembly components. So CCar, CWheel, etc. can't inherit from
> CCompositeComponent. More important here is that /only/ a
> CCompositeComponent can have descendants.
>
> What you have actually implemented is a simple hierarchical list:
>
> +------------------------------+
> | |
> | parent of |
> | 0..* |
> [CCompositeComponent] ------------------+
> A 0..1 child of
> |
> +--------+-----+-----+----...
> | | |
> [CCar] [CWheel] [CTyre]
>
Yep and that is a type of GoF composite.
> >
> >
> > class CEnumeratingCarVisitor : ICarComponentVisitor
> > {
> > public long mlonCars = 0;
> > public long mlonWheels = 0;
> > public long mlonTyres = 0;
> > public long mlonLugNuts = 0;
> >
> > private void VisitChildren(ICarComponent)
> > {
> > foreach(Component in CarComponent.GetSubComponents)
> > {
> > Component.AcceptVisitor(this);
> > }
> > }
>
> Where does CarComponent come from? B-)) (Big grin because I think the
> answer to that is going to cause you some problems.)
Sorry, my lazyness meant that I only declare the parameters type, the
variable names are missing.
the Component variable is the enumerated menmbers of
CarComponent.GetSubComponents.
(I'm waiting for the problems).
>
> Note that your foreach is doing an implicit test on the conditionality
> of the relationship (i.e., the loop is a no-op if the CarComponent in
> hand has no children). This is equivalent to calling GetComposite() and
> testing the result in the GoF example.
Except there is no test - look less code = less bugs
:-)
Why treat a element with no children different from one with ? There
seems no reason to make it a special case.
>
> >
> > void VisitCar(ICarComponent)
> > {
> > mlonCars++;
> > VisitChildren(CarComponent);
> > }
> >
> > void VisitWheel(ICarComponent)
> > {
> > mlonWheels++;
> > VisitChildren(CarComponent);
> > }
> >
> > void VisitTyre(ICarComponent)
> > {
> > mlonTyres++;
> > VisitChildren(CarComponent);
> > }
> >
> > void VisitLugNuts(ICarComponent)
> > {
> > mlonLugNuts++;
> > VisitChildren(CarComponent);
> > }
> > }
>
> I assume a typo here and the argument to VisitChildren should be
> ICarComponent. If not, where does CarComponent come from?
sorry, Lazyness
ICarComponent is an interface and the variable is implicitly
CarComponent.
i.e. it should read
void VisitLugNuts(ICarComponent CarComponent)
{
mlonLugNuts++;
VisitChildren(CarComponent);
}
>
> Note that you said above that you could enumerate from any Element, but
> that is not possible from this code. The VisitChildren is a simple
> depth-first traversal that must start from the topmost Component in the
> tree. If it starts from an arbitrary point in the tree the enumeration
> will be incorrect if that point is below or sibling to one of the
> components one is counting. Unlike AddSubComponent, there is no way (as
> written) to fix this with a hidden traversal back up to the top of the
> hierarchy.
Our code do different things - mine is more complex and more powerful
- I agree.
>
> Also note that this is not a Visitor pattern. Each subclass of Visitor
> encapsulates a different operation but you are combining operations in
> CEnumerationVisitor in the same way I combined them in ComponentList.
> What you have actually done is much closer to my ComponentList. In so
> doing you have eliminated the need for a bunch of no-op methods, just as
> I did.
Semantics.
I have defined my operator to be "count the number of elements of each
type" rather than "count the number of elements of type wheel"
You have defined it as "get me the number of element of type X".
>
> However, there are some problems here. CEnumeratingCarVisitor is not
> really a problem space entity. It is just a class facade wrapped around
> a set of functions. IOW, you are organizing a function library. The
> acid test is to ask (without prompting) a problem domain expert what he
> thinks an Enumerating Car Visitor is and what a Component List is.
> You'll get a reasonably accurate description for the Component List but
> good luck on Enumerating Car Visitor.
I don't buy this - my conceptual model is not bounded by "Domain
Experts".
I need them to help me shape my conceptual model, their model needs to
map INTO my model, but my model does not need to map INTO their.
i.e. mine is a generalisation of theirs.
I abstract problem domain abstractions out and attempt to generalise
them, I can ask him what a ComponentEnumerationProcess would look
like.
I am encapsulating a process in a class - so what - it encapsulates an
operation - it no supprise it looks 'functional' - I make no apology
for it.
>
> Another problem lies in collaboration. CEnumeratingCarVisitor now owns
> the responsibility for knowing the counts. So anyone who needs one of
> those counts is going to ask CEnumeratingCarVisitor for it. But how do
> those counts gets initialized? You would have someone invoke
> CCompositeComponent.AcceptVisitor. IOW, they would be collaborating
> with CCompositeComponent rather than CEnumeratingCarVisitor.
>
Everyone who want to know how may components there are of a each type
inside a given component, would indeed have to set off the visitor to
do it.
My code does something different to yours.
> To me that doesn't make sense. If CEnumeratingCarVisitor owns the
> responsibility for knowing the values, it should also own the
> responsibility for maintaining them. So if someone wants those values
> changed, they should talk to CEnumeratingCarVisitor. IOW,
> CEnumeratingCarVisitor should have a UpdateEnumerationCounts() method.
>
???
The counts are implied by the structure of any given component. I can
as you have done, store and maintain them explicitly OR I can provide
behaviour that will derive them.
BUT for your code to do what my code does, it would have to be much
more complicated.
> However, that method could simply navigate the structure and count what
> was there -- exactly like my ComponentList. There would be no need for
> callbacks in CCompositeComponent. That would provide decoupling because
> CCompositeComponent would not even know it was being counted much less
> that CEnumeratingCarVisitor was doing the counting.
>
> Another problem with your scheme of collaboration is incremental updates
> of components in the structure. AddSubComponent is an orthogonal
> responsibility in another object. If a Wheel is added, how do the
> counts in CEnumeratingCarVisitor get updated so that the mlonWheels
> value is accurate when it is accessed by someone else? In your code
> AddSubComponent would need to invoke CEnumeratingCarVisitor methods like
> IncrementWheelCount(), IncrementTyreCount(), etc. _for the correct type
> of component_.
>
They don't, every time I want to know how many component there are I
have to derive that information with a visitor.
It's different - not wrong, just different.
> Alternatively, if one encapsulates all the collection activities (add,
> remove, find, count members, etc.) in a single class like ComponentList
> one gets synchronization quite naturally and it is hidden from
> collaborating objects. One also gets excellent cohesion because all the
> responsibilities for managing the aggregate are in one place. If
> somebody wants something related to the aggregate itself, they talk to
> ComponentList. If they want something related to a particular
> component, they talk directly to the Component (after ComponentList
> finds it for them).
>
> >
> > Hmmmm, looking at that I thought "oh no, that's loads more code, I'm
> > going to get roasted..." but if you go through it the only lines
> > adding complexity i.e. NOT CEnumeratingCarVisitor::VisitTyre becuase
> > that is just a enforced restatement of the declaration in the
> > interface and comes as a consequence of the concrete visitor declaring
> > it's intention to implement the interface.
> >
> > My downfall in terms of absolute lines executable code is the
> > duplication of
> >
> > mlon<ComponentCount>++;
> > VisitChildren(CarComponent);
> >
> > in the visit methods (I could have implemented something like your
> > array structure of counts - and would do so if N were large (say >10))
> >
> > In strict GoF terms for every addition of a new subclass would require
> > 1 class declaration that declares the intention to implement a new
> > Accept.
> > 1 line for the VisitXXX() declaration.
> > 1 line for the Visitor interface
> > (for small N) 2 lines in the visitor Visit + 1 line for the
> > declaration of mlon<Component>
> >
> > You would have
> > 1 new line somewhere that defines a new 'Type' say Axle
> > 1 new line somewhere that create the relationship from a CAxle to have
> > type = enuAxle, I assume not in the client code.
> > 1 change to COMPONENT_TYPE_COUNT.
> > 1 new entry in the array count_table.
> > 1 new entry in the func_table
> >
> > AHHHH you have 5 and I have 6 !!!!!
>
> First, you didn't do a GoF Composition. B-) But that really isn't the
> issue.
I think I've addressed that.
>
> Second, you are counting lines that would have to be there anyway. They
> are necessary to construct the aggregate's relationships. More to the
> point, they are intrinsic to Composite and don't really have anything to
> do with the Visitor issue. That's why I didn't bother with them and
> just used the "// do add stuff" placeholder. (I also noted that the
> code for my two placeholders will be found in both versions.)
>
> There is no way to add a LugNut to the structure at the proper place
> (i.e., as a child of a Tyre without enough LugNuts) without
> understanding the types of components and the tree structure. By making
> AddSubComponent an instance method you have simply shifted the code for
> doing that sort of checking into the client. To find the right place to
> invoke AddSubComponent the client is going to have to invoke isWheel()
> and getChildCount() methods from CCarComponent.
It could use a visitor to find the wheels !!!!!
It needs to know which visitor and which car.
I don't understand how your code encodes the knowledge of a nut being
on a wheel - I don't think it does.
>
> [Actually, I shouldn't say "no way". You could use Visitor for
> AddSubComponent. However, after including all the gloppy stuff for that
> on type of the enumeration stuff things would be pretty near
> indecipherable. Frankly, I already found it obtuse because I had to
> keep flitting back and forth between classes and interface definitions
> to understand what was going on. Note that my solution had one
> interface and one class implementation that were relevant while your
> solution one jumps around among two interfaces and six classes to figure
> out what's going on.]
Seperating the concerns does this - I accept.
Is it more extensible ? yes
Is it more powerful ? yes
Is it conceptually simpler ? (I actually would argue I only have 2
conceptual roles + client) - possibly
Is it more code ? yes
Is it more executably code (+lookups) ? at the beginning yes -
incrementally, similar.
Is it more maintianable ? I think possibly yes (on the grounds of
incidental coupling).
Is it harder to understand ? yes
Does it do the same as is comparable is a simple manner? no.
>
> >
> > The problem is that I still think my code is slightly 'safer' simply
> > because my coupling is explicit and not based on things like order - I
> > really, really don't like the manual maintenance to the arrays and the
> > fact they are order sensitive - it really is the sort of thing that
> > hides bugs, because the order of the 'types' has to be the same as the
> > order of the array elements - for me this comes for free - I don't
> > maintain them, the compiler does - it is not part of intrinsic
> > complexity and thus disappears in a puff of OO magic smoke.
>
> Data Structures are good in the same sense that unconditional
> relationships are good. They enforce problem space rules in the static
> structure rather than in dynamic code. The tables I used are
> technically ordered but that really doesn't matter. The index
> represents identity and that is defined orthogonally.
>
The index in the declaration indicates the size of the array (I don't
need this).
More importantly the order of the array is coupled to the enumeration
of type.
You can get around this and do what the compiler does for me but you
need to add
func_table[COMPONENT_TYPE_WHEEL] = Component::getWheels;
func_table[COMPONENT_TYPE_TYRE] = Component::getTyres;
func_table[COMPONENT_TYPE_LUGNUTS = Component::getLugnuts;
...
etc
...
but suddenly your number of lines of code is beggining to grow
and you said that more lines = more bugs
I actually prefer the above because even though the number of lines of
code is increasing there is no incidental coupling and the
relationship is explicit.
I still think its an exposed v-table and would prefer the compiler to
maintain it.
> Since the '70s 3GLs have provided lots of ways for defining identity in
> one place (e.g., C's #define) so that it can be used consistently and
> with developer-friendly mnemonics. Look-up tables are actually a very
> good way to enforce consistency once identity has been properly defined.
>
but exhibut incidental coupling unless initialised as I have
demonstated.
> However, the real advantage of look-up tables lies in the fact that they
> are static structures rather than dynamic structures. As a general rule
> static structures are more stable over time and are much easier to modify.
>
The stability is a function of the inherent complexity, if the
inherent complexity changes then both my Accept and you lookuo
changes, if it doesn't neither do.
To me your statement should read, complexity that can be described in
static tables is more stable over time, the expression of that
complexity is irrelevant to the stabilty.
The question of maintainability is open to question - I would claim
you encapsulation of this complexity is classic SA/SP and not OO and
your hard data would imply that it is thus probably not as
maintainable as an OO solution - whether visitor is a sensible OO
solution is another matter.
> Another advantage of look-up tables is that they can be constructed from
> external configuration data. Then one can change the application
> behavior without touching the code itself. That wasn't the case in my
> example because I didn't supply the Factory to do so. But I could have
> (even to initialize func_table, though it would be pretty ugly at the
> OOP level).
It would.
It's ugly because it makes something in you (your OOP experience
possibly) whisper
"ooooo, that looks like it's going to go wrong"
"ooooo, that looks like it's going to go wrong"
"ooooo, that looks like it's going to go wrong"
"ooooo, that looks like it's going to go wrong"
"ooooo, that looks like it's going to go wrong"
>
> We have been around on this before, but the compiler is going to provide
> an error or warning somewhere in my solution for every situation where
> it would for your solution. I can't invoke methods that don't exist any
> more than you can.
you can put functions that satisfy the prototype but are not sensible.
I am constained by the interface.
1 error putting an entry in creates multiple errors in the execution.
1 error calling the wrong visit is 1 error.
If you create a new 'type' but don't update your COMPONENT_TYPE_COUNT
you'll get a run time error, I'll get a compile error.
>
> >>>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.
> >
> >
> > I agree, but, encapsulated auto managed v-tables should be prefered to
> > exposed manually managed - In fact if I didn't know better (which I
> > don't) I would say this is the Rumbaugh C style of writing the
> > solution!!! :-)
>
> But the look-up tables are encapsulated and hidden; they are a private
> implementation of ComponentList. In fact, that's pretty much the point.
> They are an efficient, easily maintained implementation encapsulated
> in one place.
>
They are encapsulated from the client code, but maintainability has a
lot to do with whats abstracted away from the programmer - and they're
lounging around in all their dreadful naked glory - saying "go on just
edit me, one slip and the whole app comes crashing down on your head,
go on, you know you want to".
>
> *************
> 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: EventHelix.com: "Re: Is UML good enough for packet processing and forwarding design?"
- Previous message: Robert C. Martin: "Re: Aggregation vs composition"
- In reply to: H. S. Lahman: "Re: implementing roles in OOP......"
- Next in thread: H. S. Lahman: "Re: implementing roles in OOP......"
- Reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Relevant Pages
|