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