Re: implementing roles in OOP......
From: Mark Nicholls (Nicholls.Mark_at_mtvne.com)
Date: 04/19/04
- Next message: Philippe Ribet: "Re: OOP Language for OS Development"
- Previous message: Ioannis Vranos: "Re: OOP Language for OS Development"
- 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: 19 Apr 2004 03:13:30 -0700
>
>
> An OO object is supposed to be logically indivisible as an entity at a
> given level of abstraction. Yet an object does have multiple
> responsibilities. Each responsibility is also supposed to be logically
> indivisible. To be consistent with the notion of indivisibility at the
> entity level one relies on cohesion -- the individual responsibilities
> must be clearly related at the level of abstraction. IOW, they are
> reflections or aspects of the same concept. Otherwise one can
> demonstrate that the entity itself is not logically indivisible.
Now I get concerned (for me).
(I have just ordered the "roles, responsiblities and something else"
Wirf-Brock (!) book, so in a week or two I may either have a different
grasp of the subject or a better grasp of the concepts/language to
explain.
Role, responsility, type (interface), aspect and class.
Whats all that about and how do they relate?
I'm not sure I have a good feel for the difference between role and
responsility - in fact in my head they are the same and none of the
books I own mention them in any sort of well defined manner (yet) -
Booch, Rumbaugh, GoF.
If you say an object has multiple responsibilities, it implies to me
it has multiple roles (an observation).
Now I in my design process do abit of Analysis (i.e. cicles, arrows
and scribble on a bit of paper, scratch head, post messages to groups,
go to the pub, scratch head, arrows, scribble) , identifiy the
principle roles (responsibilities?) and their static relationships
(which sometimes is quite hard), map those to draft interfaces (hmmm
interfaces are part of my Analysis process - oh well).
At this stage Interfaces are good to me, they idenfify my principle
abstractions in pretty much a 1:1 basis, but note to me 1 interface =
1 role.
Nows the bit that does my head in, I need to identify classes that
implement these interfaces, and the relationships to tie these things
together, this usually results (at the OOP level) with a raft of
slightly more implementation specific interfaces and some concrete
classes that implement them BUT I still in my head think 1 interface =
1 role so if I have an interface that has a single method, which is
simply navigation to a ComponentList, I consider this to be a role !
I have (in theory, but almost in practive) NO class level methods and
if they exist its just an implementation optimisation - they do exist
really just not explicitly.
So with the Car thing - I would have a generic componentlist interface
and one or more classes that implements it, and a generic motorised
vehicle interface and one or more classes that implements it, Cars
role is to 'wire' these two roles together and provide navigation to
them, who decided which specific motorised vehicle or componentlist -
either the car itself or a factory (or builder).
I don't know how that relates to what you've said, apart from I am
confused by, on one hand an object having multiple responsibilities
but only having a single role.
>
> MI necessarily brings in different concepts because there are two
> different root superclasses with different properties that exist
> independently as entity abstractions. If those superclasses were
> logically indivisible from one another, they wouldn't be different
> classes. Similarly, each superclass represents a unique collection of
> properties. Because we normalize the Class Diagram none of those
> properties is shared. So there is no basis to infer that those
> properties are in any way related. Thus composition, whether through MI
> or otherwise, is merging different conceptual entities at a given level
> of abstraction, which makes the result of the merger not cohesive.
>
"So there is no basis to infer that those properties are in any way
related"
hmmm, they are related, but that relationship is explicit and
externalised.
hmmmm, cohesive.......
> <soapbox mode>
> Note the reliance on "at a given level of abstraction". In some cases a
> Car is abstracted as a monolithic entity. In others it is a collection
> of components, each with its own class. To avoid hierarchical
> decomposition OO needs exactly that sort of flexibility in abstraction
> indivisibility to avoid long dependency chains. However, any
> decomposition of abstractions must be driven by the problem space.
>
> I can decompose Car into DriveTrain, Wheels, Body, etc. but only if the
> problem space supports that. I can't decompose a Car abstraction into
> Bottles and Boogles because they are not identifiable in the problem
> space as Car components. That's obvious for something preposterous like
> 'boogle'. It is less obvious for -able properties acquired through
> inheritance and that is the source of a lot of bad OO abstraction.
>
> Problem space consistency is what makes the -able view of inheritance
> non-OO in many cases. A car isn't intrinsically persistable or
> displayable. One doesn't walk into a dealership and ask for a Green
> Displayable Honda Accord. Yet many developers and some infrastructures
> do exactly that in their code.
> </soapbox mode>
hmmmm.
>
> > I must admit to finding the next sections are hard to follow, it is
> > English but I'm not sure whether its the same English that I speak.
> >
> > I think in code (a failure on my part, I admit) but true.
>
> It is likely to be at least partly my fault. I have been a
> translationist so long that I take a lot of things for granted.
> Translation requires a very deterministic view of the computing space
> where one maps OOA/D artifacts into 3GL mechanisms as a matter of
> course. But to do that one has to think of 3GL mechanisms much like
> design patterns. IOW, as "cookbook" conversions. While they are
> "cookbook" conversions, that isn't always obvious unless one has done it
> several times.
>
> To overcome that you are going to have to be specific about what isn't
> clear so that I can clarify.
>
> >
> >
> >>>
> >>>>It does, indeed, clearly separate behaviors. However, collaboration is
> >>>>another story entirely. Now the Car thingee is all about managing the
> >>>>MotorizedVehicle set and the HasFourWheels set. That is nontrivial
> >>>>because of state variables -- a particular Car thingee needs to
> >>>>coordinate a specific MotorizedVehicle member with exactly the right
> >>>>HasFourWheels member.
> >
> >
> > What confuses about this example how would you get these things to
> > collabote i.e. in code
> >
> > // my abstractions/roles/aspects
> > interface IMotorizedVehicle
> > {
> > ...
> > }
> >
> > interface IHas4Wheels
> > {
> > ...
> > }
> >
> > I assume you wouldn't go....
> >
> > class CCar : IHas4Wheels,IMotorizedVehicle
> > {
> > implement both interfaces
> > }
> >
> > or
> >
> > interface ICar : IHas4Wheels,IMotorizedVehicle
> > {
> > }
> >
> > class CCar : ICar
> > {
> > // same as above really.
> > }
>
> You're right, none of the above. B-) But that is because you haven't
> described all the objects. You postulated different sets for Has4Wheels
> and MotorizedVehicle. Since the set members are all some notion of Car,
> then Car must comprise a superset. That implies:
>
> [Car]
> ICar
> A
> |
> +------------+----------------+
> | |
> [Has4Wheels] [Has3Wheels]
> I4Wheel I3Wheel
> A A
> | |
> +---------+-------+ +--------+-------+
> | | | |
> [Motorized] [HorseDrawn] [Motorized] [HorseDrawn]
> IMotor IHorsey IMotor IHorsey
>
> because sibling subsets must be disjoint sets of their superset.
>
> That sort of silliness gets out of hand pretty quickly, so I would look
> for something better. For example, I might look to make the number of
> wheels an attribute and let that parameterize the Motorized and
> HorseDrawn behaviors. Or I might separate out one set of behaviors with
> Strategy or State. Maybe even both:
>
> 1 * * 1
> [PowerTrain] ------------- [Car] ------------- [WheelConfiguration]
> A A
> | |
> +--------+--------+ +-----------+----------+
> | | | |
> [V8] [Horsey] [4x4] [4x2]
>
> Which way I simplify the inheritance will depend on the particular
> problem being solved.
I agree, but isn't this promoting object composition over inheritance
and thus, in your terms bad (or not) OO?
Is this not consistent with my described process...hmmmm.....almost?
OK it is and it isn't?
i.e. rather than say Car IS A PoweredVehicle and a WheeledVehicle,
you've said it HAS a PowerTrain and a WheelConfiguration BUT......
For a client to navigate to these entities Car Must expose;
PowerTrain GetPowerTrain()
WheelConfiguration GetWellConfiguration()
To me these are two new interfaces and thus two new roles i.e. Car is
now implementing 3 roles (do you ever feel that you are going around
in circles).
Do we have a different interpretation of Role?
>
> The bottom line, though, is that I would very likely have more classes
> than Car. The two-level subclassing above would immediately make me
> nervous precisely because it is complicated and the tree imbues Car with
> too many disparate responsibilities. (Duplicated subclasses is an
> almost infallible indicator of lack of cohesion in the root superclass.)
> So if I can't convert some of the subclassing to knowledge attributes
> easily, I'm going to look to delegate some of the responsibilities to
> other entities. [Of course, pursuant to my soapbox point above, those
> classes will have to abstract problem space entities decomposed from Car
> _in the problem space_.]
Yep I agree.
>
> On Visitor vs Strategy:
>
> >>One difference lies in Context's client. In the Strategy pattern
> >>Context's client does not need to instantiate the relationship; that can
> >>be someone else entirely. However, in the Visitor pattern whoever
> >>passes 'v' is both the client of the behavior in the Client/Context
> >>collaboration AND the one who understands the solution context that
> >>determines which Visitor subclass participates in the "current"
> >>relationship.
> >
> >
> > hmmm...my heads beginning to melt......
>
> Try thinking of it this way. In Strategy we have:
>
> 1 R1 1 1 R2 1
> [Client] ------------ [Context] ------------- [Strategy]
> + Operation() + Operation()
> A
> |
> +------------+------------+
> | |
> [A] [B]
> + Operation() + Operation()
>
> When Client collaborates with Context it invokes Context.Operation().
> That Operation() is mapped 1:1 with Strategy.Operation(). The
> substitution comes into play when the A or B implementation of
> Operation() is invoked. Which implementation that will be depends upon
> how R2 is instantiated. Say,
>
> class Context
> {
> public:
> Operation()
> setStrategy (Strategy*)
> ...
> private:
> Strategy* myStrategy;
> ...
> }
>
> Context::Operation()
> {
> myStrategy->Operation()
> }
>
> Which implementation Client gets executed depends on where
> Context.myStrategy is currently pointing. But that is determined by
> someone setting it via Context.setStrategy. That is likely to be
> someone other than Client and Client is completely oblivious to it.
> Client just knows Operation() is being invoked.
OK, I understand this now, I did understand Strategy, but maybe not
quite in this light :o)
>
> In contrast, in Visitor we have:
>
> R1 1 1 R2
> +---------------- [Client] -------------------+
> | |
> | selects | invokes
> | 1 1 R3 uses 1 | 1
> [Element] ------------------------------------ [Visitor]
> A A
> | |
> ... ...
>
> What is really happening is that Client is invoking a behavior in
> Visitor but that behavior needs to collaborate with a specific Element.
> Client selects that Element and instantiates the R3 relationship so
> that Visitor can access it. All this is very round-about because of the
> callback mechanism but the basic facts are still: (A) Client needs a
> Visitor behavior to execute and (B) that behavior collaborates with a
> specific Element instance.
GoF yep.
>
> The problem is that Client also has to make sure that the right Visitor
> flavor is used for the selected Element at the subclass level. That is
> handled via instantiating R2, which is usually done by Client but could
> be done by anyone. Nonetheless R1, R2, and R3 must be in synch. That
> synchronization is handled through Element.Accept as Client passes the
> right Visitor instance.
>
> Therefore Client is intimately aware of the the synchronization implicit
> in R3 and it manages it explicitly by passing 'v' to Element.Accept.
> Thus Client knows a whole lot more about the substitution of behavior
> than the corresponding Client in Strategy.
>
> >>[FWIW, if I were actually implementing Visitor, I would not use the
> >>callback so directly for exactly this reason. That is, Context.Accept
> >
> >
> > Element.Accept I hope (otherwise I've really lost it).
>
> Right.
>
> >
> >
> >>would not directly invoke the behavior. Instead it would simply
> >>initialize a pointer to 'v' in Context. Subsequently, during a Client
> >>collaboration the Context would "walk" that pointer to actually invoke
> >>the behavior.
> >>That would allow someone besides the Client to determine
> >>which Visitor was relevant to the current solution context, thus
> >>separating Client's concerns.]
> >
> >
> > I am really lost - it's too abstract for me to see the wood for the
> > trees.
> >
> > When you say 'v' do you mean VisitMethod or Visitor?
>
> I meant the 'v' in the GoF example, which is a reference to a Visitor.
>
> Basically all I am proposing is to rewrite Element.Accept in the GoF
> example as
>
> class Element
> {
> public:
> Accept (Visitor* v) {myVisitor = v;}
> doIt()
> ...
> private:
> Visitor* myVisitor
> ...
> }
>
> Element::doIt()
> {
> myVisitor->VisitCOncreteElementA (this)
> }
>
> IOW, I moved the actual call to doIt from Accept and I just used Accept
> to "register" the right Visitor, which anyone can do who understands the
> context can do (i.e., anyone who knows what Visitor needs to be
> connected to the Element instance). In effect this simply changes my
> diagram above to:
>
>
> [Client] +---------- [SomeOneElse]
> | 1 | | 1
> R1 | | |
> | invokes | | selects
> | 1 1 | uses 1 | *
> [Element] ------------------------------- [Visitor]
> A R3 A
> | |
> ... ...
>
> Now Client doesn't have to know anything about synchronization. It
> simply announces a condition where executing something via myStrategy is
> appropriate. IOW, Client is only about timing the sequence of
> execution. SomeOneElse is responsible for figuring out what Visitor
> flavor is appropriate when the condition in Client prevails.
>
> Time for another break.
>
>
hmmmm, interesting, personally I wouldn't call it a visitor though, it
seems more like embedding your strategy context in an element.
I like strategy part, I'm not too sure about the 1:1 relationship
between it and the element - mainly because potentially the element
may be
processed by multiple threads and that relationship would potentially
worry me.
- Next message: Philippe Ribet: "Re: OOP Language for OS Development"
- Previous message: Ioannis Vranos: "Re: OOP Language for OS Development"
- 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
|