Re: Message-based vs. method-based interaction [was: Re: LSP and subtype]



Responding to Huber...

I think they evaporate because if one truly separates message and
response, then the message is just an announcement of something the
sender did.  That means the sender implementation does not to know
about the response, much less depend in any way on what the
responder does, so LSP becomes academic.



Right, but as I said, you'd never think of sending a "run" message in such an environment. So "message-basedness" alone doesn't do the job, the problems evaporate because the prey interface was improved. You no longer impratively say "run". If you had chosen respondToAttack() as Prey interface right in the beginning then there wouldn't have been as many problems with adding additional subclasses.


But why worry about it?


You need to worry about it no matter whether you use message-based or
method-based interaction. I make one last attempt: When the hierarchy
only contains Gazelle & Impala and a short-sighted developer thinks that
"run" would be an appropriate name for a message to send to these
subclasses then this message is no longer appropriate when you add the
other animals to the hierarchy. It's not just the name that is wrong, in
a more complex scenario the message might be carrying parameters that
become inappropriate when additional subclasses are added. That is, you
have exactly the same problem as in the method-based scenario. More on
this below.

I was imprecise. I should have said, "Why worry about it when designing the client?"


Separation of message and method allows one to design both the client and the service independently. Then the entire LSP issue comes down to determining where in the subclassing tree the client should access. (See the other message today for why it is important to always specify the tightest contract conditions possible at each level of the tree when defining the tree.)


We only need to have the imperative in the
interface because the OOPLs force us to do so.


You keep repeating that too and I really have a hard time to see why.
Clearly it must have occurred to you that no OOPL forces you to do
anything in any way. This also goes for method-based vs. message-based
interaction. You can implement a message-based communication system in
just about any OOPL so you are never forced to do anything method-based.

The OOPLs do not separate message and method; the message in the interface is always the method signature. That forces us to decide how to name it. We can name it for the owner context of what it does or we can name it for the client context (i.e., what the client has done to trigger the response), but we can't do both. The convention, which is a carryover from procedural development, is to name it by what it does and that creates an imperative mindset that implies an expectation of what will happen on the part of the client.


That view is, unfortunately, both insidious and pervasive. It allows a very convenient mapping of procedural development paradigms onto OO development. All too much bad OO code exists because client objects are implemented with an expectation of that will happen. The only way to prevent traditional spaghetti code is to decouple the implementations. To do that one needs the OOA/D mindset that clients generate messages, services respond to messages, and the developer is responsible for connecting the dots at a quite different level of abstraction.

When one separates the message and defines it as "attacks", that reflects the client context quite nicely. When one defines a response of "run", "takeFlight", or "stomp", that reflects the service view quite nicely (and rigorously). On the service side one can use conditions to define the LSP context but they really don't matter to the client's responsibilities.

Those conditions only matter to the developer in deciding where the client needs to access the tree. IOW, once message and method have been <conceptually> separated in OOA/D, the LSP resolution becomes a matter of deciding which subset of objects the message can go to and defining a relationship to implement that participation. Implementing that relationship then becomes a mechanical task in the OOPL (e.g., dealing with statically bound type specifications).

 That raises defining
Base methods to an art form where one must consider all possible
present and future contexts.

If I separate the message from the method, then I can name the methods
run,


Ok so far. Keep in mind that at this point you have already defined a
*message* with the name "run"...

takeFlight, stomp, or whatever is appropriate as subclasses are
added.


... which becomes inappropriate here and you have exactly the same
problem as in the method-based approach.

How is it inappropriate? It allows me to define each behavior in a very descriptive manner according to its unique semantics. Note that I can't do that in an OOPL because every subclass must implement its method with the same name as the superclass name. [Note that in UML I can have unique, descriptive method names in the subclasses for the superclass' interface. In fact, in UML the object interface is a separate model element from the class definition, which is not the case in the OOPLs where the interface type definition is also the object definition.]




Then I make the message that Base responds to "chases" or

"attacks", which is meaningful as a sender announcement, and I can map
that to the relevant behavior.


Nope, see above.

Sorry, I just don't know what you are objecting to here.

message and method.  One only gets into trouble at the OOP level
where they aren't separated.



