Re: implementing roles in OOP......
From: H. S. Lahman (h.lahman_at_verizon.net)
Date: 04/01/04
- Previous message: Cy Coe: "Re: xp and simple design"
- Next in thread: Mark Nicholls: "Re: implementing roles in OOP......"
- Reply: Mark Nicholls: "Re: implementing roles in OOP......"
- Maybe reply: Mark Nicholls: "Re: implementing roles in OOP......"
- Maybe reply: Universe: "Re: implementing roles in OOP......"
- Maybe reply: Mark Nicholls: "Re: implementing roles in OOP......"
- Maybe reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Maybe reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Maybe reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Maybe reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Maybe reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Maybe reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
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
- Previous message: Cy Coe: "Re: xp and simple design"
- Next in thread: Mark Nicholls: "Re: implementing roles in OOP......"
- Reply: Mark Nicholls: "Re: implementing roles in OOP......"
- Maybe reply: Mark Nicholls: "Re: implementing roles in OOP......"
- Maybe reply: Universe: "Re: implementing roles in OOP......"
- Maybe reply: Mark Nicholls: "Re: implementing roles in OOP......"
- Maybe reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Maybe reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Maybe reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Maybe reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Maybe reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Maybe reply: H. S. Lahman: "Re: implementing roles in OOP......"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]