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



Responding to Huber...

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)

I don't see any contradiction. In the first quote I am saying that one can only /emulate/ separation of message and method in the 3GLs by providing added infrastructure beyond what the 3GL provides.


In the second quote I am responding to you saying that you can do it by adding augmentation of the 3GL through libraries. Those libraries are added infrastructure that was created by some developer beyond what the language provides. The point I was facetiously making was that with enough such additional infrastructure one can do anything one wants with the language. But that doesn't change the fact that the language doesn't directly support whatever the infrastructure provides.

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.

Because in the 3GL the developer must commit to serial/synchronous vs. concurrent/asynchronous to write the code. In the 4GL that commitment is deferred so that the developer doesn't have to worry about it when focusing on the functional requirements. [And if one is doing translation, the developer /never/ has to worry about it because the transformation engine handles the grunt work.]


As far as development speed is concerned, the 4GL is much faster. There are two basic reasons. First the notation is much more compact, by at least an order of magnitude. (A 250 KNCLOC C++ application's resolution of functional requirements can be fully described in about two dozen UML diagrams that are easily readable on 11x17 pages.) That compaction reflects raising the level of abstraction of the problem by a great deal. In addition UML allows one to focus on quite different views of the solution. Dealing with a much more compact and abstract view of the solution greatly increases the developer's efficiency.

The second and more important reason is computing space design reuse. The 4GL can be compiled just like a 3GL, which is what translation tools do. All of the code that addresses nonfunctional requirements is the problem of a different union -- the people who build the transformation engine that automates the optimization. In a large application that can be a rather substantial amount of the code (e.g., for things like read-ahead caching). It also eliminates an enormous amount of technology boilerplate (e.g., one will never see a SQL statement in an OOA model).

Bottom line: model-based translation is the most agile software development technique available and productivity will be integer factors greater than 3GL-based development.

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.

Alas, it's not a choice if one is going to do OO development; it is basic ot the paradigm.


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.

One can argue that the goal of OO development is to eliminate the implementation dependencies that plagued traditional procedural development. The crucial element of the OO paradigm for /ensuring/ that is to conceptually separate message and method in OOA/D. This is really fundamental OOA/D 101.


So what do you want me to prove? That the primary goal of OO development is maintainability? That implementation level dependencies inhibit maintainability? That the mindset of separating message and method helps prevent such dependencies? It seems to me that I have already demonstrated the last two with the return-value example. I'm not sure how one would prove the first other than a really tedious literature search from the '70s and '80s.

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).

Note how telling your first sentence is. Picture me jumping up and down screaming, "But that's the whole point!"


Why is the Predator developer concerned with the Prey interface at all? Why should the Predator developer even know there was a Prey? You are assuming it is the Predator developer's job to define the Prey interface. That implies an expectation of Prey behavior on the part of the Predator, which is a quintessential procedural view.

In the OO paradigm Predator and Prey are unique, self-contained abstractions extracted individually from some problem space for their /intrinsic/ properties. Sure, the same developer is probably defining both and that developer always has one eye on the overall problem when deciding what to abstract. But it shouldn't matter if they were abstracted by different developers. The objects still abstract intrinsic properties of the underlying entity and that is all one needs to fully define them.

So define the Predator and the announcement it wants to make. Define the Prey and the responses it might provide. Then connect the dots by deciding that the Predator announcement should go to Prey. Finally, once that is decided, design the Prey interface to accept the announcement message and dispatch to the proper behavior.

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.

It matters to the implementation of Predator if one wants to decouple the implementations. And it matters to the Prey because using the same name everywhere obscures the fact that there are entirely different responses. It also obscures the LSP issues about how tight the access constraints need to be at each level of access in the tree.


Are such subtle nuances necessary to writing software without implementation dependencies? No. But they sure help to ensure the correct mindset, especially for newbies who are converting from procedural development. Developing software is tough and understanding it during maintenance is even tougher. So original developers should provide as many clues as they can about what is going on. And just thinking about providing them is a methodological check on getting things right.

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.

We are back to documenting the LSP constraints again. What LSP constraints does IPrey have in the original development? Are they the highly restricted constraints of run() or the less restrictive constraints of run() AND takeFlight()? That is, when you add:


public class Bird : IPrey
{
public void RespondToAttack()
{
    takeFlight();
}

void takeFlight() {....}

can any existing client continue to use the IPrey interface if the instance in hand is a Bird? Let's assume the client is a Lion who can't deal with Birds. Can you then add this [Bird] class at all?

You can't because the original IPrey for Lion assumed the restrictive definition by implication because _that is the behavior [Lion] accessed_. You will have to introduce an [Antelope] class with IPrey and some other interface for [Bird] alone and yet another interface for the loose combination that allows [Pterodactyl] to chase all of them.

Bottom line: any time you reorganize implementation overrides in the tree you may have to define more interfaces and, possibly, modify which interfaces the existing clients use.

Now compare this to my solution:

<snip>

public class Antelope : IPredatorMsgs
{
   public void class attack ()
   {
        this.run();
   }
}

What is the difference? I use one interface, IPredatorMsgs, at every level of the [Prey] subclassing tree. That is, Prey, Antelope, and Bird all provide the same interface and all clients use it. Each class just implements that interface its own way.


So how do I resolve the LSP constraints? By the level at which I implement the relationship for the collaboration.