You keep saying that. You only get into trouble when you don't see that the problem at hand requires messages-based interaction instead of method-based interaction. An architect not seeing that is a bad architect. Surely, a OOPL makes it easy for newbies to fall into this trap but an experienced architect won't.


But that's why OOA/D is message-based (and behavior access is assumed
to be asynchronous).  If they are separated, then one can't write
client implementations so that they depend on what the response is.


Ah really? I think you ignore the situation where the callee object
(e.g. Sub3) is expected to respond with a message of its own.

And I think this is a classic procedural mapping onto OOP. B-) There should be no expectation of a response. For example, the classical procedural overlay would be:


ClassX::method1 (x)
    tmp = myClassB->doIt(x + 3)
    this.attr1 = tmp * 5

This is actually very poor OO code because it creates an implementation dependence in ClassX.method1 on what ClassB.doIt does. For example, one cannot unit test ClassX.method1 without a working implementation of ClassB.doIt. (Stubbing the test harness to return an appropriate value for the provided 'x' is just self-delusion; one is just testing the test harness.)

In a properly formed OO application this method would have to be broken up until each responsibility was logically indivisible. Then one can connect the collaboration dots properly between those logically indivisible behaviors with messages:

ClassX::method1 (x)
   myClassB->doIt(x + 3)   // pure message w/ data packet
                           // message ID is doIt
                           // address is myClassB reference that
                           // instantiates a relationship

ClassX::method2 (x)
   this.attr1 = x * 5

ClassB::doIt (x)
   // manipulate 'x' to produce 'y'
   myClassX->method2 (y)  // pure message w/ data packet
                          // message ID is method2
                          // address is myClassX reference that
                          // instantiates a relationship

Now when ClassB.doIt computes the new value that ClassX needs, it sends a message to ClassX via calling ClassX.method2 with the appropriate value. [At the OOA/D level the messages would not need to be expressed as method calls; they could be pure messages of {message ID, address, [data packet]}. IOW, they look just like FSM events.]

[Note that to unit test ClassX::method1 all one needs to prove is that a message was issued to the right object with the right ID and data. IOW, once the message is issued, ClassX's responsibilities are over.]


That
implementation independence is the real goal and enables
maintainability.  Once the OOA/D has been structured so that is true,
it doesn't matter that the OOPLs use procedural message passing -- the
software /structure/ is already decoupled.  IOW, in OOA/D the
"architect" doesn't have a choice about thinking message-based.  (In
fact, the translation methodology I use describes /all/ object
behavior with state machines so client and service are always
decoupled through events.)


While that might be of value for some situations (e.g. Predator <-->
Prey) I really don't see why anyone would ever *implement* "everyday"
objects like FileStream, Stack, List, Dictionary, etc. message-based. As
I mentioned before, message-based communication is not without problems.
IMO the worst problem is that all type-checking is done at runtime what
leaves errors hidden longer than necessary. Moreover, compile-time type-checking is invaluable for maintainability. More on this below.

The separation is far more ubiquitous than that. For example, having behaviors return a value as in the example above is unfortunately all too common in supposedly OO applications. If one separates message and method doing that becomes conceptually impossible and the attending spaghetti code implementation dependencies are eliminated. (Only knowledge responsibility getters return values because knowledge access is inherently synchronous.)


Note that Stack and List are computing space data holders with no intrinsic business rules or policies unique to the problem in hand. They are pretty typical of nuts & bolts computing space objects and they don't need to be message based because they have no problem behavior responsibilities. FileStream is basically just a message API to external software (e.g., a GoF Facade pattern) so invoking it is just sending a message off to the nether regions, which is about as message-based as one can get. I don't know your context for Dictionary, but I would expect something similar to apply.

Note that type checking is a pure OOP issue. The messaging can be expressed at the OOA/D level in a sufficiently generic manner so that it can be implemented in a variety of ways. For example, full code generators for translation can generate code properly for either statically or dynamically typed/bound languages from the same OOA model.

[BTW, the reason the OOA/D assumes asynchronous behavior communication is because that is the most general form; synchronous communication is just a special case of asynchronous. If the end environment is inherently synchronous the asynchronous OOA/D model can be unambiguously converted to a synchronous solution during OOP. However, if one has a synchronous OOA/D model and the target environment is inherently asynchronous, one cannot always convert the model unambiguously.

Another justification is that an asynchronous solution can always be implemented concurrently during OOP while that would not always possible for a synchronous OOA/D solution. Bottom line: the application developer does not need to worry about issues like concurrency or whether the actual implementation will be synchronous or asynchronous at the OOA level and can focus on just the resolution of functional requirements.]


