[LWN Logo]

From: heho@gmx.net (Helge Horch)
Subject: Design by Interface in Squeak [LONG] (was Re: Bitten By Dynamic Typing)
Date: Mon, 25 Jan 1999 16:15:21 GMT

In a recent thread "Bitten by dynamic typing", Robb Shecter
<shecter@darmstadt.gmd.de> wrote in
<36A8B725.BF7E0FF4@darmstadt.gmd.de>:

>PS:  Don't get me wrong;  the more I use Smalltalk, the more I appreciate it.  I
>just have the nagging feeling that the lack of being able to specify a checkable
>interface may hinder its use as an enterprise development language.
>
>If this still isn't clear, see my article in the current Dr. Dobb's Journal,
>"Design by Interface".  If I could do -that- in Smalltalk, I'd be very happy.

I just finished reading your DDJ article (in the February 1999 "Java"
issue, pp. 96-101), and I'd like to discuss some actual Squeak code
that I came up with. (Oh boy, now I'll get roasted by both sides of
this discussion... nevermind).

First, let me try to paraphrase the essence of the article as I
understood it:

 In order to decouple application code from other (e.g. third-party)
 components, you propose programming to an interface that
 (1) insulates from the component's method set and
 (2) maybe even abstracts some technical details behind the
     component (here: SMTP).
 There could be different Adapters (one is shown) that implement the
 interface and map the calls to the underlying components. A Factory
 decides which adapter to hand out.

Would other readers of the article agree so far? Its author? ;-)

We use a similar technique here (e.g. for Java's multiple regex
packages), but I think the important part is the definition,
documentation and enforcement-of-use of the Facade interface. That's
more of a project management task IMHO. The ability to statically
check its Adapters' method signatures is a nice bonus, but to
propagate the *use* of the Facade is crucial.

However the same modeling techniques can be (and routinely are, I
believe) applied in Smalltalk. The ability to check for conformance
can be added to some degree. I have tried to keep most class and
method names the same as in the DDJ article. Look Ma, actual code!

One way to express your example in Squeak would be:

	Object subclass: #Email		" the Factory "
		instanceVariableNames: ''
		classVariableNames: ''
		poolDictionaries: ''
		category: 'Design-ByInterface'

with these two class methods:

	interface
		"(on second thought, should be called
		  messageInterface, but it's too late now)"
		^(Interface new: 'EMailMessage') " the interface "
			addAll: #(sender: addRecipient: addCC:
				  content: subject: send)

	newMessage
		^SqueakEmailAdapter new

Here's the adapter to the resident SMTPSocket class:

	Object subclass: #SqueakEmailAdapter
		instanceVariableNames: 'from to ccs subject content
result '
		classVariableNames: ''
		poolDictionaries: ''
		category: 'Design-ByInterface'

with one class method:

	new
		^super new initialize

and these instance methods:

	data
		"** well, something along these lines **"
		| s |
		s := WriteStream on: String new.
		s nextPutAll: 'From: ', from;
			cr;
			nextPutAll: 'To: ', to;
			cr;
			nextPutAll: 'Subject: ', subject;
			cr;
			cr;
			nextPutAll: content.
		^s contents

	initialize
		ccs := OrderedCollection new

	recipientList
		| answer |
		answer := OrderedCollection new.
		answer add: from.
		answer addAll: ccs.
		^answer

	server
		^'localhost'

	addCC: anAddress
		ccs add: anAddress

	addRecipient: anAddress
		"** obviously I don't know SMTP **"
		to isNil
			ifTrue:  [ to := anAddress ]
			ifFalse: [ self addCC: anAddress ]

	content: aString
		content := aString

	send
		result := SMTPSocket
			deliverMailFrom: from
			to: self recipientList
			text: self data
			usingServer: self server

	sender: anAdress
		from := anAdress

	subject: aString
		subject := aString

The interface as specified in the Email class>>interface method could
be coded up (and be extended left-and-right, no doubt) thusly:

	Object subclass: #Interface
		instanceVariableNames: 'name selectors '
		classVariableNames: ''
		poolDictionaries: ''
		category: 'Design-ByInterface'

with one class method:

	new: aString
		"answer a new Interface of a specific name"
		^self new initialize name: aString

and these instance methods:

	initialize
		self name: '(unnamed)'.
		self selectors: Set new

	name: aString
		name := aString

	selectors: aCollection
		selectors := aCollection

	missingSelectorsIn: aClass
		"answer a Set of the selectors that are required
		 by the receiver and that are missing in the
		 argument class"
		| staticallyKnown faulty |
		staticallyKnown := aClass allSelectors.
		faulty := self selectors select: [ :any |
			(staticallyKnown includes: any) not].
		^faulty

	add: aSymbol
		selectors add: aSymbol

	addAll: aCollection
		selectors addAll: aCollection

	name
		^name

	selectors
		^selectors

	isImplementedBy: anObject
		"answer true if the argument implements all
		 selectors of the receiver"
		self selectors
			do: [ :each |
				(anObject respondsTo: each)
					ifFalse: [^false]].
		^true

And a final convenience method in Object:

	conformsTo: anInterface
		"answer whether the receiver implements all
		 selectors of the interface"
		^anInterface isImplementedBy: self

All errors in the code are mine, and mine alone. An example use case
would be:

Email newMessage
	sender: 'me@localhost';
	addRecipient: 'you@localhost';
	addCC: 'him@localhost';
	subject: 'this is a test';
	content: 'as if you would not know...';
	send

Invitation to discussion:

1) Might this be sufficient (to some degree)?

   E.g. because the snippet

	Email interface
		missingSelectorsIn: Email newMessage class

   helped during the implementation of the SqueakEmailAdapter.

2) Why is this insufficient?

   E.g. it can not detect dynamically "understood" methods, i.e.
   of Proxies via #doesNotUnderstand: .

3) Doesn't Protocol documentation involve much *more* than just the
   message signatures anyway? I.e. calling sequence, required vs.
   optional protocol, exceptional conditions and whatnot.

4) Should the snippet

   Email newMessage
	conformsTo: Email interface

  really give me the warm fuzzies?

  Answer: No, because I did not test the adapter. Now where have I
  put Kent's UnitTest package...

Happy talking everybody,
Helge
-- 
You read a scroll of "postpone until Monday at 9 AM".--more--
Everything goes dark...