                      [Predator]
                          A
                          |
    +---------------------+------------------------+
    |                     |                        |
    |                [Pterodactyl]                 |
    |                     | 1                      |
    |                  R1 |                        |
    |                     | attacks                |
    |                     | 1                      |
 [Lion]                [Prey]                    [Hawk]
    | 1                   A                         | 1
    |                     | R2                      |
 R3 |        +------------+--------------+          | R4
    |   1    |                           |     1    |
    +---- [Antelope]                    [Bird] -----+
  attacks     A                                attacks
              | R5
     +--------+-------+
     |                |
  [Impala]         [Gazelle]

When I instantiate the R1, R3, and R4 relationships I enforce the LSP constraints by making sure only the right critters participate in the relationship. That decision is based on the collaboration context implicit in the relationship and what the behavior conditions are at each level of the Prey structure, not the interface.

Why is that better? Because I can change my mind later. I can decide that I want Lions to be able to chase Birds too. To do that all I have to do is instantiate the relationship so that it accesses the tree at the [Prey] level like [Pterodactyl] does. When I do that I do not have to touch the [Lion] implementation because it still sends its message to the IPredatorMsgs interface. (In fact, the code that enforces the LSP rules on relationship participation is probably encapsulated in another object entirely!)

Summary: in my solution the collaboration decision that is driven by LSP compatibility is isolated and resolved outside the context of either the [Predator] or the [Prey] implementations. Therefore the implementations of various Predators cannot be affected by changes in the tree. So one can make changes in the tree without touching the [Predator] implementations. Finally, any changes in the tree conditions are isolated exactly where they should be: where collaboration participation is defined.

Why is handling LSP condition changes better isolated in relationship instantiation rather than multiple interfaces? Several reasons. The main one is that one /never/ has to touch the client implementation when the tree changes. The only implementation that ever gets touched is whoever instantiates the relationship and that is a responsibility that is well defined and deliberately isolated in the overall solution, which is the second benefit -- that one always knows where to look.

The third reason is that relationship participation is always defined in basically the same way. One doesn't have to be wary of exotic problem space circumstances; the instantiation will always be the same sort of logic and its correctness will be determined in terms of collaboration participation. The fourth reason is related to the third. Relationships are defined for the class members, not individual properties. So when one validates participation one must look at the overall context rather than individual behaviors, which is the real issue in substitution via inclusion polymorphism.

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;
  }

This is just stubbing the expected value from ClassB::doIt.


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.

Here all you are doing is implementing ClassB::doIt in the test harness; it's just a form of stubbing. As I indicated originally, that is just self-delusion; all one is testing is the test harness. You are just emulating what ClassB::doIt is /supposed/ to do, not what it actually does. To fully unit test ClassX::method1 you must have a working implementation of ClassB::doIt to ensure the correct value will be returned.


To put it another way, as soon as ClassB::doIt returns a value that the caller uses, ClassB::doIt's specification just becomes an extension of ClassX::method1's specification. That is, one cannot specify the value placed in 'attr1' for a given 'x' input without specifying what ClassB::doIt does. In that case one needs to validate against both specifications.

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?

Whether it is a data holder depends on the nature of its behavior. In the strict sense even a simple getter is a behavior, especially at the 3GL level. The key is whether the behavior reflects business rules and policies that are unique to the problem in hand and whether they affect anything except the object's knowledge. If the business rules and policies are from a more general problem space, such as mathematics, or they modify the application state outside the object, then they are behaviors. Otherwise they are just synchronous knowledge accessors and the object is just a dumb data holder. But I've already gone over this so we are going in circles.


So whether OrderList is just an alias for a library collection class or it is a unique problem space abstraction depends on what the problem is.

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.

I'm afraid it is right because your solution is only testing part of ClassX::method1's specified behavior; the rest is emulated in the test harness.


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).

The problem is not with sending a message. The implementation dependency is with getting back a value computed by another object's behavior that is then used by the caller.


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


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

How? In this simple example the specification of the first version of ClassX::method1 is expressed solely in terms of the value placed in attr1 for a given input value, 'x'. When ClassB::doIt returns a value the caller is dependent on that being the right value for its specification of what attr1 will contain for the given input 'x'. The value placed in attr1 will be wrong if ClassB::doIt does not compute its return correctly.


But in my second version ClassX::method2 receives the value as an /input/ argument. That completely eliminates the implementation coupling and allows ClassX::method2 to be fully unit tested solely in terms of its inputs and outputs.

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?

OK, that's it. In the passed couple of messages you have gotten increasingly strident. Clearly you are no longer interested in discussing this reasonably. Ta-ta.



************* 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: 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)
  • Re: implementing roles in OOP......
    ... > announcement but the Y method name makes no sense in that context. ... > priori knowledge of the structure there is no way for the client to know ... I can imagine an interface that would allow adding Composites ... The example given is exactly the sort of thing that does happen, ...
    (comp.object)
  • Re: Creating a "toy" OO/AO language...
    ... That is a major difference between the OO paradigm and procedural/functional paradigms where behavior always dominates the construction. ... In an OO context there are two ways to implement a closure in the functional programming sense of a method that operates on a bounded set of variables. ... The only object that should have an interface to the strategy ... In the OO paradigm one tries to minimize that by limiting it to client and service through peer-to-peer collaboration by eliminating a hierarchical call chains. ...
    (comp.object)
  • Re: Tell, Dont Ask
    ... That precondition forces the client to keep track of what order the messages are sent to Range and help ensure the Range object's invariants. ... The whole point was for Range to ensure its own invariant. ... But there is a big difference between a contract that requires the context to provide values in a specific order and a contract that requires the context to provide pairs of values in any order. ... Here my solution was constrained by separate setHigh and SetLow interface methods in the original example. ...
    (comp.object)
  • 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)