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