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



H. S. Lahman wrote:
> Responding to Huber...
>
>>>>> 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.
>>
>>
>> I appears that you didn't get my point. I try one last time: *If* the
>> problem at hand indeed requires separation of message and method then
>> you can implement infrastructure to that effect in just about any
>> OOPL (I don't have to prove that, do I?). Why exactly do you think
>> that an OOPL *forces* us to do anything method-based?
>
> I'm afraid you would have to prove it. You can only do that by
> overlaying some sort of developer structure on the 3GL syntax (e.g.,
> creating state machines whose events are pushed/popped on a separate
> queue). Such separation is impossible to do with pure 3GL language
> syntax.

Oh, and how is this statement not a direct contradiction to what you
said in another post:

<quote>
> I suspect this won't lead anywhere. Let me rephrase my original
> question: What *exactly* can one do in a 4GL that cannot be done in a
> 3GL like C++, Java, C# augmented with appropriate libraries?

This is a variation on the argument that if you give me a big enough
computer and enough time, I can model the universe in real time. B-)
If you provide enough library augmentation for emulation, you can do
pretty much whatever you want.
</quote>

?????? (I can't make any sense of this)

> However, that is not the point. The problem with procedural message
> passing is that it implements a Do This imperative. That imperative
> reflects the procedural programming paradigm where software is built
> with in terms of organizing sequences of operations: Do This, then Do
> This, then... That paradigm led to the legendary spaghetti code
> because callers typically had an expectation built into their
> implementations about what needed to be done next in the overall
> solution. IOW, the caller could not be specified without also
> specifying the entire functional decomposition tree down the chain of
> calls it made. Nor could the procedure be tested without a full
> implementation of the
> descendant calling sequence. (Things were even more obvious for
> functions below.)
>
> The primary contribution of the OO paradigm to maintainability was to
> break the spaghetti chain by separating message from response. That
> way the message sender implementation /can't/ depend upon the
> response; it simply sends a message to announce something that it has
> done and the developer connects the dots outside the context of
> individual method specification.
>
> As I pointed out before, though, the real point is to avoid those
> specification dependencies in the implementation. IOW, it is about
> how one designs methods and collaborations. If one does that
> properly so one is not hard-wiring solution sequences in objects, then
> it doesn't
> matter if one uses procedural message passing in the OOPL code. The
> procedural calls are then benign because the calling implementation
> does not depend in any way on what the callee does.
>
> State machines happen to be one way of doing that. But if the
> application is to be implemented serially in an inherently synchronous
> environment, one can eliminate the queue manager and replace event
> generation with direct procedure calls to target methods during OOP.
> Then looking at the code one would not be able to tell state machines
> had been used in the OOA/D and superficially the program would look
> exactly like any procedural program. One would have to look at the
> method specifications to realize that there were never any
> implementation dependencies between objects.

Which leads us to the question why a developer shouldn't implement such
a system directly in a 3GL language, without the detour through a 4GL
and state machines? I'm asking because I believe the direct 3GL way is
often faster.

> There would be none because one cannot define a state action to depend
> on the response to any event the action generates because there is an
> arbitrary delay between when the event is issued and when it is
> consumed. That is painfully obvious with state machines. However,
> exactly the same thing is possible if one conceptually decouples
> message and method for /any/ behavior in the OOA/D. Again,
> superficially the resulting 3GL code would be indistinguishable from
> procedural code in the way calls were made.
>
> Bottom line: one /always/ wants to build an OO program _as if_ message
> were separated from method.

Sorry, I don't buy that, primarily for the reason stated above.

>>> 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.
>>
>>
>> That approach may be of value for a certain class of problems, but I
>> don't see why anyone would use it to implement everyday objects like
>> e.g. Customer, Address, Order, etc.
>
> It is of value in any OO application. It is fundamental to the
> paradigm. In fact, if one does not take that OOA/D view one is likely
> just creating C or FORTRAN programs with strong typing.

You'd have to prove that.

>>>>> 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?
>>
>>
>> There was a misunderstanding. Now I see that in the message-based
>> system you would have chosen "attack" as the message that is sent by
>> the client right in the beginning, i.e. when the hierarchy only
>> contains Impala & Gazelle. The question that remains then is: Why
>> would the a developer choose "attack" as an appropriate message in
>> the message-based approach but not think of respondToAttack in
>> method-based approach? This of
>
> Because that is what the sender context is. The Predator is just
> announcing what /it/ is doing.

Which is exactly what the Predator developer did when he defined the
IPrey interface. If you want to be picky about names you can name that
interface IPredatorMsgs or or whatever (which is alright with me).

>> course assumes that Prey + subclasses are implemented by the same
>> developer as Predator. If you insist that they by implemented by
>> separate developers who know nothing of each other when they start
>> you still do get away with interfaces and do not need messages at
>> all: public interface IPrey
>> {
>> void RespondToAttack();
>> }
>>
>> public class Predator
>> {
>> public void Attack( IPrey prey )
>
> The name confuses things. "Attack" is the message ID for the message
> sent out of this message. This method in Predator would be something
> like respondToHunger

This is beside the point. Without proper requirements there is no way
to know how to name things.

>> {
>> prey.RespondToAttack();
>> }
>> }
>
> This is where the 3GL type systems get in the way. There is no way
> for Predator to send a message called "attack" while Prey responds
> with run, takeFlight, or respondToAttack unless one introduces other
> infrastructure like event queues.

I don't see how this matters here.

>> The above two classes are implemented by developer A.
>> Simultaneously, developer B implements Impala & Gazelle as follows:
>>
>> public class Gazelle
>> {
>> void run() { /* ... */ }
>> }
>>
>> public class Impala
>> {
>> void run() { /* ... */ }
>> }
>>
>> // and so forth for other Prey classes
>>
>>
>> Now when the two developers need to hook their stuff together then
>> Developer B simply has his classes implement IPrey:
>>
>> public class Gazelle : IPrey
>> {
>> public void RespondToAttack()
>> {
>> run();
>> }
>>
>> void run() { /* ... */ }
>> }
>>
>> public class Impala : IPrey
>> {
>> public void RespondToAttack()
>> {
>> run();
>> }
>>
>> void run() { /* ... */ }
>> }
>>
>> Why is this any worse than the message-based approach?
>
> The problem is knowing how to define respondToAttack. In the original
> case the only relevant behavior for any prey in the problem context
> was run(). To define respondToAttack one must be prescient about how
> the tree will be modified in the future. That's because at every
> level of
> the tree one needs a clear idea of what the LSP constraints are.
> (Which is how this thread got started.) Therefore to introduce
> respondToAttack you have to be able to define its LSP constraints. In
> particular, you need to define how run() constraints are more
> restrictive.

What? The message-based approach has the same problem. That is, if the
Prey base class promises certain behavior as a response to an "attack"
message then run must observe the LSP in exactly the same way as above.
If no such behvior is promised then run can do anything it wants.

> If we don't we will have a potential problem for any client of
> respondToAttack when we expand the tree. They may be broken when we
> add [Bird]. That is exactly what happens with [Lion]. In the new
> tree we stipulated it can only attack antelopes. But we are accessed
> prey.respondToAttack. So if the [Prey] instance in hand happens to be
> an instance of [Bird] the software is broken. One solution is to
> introduce [Antelope] and modify [Lion] to access
> Antelope.respondToAttack.

I don't get it. All of the above would apply no matter whether you have
method-based or message-based interaction.

> However, if we defined Prey.respondToAttack's LSP constraints properly
> in the first place to accommodate the future tree, we would have seen
> something was wrong with:
>
> 1 1
> [Lion] -------------------- [Prey]
> + respondToAttack
> A
> |
> +-----------+-------------+
> | |
> [Gazelle] [Impala]
>
> That's because the LSP constraints for that respondToAttack were too
> general to limit access only to critters who run. Now in this case
> the obvious answer is that we misnamed the superclass. For our
> original problem we should have used [Antelope] rather than [Prey]
> for the superclass. If we do that, then when we expand the tree to
> include birds the client is unaffected because we are already
> accessing
> Antelope.respondToAttack:
>
> [Prey]
> + respondToAttack
> A
> |
> +------------+----------------+
> 1 1 | |
> [Lion] ------------------ [Antelope] [Bird]
> A
> |
> +-----------+-------------+
> | |
> [Gazelle] [Impala]
>
> In addition, the Antelope : Iprey interface still does the right thing
> when it maps respondToAttack to run().
>
> This sort of logic train is what you are after when you argue that one
> should have defined Prey.respondToAttack in the first place. That's
> fine, but one can only get it right if one is prescient about the
> exact LSP constraints one will eventually need for
> Prey.respondToAttack. It is that analysis that leads to uncovering
> side problems like
> Prey/Antelope naming.
>
> The only way to avoid that is to degrade the interface method so that
> it has no prey semantics:
>
> [Prey]
> + arbitraryName
> A
> |
> ...
>
> while the implementation of arbitraryName dispatches to the
> appropriate /private/ method (run, takeFlight, etc.) with the correct
> implementation for the LSP constraints at that level. Alas, that
> introduces its own set of problems for groking what is going on.

I still don't get it. What you describe above applies in both
scenarios, the message-based one and the method-based one.

> However, if one takes this last bit a small step further and name the
> /interface/ method for the message rather than naming it arbitrarily.
> Then we have exactly the same mapping that one achieves in OOA/D:
>
> public class Antelope : IPredatorMsgs
> {
> public void class attack ()
> {
> this.run();
> }
> }

Excuse me? Appart from naming and the additional complexity with Lions,
Antelope, etc. this looks just like the implementation I proposed. I
try to clarify. When I asked

<quote>
Why is this any worse than the message-based approach?
</quote>

in my last post, then I wanted to know how the method-based approach I
presented is any worse than the *equivalent* message-based approach
(i.e. with only Predator, Imapala & Gazelle as concrete participants).
Your response however, lengthily explains *general* problems with
inheritance, naming and so forth. That is, these problems exist in both
the message-based and the method-based approaches.

> [Of course this assumes one has an OOPL where one can define
> interfaces separately from classes.]

??? AFAIK, you can do that in just any OOPL that supports interfaces.

>>>>>>> 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.)
>>
>>
>> That's why anyone who has ever written unit-tests uses interfaces, as
>> follows:
>>
>> public interface IWhatever
>> {
>> int doIt( int i );
>> }
>>
>> public class ClassB : IWhatever
>> {
>> public int doIt( int i )
>> {
>> // calculate
>> }
>> }
>>
>> public class ClassX
>> {
>> IWhatever whatever;
>>
>> public ClassX( IWhatever whatever )
>> {
>> this.whatever = whatever;
>> }
>>
>> void method1( int x )
>> {
>> int tmp = this.whatever.doIt( x + 3 );
>> }
>> }
>
> You are still implementing ClassB::doIt.

