making a constructor in Pharo or Smalltalk
Asked Answered
G

2

6

I want to implement a small class in Pharo so I have done this:

Object subclass: #Person
    instanceVariableNames: 'name age'
    classVariableNames: ''
    category: 'Test'

and I want to simulate a constructor so I have made a method like the following:

newName:aName newAge:aAge
    "comment stating purpose of message"

    name:=aName.
    age:=aAge.
    ^self.

but when I want to call it from the playground in Pharo like this:

objPerson:=Person newName:'Carl' newAge: 10.

it is not recognized by Pharo, what am I doing wrong?

Gesticulative answered 27/4, 2016 at 1:36 Comment(0)
M
9

The expression Person newName: 'Carl' newAge: 10 is a message to the class object Person. Therefore you have to implement it on the class side of Person.

Your code needs to be tweaked like this

Person class >> newName: aName newAge: anAge
  | person |
  person := self new.
  person name: aName.
  person age: anAge.
  ^person

Note that in the code above self refers to the class because the method is on the class side. But since person is an instance of Person, the messages name: aName and age: anAge have to be defined on the instance side.

Therefore, on the instance side, you need to add the two setters:

Person >> name: aString
  name := aString

Person >> age: anInteger
  age := anInteger

With these three methods you should be able to run your example.


Some additional comments on the coding style:

Firstly, I would have chosen a different selector for the "constructor" method (in Smalltalk we call these "instance creation" methods). For instance,

Person class >> named: aString age: anInteger
  ^self new name: aString; age: anInteger

Secondly, there is no need to use the temporary person for the newly created instance as the expression self new already refers to such an instance.

Finally, note the use of the cascade syntax in

^self new name: aString; age: anInteger

which means that the message age: anInteger will be sent to the same receiver that name: aString, which in this case happens to be the new instance returned by self new.

Muire answered 27/4, 2016 at 1:59 Comment(0)
D
7

Although in general I agree with Leandro's answer, it exposes accessors to the private properties.

Kent Beck in his Smalltalk Best Practice Patterns (which I highly recommend), proposes to use set as a prefix for the instance-side constructor, e.g.:

"add to class side to 'instance creation' protocol"
Person>>class name: aName age: anAge
    ^ self new
        setName: aName age: anAge;
        yourself

"add to instance side to 'initialization' protocol"
Person>>setName: aName age: anAge
    name := aName.
    age := anAge.

This way you can control whether you will or will not expose the properties.

Or you could stick with your original naming and just add new, which you forgot.

objPerson := Person new newName:'Carl' newAge: 10.

Deer answered 27/4, 2016 at 8:44 Comment(7)
Good point. However, I would use the #name:age: selector instead of #setName:age:. Other languages need to prepend set or get to setters and getters because they have no other way of distinguishing between them. In Smalltalk the colon is part of the selector and then the use of these prefixes becomes superfluous.Muire
@LeandroCaniglia that is precisely the reason why it is prefixed — to distinguish it from a regular setter (which is not prefixed). You are not meant to call this method again.Deer
I value the idea of having instance-side constructors and also the idea of distinguishing them further from regular setters. However, I prefer conveying the distinction from the classification of the method rather than attaching set to the selector.Muire
using set as prefix is bad, because is just a way of hiding the fact that you are not doing anything but... setting variables. I this case, I would use #initializeName:age: as a selector... then you can do something like: #name: aName age: aNumber ^ self basicNew initializeName: aName age: aNumber.Masterwork
Anyaway this is just a discussion about style and not the most important (and the question posted it self)... so I think Leandro AND Peter answers are both correct :)Masterwork
So there is no way to force users of a class to use one constructor? I mean you can still call (Person new) and then you end up with an object that has not been fully initialized / that has invalid state. Meh.Throb
@Throb you can implement new method on Person class and throw an error instead. Smalltalk is a lot about conventions (e.g. no private methods either), not about restricting users.Deer

© 2022 - 2024 — McMap. All rights reserved.