Re: Client/Service Design Style.

John Carter <[email protected]>
Newsgroups gmane.comp.programming.language-of-the-year
Message-ID <[email protected]>
On Fri, 23 May 2008, Gregg Irwin wrote:

> JC> The idea is whenever you have any two interacting modules / objects
> JC> you explicit elect one to be the client and the other the service.
>
> I consider this "layering". You can call down into lower layers, but
> not back up to higher ones (unless you pass in a callback of some
> kind).

Interestingly enough I've given up calling this layering, since in any
system of commercial grade complexity, you usually cannot draw a
consistent two dimensional layer diagram.

However, an acyclic digraph has all the nice properties of a layer
architecture but is up to handling realistic size systems.

> JC> What I'm talking about is a style of design, OOD or otherwise where
> JC> the software is partitioned into modules every interacting pair of
> JC> modules is either a client or a service.
>
> This is "partitioning", but might also just be considered "modularity".

You missed the core part of this sentence. "every interacting pair of
modules is either a client or a service"

I have seen systems that claim to be modular, but part of one module
is written to be the client of the other.... and later on it seems to
behaving as the service. ie. Client/Service design is modular, but
modular design is not necessarily client/service.

> JC> The client is written so that it is bound to the interface of the
> JC> service but utterly ignorant of the implementation.
>
> JC> The service is written to encapsulate it's state in a manner that it
> JC> cannot unwittingly be corrupted by any client.
>
> This is "information hiding".

Yes... but a little more. I see lots of OOP code that has getters and
setters galore.... information is hidden behind methods.

But access to the internal state is often leaked to such a degree that
it is trivial for client code to corrupt it.

The step beyond is to "protect the invariant", ie. their _is_ no
method, no pointer, no access that would permit client code to move
the state space of the service into an invalid state.

In fact, if there are no constraints on the range of values that an
element can take on, or no constraint to maintain a relationship with
some other item of state... then I see little reason to "hide" the
information. It's just a plain old "struct" then.

ie. OOP practitioners encapsulate state in methods, but leak the
invariant.

I protect the invariant, but am not terribly fussed about leaking
state if it's unconstrained. (However I tend to assume I haven't fully
understood what the invariant is, and hence try _hard_ to place state
where it doesn't need to be leaked!)

> All of these fall under the auspices of modular programming. They have
> each had new names and and refinements applied in different
> areas, e.g., information hiding is "encapsulation" in OO terms.

Yes and no....

The classic OOP gotcha is... what should I pass as a parameter?

class A {
   public:
   void a1( const B& b) {
      int b1 = b.getAge();
      double b2 = b.getHeight();
     string name = b3.getName();

     .... //dostuff
   }
   void a2( int b1, double b2, string b3);
};

B b;
A a;

a.a1( b);
a.a2( b.getAge(), b.getHeight(), b.getName());

Which is the correctly designed signature? a1 or a2?

Signature a1 couples class A to class B. Coupling Bad!

A common bit of refactoring folklore I give people is this... if you
find a commonly recurring set of parameters in your function calls (or
in ye olde FORTRAN common blocks) consider making an object out of it.

Now let's take this one further....

A is clearly a client, as it makes use of B.

Now here below is a fairly clearly _wrong_ bit of code.... (but I have
seen very subtle variations of this wrongness, but it would take
several tens of pages to layout some of the real world tangles I have seen...)

class A {
   public:
   void a1( const B& b) {
      int b1 = b.getAge();
      double b2 = b.getHeight( self); // Tangle Bad!
      string name = b3.getName();

     .... //dostuff
   }
   void a2( int b1, double b2, string b3);
   double a3();
};

class B {
   public :
   //...
   double getHeight( A& a)  {
      return someFunctionMyInstancesVarsAnd( a.a3());
   }

B b;
A a;

a.a1( b);
a.a2( b.getAge(), b.getHeight(a), b.getName());

Here, a1(), cosmically speaking, is a function in B, eg. getHeight(), which makes use of A::a3(). We have a
tangle of dependencies!

Worse, the Design By Contract rule is "The invariant must be intact at
the start and end of every public method."

You can think of the invariant as closing and opening at the start and
end of every public method.

It is like a door, when you are fiddling with the internal state, you
close the door and let no one in until you have put it all safely back
together again.

Now a1 closes that instance of the "A" door, and then invokes
getHeight() which deep in it's bowels attempts to use a method "a3()"
on that instance of "a"! But the "invariant" door is closed! Invoking
a public method on that instance is not guaranteed to produce sane
results!

So here deep DbC style analysis will tell you _why_ this type of
design is wrong.

DbC will not prevent a team of programmers, slaving away for years,
from getting themselves into this state, possibly via a call graph
several layers deep.

By explicitly creating an acyclic digraph of clients and services
this style tangle becomes impossible.


John Carter                             Phone : (64)(3) 358 6639
Tait Electronics                        Fax   : (64)(3) 359 4632
PO Box 1645 Christchurch                Email : [email protected]
New Zealand
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.