Thursday, May 21, 2009

Adding new properties to a class

When you load a new toy into Squeak and toy with it in a workspace, that is usually fun. So much more fun than creating a new class. I always suspect that I use the Squeak tools all wrong, but honestly, to create a new class with a couple of properties, it takes me fivethousand clicks to set all the instance variables, set the accessors and get them all properly initialized.

So, I still don't know if there might not be a much easier way to do it, but I'm fine with what I've dreamt up today. If I want to add a new instance variable to a class, including all accessors, I call this from a workspace:

ManyManyRelation addPropertyName: #oneLot.

And if I want to initialize the property to something, I use this:

ManyManyRelation addPropertyName: #otherLot lazyInitializeWith: [Set new].

The following method goes to the instance side of Class:
addPropertyName: aSymbol
self addInstVarName: aSymbol.
self compileAccessorsFor: aSymbol

And then:
compileAccessorsFor: aSymbol

"Compile instance-variable accessor methods
for the given variable name "
self compileSilently: aSymbol asString , '
^ ' , aSymbol classified: 'access'.
self compileSilently: aSymbol asString , ': anObject'
, aSymbol , ' := anObject' classified: 'access'

You can now call something like ManyManyRelation addPropertyName: #oneLot, and it adds an instance variable to ManyManyRelation, including both accessors.

Let's push it one step further. Hooking into initialize really doesn't feel right––if objects are about modelling the real world, then what exactly corresponds to object initialization? While still not being optimal, the following creates lazy initizers in the accessors.

The following two methods go as instance methods into Class:
addPropertyName: aSymbol lazyInitializeWith: aBlock
self addInstVarName: aSymbol.
self compileAccessorsFor: aSymbol lazyInitializeWith: aBlock

compileAccessorsFor: aSymbol lazyInitializeWith: aBlock 
| initializeCommand decompileString |
decompileString := aBlock decompileString.
initializeCommand := decompileString copyFrom:
2 to: decompileString size - 1.
self compileSilently: aSymbol asString , '
^ ' , aSymbol , ' ifNil: [ ' , aSymbol ,
' := ' , initializeCommand , ']' classified: 'access'.
self compileSilently: aSymbol asString , ': anObject
' , aSymbol , ' := anObject' classified: 'access'

You use it by calling ManyManyRelation addPropertyName: #otherLot lazyInitializeWith: [Set new].

By the way, I would refrain from using (CreateAccessorsForVariableRefactoring variable: #hasMany class: OneManyRelation classVariable: false) execute, which is what happens when you create accessors using OmniBrowsers, the reason being it behaves strangely when the object already responds to a message, most typically like name.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.