Dmitry Ponyatov
Dmitry Ponyatov

Reputation: 346

Smalltalk: right way for custom construction

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:

  1. C1_File new: 'blablabla'

or

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

Answers (3)

Peter Uhnak
Peter Uhnak

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

  • Constructor Method
  • Constructor Parameter Method
  • Shortcut Constructor Method

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
  • Readers/Writers often use 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

JayK
JayK

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

EstebanLM
EstebanLM

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

Related Questions