Reputation: 346
What book best describes right way to make custom constructors ?
For example, I want special file system (emulatied in RDBMS storage).
Object subclass: #C1_Object
C1_Object subclass: #C1_File
instanceVariableNames: 'stream name'
Use case:
C1_File new: 'blablabla'
or
C1_File create: 'blablabla'
(1) looks native, but I have seen recommends don't override system allocation mechanics.
Next step: what is better
C1_File class>>create: aFileName
^ self new initialize: aFileName
C1_File>>initialize: aFileName
name := aFileName.
stream := C1_FileStream forceNewFileNamed: aFileName.
or
C1_File class>>create: aFileName
| instance |
instance := super new.
instance name: aFileName.
instance stream: ( C1_FileStream forceNewFileNamed: aFileName ).
^ instance initialize
C1_File>>initialize
^ super initialize
Upvotes: 0
Views: 780
Reputation: 10207
What book best describes right way to make custom constructors ?
Kent Beck's Smalltalk Best Practice Patterns (which I highly recommend to keep at hand for reference) contains around 100 Smalltalk patterns, among which is also
All of them discuss various aspects of object creation and parameter passing, however the common theme is increased understanding and clarity (and intention revealing selectors, which is another pattern).
When you have
C1_File create: 'blablabla'
it is not clear what is actually going to happen; is C1_File
going to create blablabla
? What would that mean? As Esteban pointed out, it is better to name the argument... C1_File named: 'blablabla'
; now I know what is going to happen.
(1) looks native, but I have seen recommends don't override system allocation mechanics.
You would have to mess with #basicNew
to mess with allocation mechanics. If you look at implementation of #new
, it actually doesn't do much.
Behavior>>new
^ self basicNew initialize
There also plenty of examples in the system:
OrderedCollection with: anItem
Color fromString: '#AC13D9'
or Color r: 0.2 g: 0.5 b: 0.1
Point x: 10 y: 17
on:
.. STONReader on: aReadStream
Note that the book mentioned in the beginning doesn't just show how to create a basic constructor, but also discusses other problems and challenges of the instance creation itself (e.g. when you have multiple different constructors to not blow up you method protocol, etc.)
Class-side vs instance-side - more of an addendum for Esteban's answer:
Keep the amount of regular behavior on the class-side to minimum; the class-side is primarily for meta-behavior --- managing the class itself, not doing the actual work.
Upvotes: 4
Reputation: 3141
I do not have a reference from the top of my head other than Smalltalk code I have seen in Sqeuak and third-party packages. Custom constructors like Read-/WriteStream class>>on: aCollection
, Text class>>fromString:
communicate in a way what their arguments will be used for in the created instance. Another style is to name the constructor directly after the instance variables that are initialized by it. Something like Point class>>x:y:
. The Collection constructors with:
and withAll:
make the code read fluently.
I would always strive to name constructors such that it becomes clear for the reader of a send, what you will get as an answer (a Stream that operates on that collection, a Point with these coordinates, an opened File with the given name/path?). I would not override new:
, and create
sounds rather generic, but could be useful when talking about files (open for writing or create if it does not exist), though that differs from the FileStream API, as far as I know.
Otherwise, there is still the possibility to not define a constructors, but initialize the object with accessors etc. directly following the new
:
MyFileDoesNotExist new
file: c1File;
yourself "or signal in this case"
Upvotes: 0
Reputation: 4357
who tell you not to override system allocation mechanisms?
In any case, override #new:
is not recommended in this case because for convention #new: with a parameter receives a size, not a string, so it will be confusing.
Now, I would use something like: named:
, newWithName:
, etc. but that's up to you (is a preference matter).
One thing: in Pharo, if you do instance := self new
and later instance initialize
you will be calling initialize twice because the default implementation of #new
is self basicNew initialize
, so your method needs to be defined like this:
C1_File class>>create: aFileName
| instance |
instance := self basicNew.
instance name: aFileName.
instance stream: ( C1_FileStream forceNewFileNamed: aFileName ).
^ instance initialize
But I also wouldn't recommend doing like that (initialise the stream in a creator method does not feels good). Instead I would do:
C1_File class>>create: aFileName
^ self basicNew
initializeName: aFileName;
yourself.
C1_File>>initializeName: aFileName
self name: aFileName.
self stream: ( C1_FileStream forceNewFileNamed: aFileName ).
self initialize.
Upvotes: 4