To put it another way, in a well-formed OO application Filestream
should be just as "loosely coupled" with its clients as Prey.  If that
is not
the case, then the problem lies with the Filestream implementation.


That brings us to the question how you measure or define coupling.
Imagine two equivalent systems, composed of the same objects. One is
implemented with message-based interaction and one implemented
method-based interaction. Why and how is coupling in the message-based
system smaller than in the method-based one?

One example is having behaviors return values, as above. However, there are other ways of creating implementation dependencies, especially when one has lots of state variables (attributes). All of those problems can be avoided provided one designs object behaviors without regard to context. Separating messages from methods goes a long way towards that mindset because one does not even think about what other objects are doing if all one does is generate announcement messages in the implementation of the object in hand.



Then it becomes must easier to make caller implementations dependent
on callee responses because of expectations arising from encoding
procedural-style Do This calls.



Which are just fine most of the time. It is only for the rather special and rare Predator <--> Prey style relationships where you need and benefit from message-based interaction. Message-based interaction has problems of its own you only want to deal with when you must.


On the contrary, one gets the benefit of it for /all/ object
collaborations -- provided the OOA/D is done properly.


If that were true than we would all be programming with languages like
Smalltalk as that seems to come closest to this purist view. Smalltalks
great days have long gone and today's languages (like Java & C#) have
recently made huge steps towards more compile-time checking. Why would
language designers invest so much time into this if message-based
interaction were the single-best way to do everything?

Actually, Smalltalk just does a better job of disguising the procedural message passing because one doesn't have syntactic sugar like "()". But the message ID is still the method name and the message is still just a procedure signature (albeit without conventional procedure syntax). When the client sends a message it is still an imperative for the service to do something. (Smalltalk also has a global facility for aliasing method names, which allows at least some separation, but it doesn't work just for individual classes.)


[OTOH, that disguising in one reason why Smalltalk is generally recommended for newbies rather than OOPLs like C++ and Java where the procedural message passing is more blatantly familiar in the syntax.]

Again, I don't think the issue has anything to do with static vs. dynamic type checking. It is about isolating the semantics of sender and receiver and allowing the developer to deal with collaborations, LSP, and all the rest of that at a different level of abstraction than individual object implementations.


************* There is nothing wrong with me that could not be cured by a capful of Drano.

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions  -- Put MDA to Work
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
(888)OOA-PATH



.



Relevant Pages

  • Re: Client/Service relationships & Flow of Requirements.
    ... the client calls "foo" when it wants "bar" to return a particular value. ... The following is designed to compel Prey objects to do something: ... predator < 100 yards from prey ... It is the expectation of the response that makes the runFrom name immediately suspect in an OO context. ...
    (comp.object)
  • Re: Message-based vs. method-based interaction [was: Re: LSP and subtype]
    ... That means the sender implementation does not to know about the response, much less depend in any way on what the responder does, so LSP becomes academic. ... When the hierarchy only contains Gazelle & Impala and a short-sighted developer thinks that "run" would be an appropriate name for a message to send to these subclasses then this message is no longer appropriate when you add the other animals to the hierarchy. ... Then the entire LSP issue comes down to determining where in the subclassing tree the client should access. ... problem at hand indeed requires separation of message and method then ...
    (comp.object)
  • Re: [Full-disclosure] [Professional IT Security Providers -Exposed] PlanNetGroup ( F )
    ... the review a second time and incorporate some of your suggestions. ... to do what the client will pay him for. ... exactly a vulnerability assessment is... ... Your response to question 3: ...
    (Full-Disclosure)
  • Re: Socket write behaviour is inconsistent?
    ... copy 1 byte to buffer, copy many bytes to buffer, copy one byte to ... Then why did you write "the client throws an error"? ... remote endpoint for your connection. ... When the response is sent using the first chunk of code, ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: Debain on the rise ! - However ....
    ... >> Maybe I'll be able to give Debian a try at some point in the future, ... Most threads get response. ... > client shouldn't bitch that they get burned by volume. ... > chance HTML email has content, ...
    (Debian-User)

Loading