Nope (I'm rather surprised, as this is really basic testing knowledge):

class Mock : IWhatever
{
readonly int expectedI;
readonly int result;

public Mock( int expectedI, int result )
{
this.expectedI = expectedI;
this.result = result;
}

public int doIt( int i )
{
// Assert is a class utility of the test infrastructure
Assert.AreEqual( this.expectedI, i );
return result;
}
}

void Test( int a, int b)
{
ClassX x = new ClassX( new Mock( a + 3, b ) );
x.method1( a );
Assert.AreEqual( b * 5, x.attr1 );
}

Yes, you'd have to call Test with different values to be sure that
method1 is implemented correctly, but the same applies to the test
harness you would write for your message-based variant. Note that we
did *not* implement ClassB.doIt in any way (as you claimed). Nor does
ClassX depend on ClassB in any way, it only depends on the IWhatever
interface.

>>> 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
>>
>>
>> I was inclined to call *this* spaghetti code because I don't see why
>> anyone would do this for everyday objects like Customer, Order, etc.
>> However, yes there are situations where this separation is actually
>> of value. So, once again it's that little word "always" that I have
>> problems with (you didn't actually use this word in this context but
>> many of your comments above hint in the direction that you think this
>> separation is always a good idea).
>
> This is /always/ of value if one has a goal of maintainable code,
> which is why one does OO development in the first place. One does
> this to
> eliminate a implementation dependency between ClassX and ClassB.

Which can just as well be done with interfaces, as I have shown.

>>>>> 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.
>>
>>
>> I don't buy that separation in computing_space & not_computing_space
>> because I believe that you *will* have a hard time to make that
>> distinction in just about any non-trivial application. If you think
>> otherwise I would be interested to hear your algorithm that allows
>> the unambiguous distinction for all classes.
>> Moreover, if you say Stack, List & FileStream do not need to be
>> message based for some reason then couldn't it be that objects like
>> Customer & Order also don't need to be message-based?
>
> As I explained when this originally came up, objects like Stack and
> List are just dumb data holders with no unique problem behaviors while
> FileStream is just an API to to external software so it /is/ just a
> bundle of messages. Also, as I think I mentioned, such objects
> probably would not even appear in an OOA model because they are
> primarily tactical OOP implementations.

Right, you did mention that, but you still have to say how you make
that distinction. For example would OrderList belong to these dumb data
holders?

>>>>> 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.
>>
>>
>> I don't think that example shows less coupling in the message-based
>> approach. ClassX still must know ClassB, ClassX still calls
>> ClassB.doIt and ClassB still needs to respond back to ClassX. I ask
>> again: How is there less coupling in this example?
>
> In the first version of ClassX::method1 the ClassB::doIt method
> returned a value that was used in the implementation of the caller.
> That value
> creates the dependence on what ClassB::doIt does because in order to
> specify what value is placed in ClassX::attr1, one has to specify how
> that value is to be computed. Nor can one unit test ClassX::method1
> without providing a correct implementation of ClassB::doIt.

Wrong, see above.

> In the second version neither ClassX::method1 nor ClassX::method2
> depend on anything that ClassB::doIt did. They could be both be fully
> specified with no reference to anything ClassB::doIt did.

The same applies to the interface-based variant (ClassX only knows
IWhatever).

> Consequently, they could both be unit tested without defining any
> implementation for ClassB::DoIt.

See above, the same applies to the interface-based variant.

>>> 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.
>>
>>
>> If it is simply a question of the mindset the why couldn't a
>> sufficiently experienced developer do "proper OO" (your definition)
>> with the method-based approach?
>
> They could.

That's good to hear. There's hope for us 3GL "dinosaurs".

> In fact, those examples of maintainable procedural
> applications usually reflected exactly this sort of mindset.
> Recognizing that correlation was crucial to formalizing the OOA/D
> methodologies. However, in a procedural environment there is no such
> explicit paradigm and supporting infrastructures to guide the
> developer.

So we developers are all idiots who need guidance from a 4GL tool, is
it
that what you are getting at?

--
Andreas Huber

When replying by private email, please remove the words spam and trap
from the address shown in the header.

.



Relevant Pages

  • Re: What doesnt lend itself to OO?
    ... >>server is a pure data transfer interface. ... essentially exposing the client or service implementation. ... >>paradigms can be abstracted just like any other problem space in an OO ...
    (comp.object)
  • Re: Text terminal rendering design
    ... free to give it any object that satisfies that interface. ... giving it a real facade object if I choose. ... Facade to avoid touching the client. ... completely incompatible with this subsystem interface. ...
    (comp.object)
  • Re: Abstract public member variales?
    ... Entity has no encapsulation and no real methods, but the great thing about it is that its public interface will never have to change during maintenance. ... Assuming there is at most only one Property instance for each property type, then the R1 collection class would own the smarts for finding the right one. ... However, I would point out that the client of the getter is someone who needs to collaborate with a specific Property, not the Entity itself. ... The second line generates a message to the Property for the collaboration. ...
    (comp.object)
  • Re: Text terminal rendering design
    ... Which is exactly what would happen in your case when the interface of the implementation object changes. ... But then you need a new object to own the actual responsibility within the subsystem implementation. ... I can pop in a facade in a completely painless manner without being forced to rename or kludge anything. ... Facade to avoid touching the client. ...
    (comp.object)
  • Re: Message-based vs. method-based interaction [was: Re: LSP and subtype]
    ... interface is always the method signature. ... We can name it for the owner context of what it does or we can name it for the client context, but we can't do both. ... overlaying some sort of developer structure on the 3GL syntax. ... Such separation is impossible to do with pure 3GL language syntax. ...
    (comp.object)