Quantcast

Smalltalk OO design/philosophy question

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
12 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Smalltalk OO design/philosophy question

Jeff M.
In C++, the constructor initializes default values for data. If I did:

class A {
  A() { /* init something */ }
};

class B : public A {
  B() { /* init more something */ }
};

Creating an instance of B will call A::A() and B::B(). This is handy,
of course, because if someone else were to program B, without access to
the source for A, they don't need to know to call some previous
initialization function - it just happens.

The closest thing to a "constructor" that I've found in Smalltalk (by
convention) is the #initialize method. So, a #new class method might
look something like this:

A class>>new
  ^(super new) initialize; yourself

The problem I have, though, is that if I subclass A and make B, and B
wants to initialize additional data from A, I have to do this:

B>>initialize
  super initialize.
  "init more here"

And while there is nothing "bad" about this, it's just not what I'm
used to, and I'm sure some day in the future, the next time I subclass
A (or B), I'll forget to do that and problems will arise.

So - my question: is the above typically what is done (calling super's
#initialize) or is there something else that is common-place to
accomplish the same thing?

Also, something that I've been noticing (and getting ready to blog
about), is that many of the habits I'm in (like the above) feel like
I'm trying to "guide" the programmer. And this feels contrary to the
Smalltalk philosophy of empowering. Perhaps this is another one of
those instances.

Thoughts, comments, and suggestions welcome as always! :-)

Jeff M.


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Smalltalk OO design/philosophy question

Schwab,Wilhelm K
Jeff,

> In C++, the constructor initializes default values for data. If I did:
>
> class A {
>   A() { /* init something */ }
> };
>
> class B : public A {
>   B() { /* init more something */ }
> };
>
> Creating an instance of B will call A::A() and B::B(). This is handy,
> of course, because if someone else were to program B, without access to
> the source for A, they don't need to know to call some previous
> initialization function - it just happens.
>
> The closest thing to a "constructor" that I've found in Smalltalk (by
> convention) is the #initialize method. So, a #new class method might
> look something like this:
>
> A class>>new
>   ^(super new) initialize; yourself
>
> The problem I have, though, is that if I subclass A and make B, and B
> wants to initialize additional data from A, I have to do this:
>
> B>>initialize
>   super initialize.
>   "init more here"
>
> And while there is nothing "bad" about this, it's just not what I'm
> used to, and I'm sure some day in the future, the next time I subclass
> A (or B), I'll forget to do that and problems will arise.

True, but as a friend of mine would say, that's job security (as long as
it doesn't happen too often or in the wrong setting<g>).  Many of the
things that I tend to jam into instance side #initialize would cause
very obvious problems if missed, so it tends to get noticed quite quickly.


> So - my question: is the above typically what is done (calling super's
> #initialize) or is there something else that is common-place to
> accomplish the same thing?

That is the intended use of #initialize, so I think you are fine.  Just
be careful about the same method on the class side; there you _not_
super send unless you really know what you are doing (and probably not
even then<g>).  As an example, you have some classes

Object
   Animal
     Reptile

and then you add Lawyer (guess where it belongs<g>), is it really
appropriate to initialize Reptile, Animal and Object (the classes
themselves), just because you have subclassed Reptile?

On the instance side, the rule is reversed: super send #initialize
unless you have reason not to do so, in which case your choice of super
class is perhaps questionable anyway.  With Smalltalk's dynamic typing
and method lookup, there is no need to share a common base class just to
be polymorphically interchangeable.  OTOH, it is still a nice way to
ensure same, but it is not forced on us by the compiler.

I will stop there; please ask for clarification if needed.


> Also, something that I've been noticing (and getting ready to blog
> about), is that many of the habits I'm in (like the above) feel like
> I'm trying to "guide" the programmer. And this feels contrary to the
> Smalltalk philosophy of empowering. Perhaps this is another one of
> those instances.
>
> Thoughts, comments, and suggestions welcome as always! :-)

I see nothing wrong with building things that are easy to use, and/or
slightly difficult to abuse.  See #subclassResponsibility as an example
- it is designed and used to make something that will break if the
required method is not available.  Obviously it does not address your
problem; I simply cite it as evidence that Smalltalkers are not opposed
to some reasonable idiot proofing.

Have a good one,

Bill

--
Wilhelm K. Schwab, Ph.D.
[hidden email]


acg
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Smalltalk OO design/philosophy question

acg
In reply to this post by Jeff M.
Jeff M. wrote:
..
> Also, something that I've been noticing (and getting ready to blog
> about), is that many of the habits I'm in (like the above) feel like
> I'm trying to "guide" the programmer. And this feels contrary to the
> Smalltalk philosophy of empowering. Perhaps this is another one of
> those instances...

Hi Jeff.
You're probably right in theory, but in practice I noticed that
Smalltalk inheritance is more 'wide' than 'deep'. Take a look at
Dolphin's Class Heirarchy Diagram an see how shallow the whole system
under Object class is.

The result...most of my inheritance is shallow. But when I sometimes
forget to put something like 'super createComponents' in
#createComponents, or 'super model: aModel' in #model: , the debugger
lets me know by showing me stuff is 'nil' than alerts my brain to say
'whoops I forgot to initialize some stuff' than is not in my present
class but probably in its parent or above.

Also recall the #initialize is no more special than any other method
name. By that I mean there are times when I include a instance side
#init which I use for the additional initialization to instance
variables or whatever. So if I were to use 'BClass new' which really
did  '^(super new) initialize', I might have coded 'BClass new init' to
separate initialize some stuff, instead of an overriding #initialize.

Hope that makes sense.
ACG


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Smalltalk OO design/philosophy question

Chris Uppal-3
In reply to this post by Jeff M.
Jeff,

> And while there is nothing "bad" about this, it's just not what I'm
> used to, and I'm sure some day in the future, the next time I subclass
> A (or B), I'll forget to do that and problems will arise.

IIRC, one of the Code Mentor's checks is for methods which override superclass
methods, but don't supersend.  Maybe that would help.

But, it's really nothing to do with initialisation per se, just that the rather
unhealthily close relationship between any class and its superclass (in any
form of OO, including "classless" languages) is exposed more clearly during
initialisation.  Whenever you subclass something, and especially when you
override existing methods, then you /have/ to consider how that affects and is
affected by code inherited from the superclass[*].  That's the price you pay
for being /able/ to inherit code from the superclass...

C++'s idea of turning off inheritance during initialisation (the vtable not
fully set up until the constructors have completed) is very interesting, if a
bit hacky.  It also pretty-much commits the language to a vtable-based
implementation too (not the most efficient way of doing dynamic dispatch), or
else to some seriously baroque manoeuvres to switch objects' classes during
initialisation.  But still, it shows that initialisation is a rather special
time in an object's life.


> So - my question: is the above typically what is done (calling super's
> #initialize) or is there something else that is common-place to
> accomplish the same thing?

I think that's the most typical approach.  You'll notice that you have the same
responsibility to supersend in many of the MVP boilerplate initialisation
methods (such as #onViewOpened and #createComponents), and when you forget, it
breaks...

As far as instance variable initialisation goes, there is a school of thought
that using lazy initialisation of instvars in "getter" methods is better than
an explicit initialisation step.  I dislike like that approach myself (I have
nothing against lazy initialisation /provided/ there is a functional reason for
it, but I won't clutter up the rest of my code just to avoid an explicit
#initialize -- especially when I consider that a good #initialize method has
rather a high documentary value).

Other approaches are occasionally reasonable.  If a small hierarchy of classes
has been co-designed (and is not intended to be particularly extendible
thereafter -- not an atypical situation); then the base class's #initialise can
be a "driver" method which is not itself intended to be overridden, but which
calls various hook methods which are. Something like:

    intitialize
        self
            intializeSearchPaths;
            initializeFontTables;
            initializeCharacterMaps;
            initializeGraphics.

where each of the intializeXxx methods is empty and/or #subclassResponsibility.

Another approach -- much more extreme -- would be to use reflection, but it's
hard to imagine that being worthwhile for something as relatively simple as
object initialisation. (It can sometimes be very useful for initialising
complex systems, but it seems overkill just as a way of avoiding the odd
super-send.)

So, for the most part, you just have to accept that supersending #initialize is
part of life...


BTW, I have found it a good idea to split initialisation into two halves, so
that the general pattern of my #initialize methods is
    intitialize
        someInstvar := ....
        antherInstvar := ....
        super initialize.
        ... more initialization ...

where I initialize any instvars that need it first (using only constants, or
constant-valued methods); then super-send #initialize; and only after that has
completed do any more complex initialisation which might depend on
functionality inherited from the superclass.

    -- chris

[*] BTW, on the subject of the non-trivial consequences of subclassing, and
harking back to an earlier conversation about hacking stuff out quickly in
Smalltalk without getting bogged down in class hierarchy design.  Say you have
an existing class A, and that you now realise that you want a new class B; you
don't yet know exactly what B will do, or how it will work, but you assume that
it'll be pretty closely related to A.  The "obvious" approach would be to
refactor A into AbstractABishThing, plus a specialisation of that to make A.
Then start writing B as another specialisation of AbstractABishThing.  Problem
with that is that you have to design the subclass-superclass relationship
before you know exactly what is going to be needed.   Quite probably I'm the
last to have realised this, but it can be simpler and more effective to start
from the opposite direction.  First create B and make it a clone of A (but not
otherwise related).  Then rework B to do the new job (using whatever tools and
techniques suit you).  Don't think much (or at all) about preserving
similarities with A.  Once B is done and feels "right", only then create
AbstractABishThing, make A and B into subclasses, and start looking for
structure which should be moved up into the superclass.  This last step is
formally unnecessary, and can be postponed indefinitely.


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Smalltalk OO design/philosophy question

Jeff M.
On Oct 6, 7:53 am, "Chris Uppal"
<[hidden email]> wrote:
>
> As far as instance variable initialisation goes, there is a school of thought
> that using lazy initialisation of instvars in "getter" methods is better than
> an explicit initialisation step.  I dislike like that approach myself ...

I agree. I thought about this, but typically I hate lazy initialization
because it forces other code to know that the data might not yet be
initialized. In this particular case, I have an OrderedCollection that
needs created. If everywhere else in the class I need to put:

animators ifNotNil: [...]

That sucks. I'd rather just know it's a collection and do stuff with
it.

> Other approaches are occasionally reasonable.  If a small hierarchy of classes
> has been co-designed (and is not intended to be particularly extendible
> thereafter -- not an atypical situation); then the base class's #initialise can
> be a "driver" method which is not itself intended to be overridden, but which
> calls various hook methods which are. Something like:
>
>     intitialize
>         self
>             intializeSearchPaths;
>             initializeFontTables;
>             initializeCharacterMaps;
>             initializeGraphics.

This definitely was another option I considered. However, in the end,
this is just postponing the problem instead of really dealing with it.
Someday, I will want to override #initializeGraphics, and I'm back as
square one. So I decided against this approach.

> Another approach -- much more extreme -- would be to use reflection, ...

I'm not quite sure where you are going with this. I'm just starting to
realize much of the reflective power in Smalltalk, and so while this
statement might be obvious to you (and others in this newsgroup), it
isn't to me. Could you expand on this?

> So, for the most part, you just have to accept that supersending #initialize is
> part of life...

The conclusion I've come to. :-)

> [*] BTW, on the subject of the non-trivial consequences of subclassing, and
> harking back to an earlier conversation about hacking stuff out quickly in
> Smalltalk without getting bogged down in class hierarchy design....

Chris, I thank you again for your insights. This is one case where I do
know what I want, I'm just not sure of the exact implementation (since
I'm still learning Smalltalk). I typically don't like to go into a lot
of detail in my posts, since I generally assume this group isn't game
developers. The more detail I give, I run the risk of getting very
off-the-wall answers. Of course, conversely, the less detail I give,
the odds go down that I get an answer that even remotely comes close to
helping me solve my problem. :-)

Thanks again.

Jeff M.

P.S. I hope to put up some good examples of the game engine for those
interested within a week or so. There's quite a lot done and working.
Anyone here should feel free to email me with questions if interested
in the progress.


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Smalltalk OO design/philosophy question

Bill Dargel
Jeff M. wrote:
> I agree. I thought about this, but typically I hate lazy initialization
> because it forces other code to know that the data might not yet be
> initialized. In this particular case, I have an OrderedCollection that
> needs created. If everywhere else in the class I need to put:
>
> animators ifNotNil: [...]
>
> That sucks. I'd rather just know it's a collection and do stuff with
> it.

Not quite. Lazy initialization is usually handled by the one getter
method. Something like:

        animators
                ^animators ifNil: [animators := ...]

Then everywhere else in the class you simply reference it as "self
animators" instead of the direct variable reference "animators". So not
as bad as you were implying.

Of course the getter method vs. direct variable access debate is
everlasting. I won't get into it, as I tend to use both, depending upon
how the mood strikes me. ;-)

--
Bill Dargel            [hidden email]
Shoshana Technologies
100 West Joy Road, Ann Arbor, MI 48105  USA


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Smalltalk OO design/philosophy question

Chris Uppal-3
In reply to this post by Jeff M.
Jeff

> > Another approach -- much more extreme -- would be to use reflection, ...
>
> I'm not quite sure where you are going with this. I'm just starting to
> realize much of the reflective power in Smalltalk, and so while this
> statement might be obvious to you (and others in this newsgroup), it
> isn't to me. Could you expand on this?

Well, for cases where it's appropriate to use reflection at all, one usually
can get away with simple uses of methods like #respondsTo: and #perform (on
the instance side) and #canUnderstand: or #includesSelector: (on the class
side).

But in this case, we want to bypass normal method lookup, so we have to use a
rather less common technique.  Here's a version of #new which directly invokes
each definition of #init that the new object has or inherits:

=================
    oddlyNew
        | new |

        new := self basicNew.

        self withAllSuperclasses reverseDo:
            [:each || initMethod |
            initMethod := each compiledMethodAt: #init ifAbsent: [nil].
            initMethod ifNotNil: [:it | it value: new withArguments: #()]].

        ^ new.
=================

Note that #oddlyNew can be defined in the base class, and it will work
correctly when inherited by subclasses -- indeed, one could define it on the
instance-side of Behaviour, and then it would be inherited by /everything/...

As I said before, I can't imagine a situation where #oddlyNew would pay for
itself (not least because instead of remembering to supersend #initialize, one
would have to remember /not/ to supersend #init); but it is an interesting
technique to have available in case of need (not that I have ever needed it
myself).

    -- chris


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Smalltalk OO design/philosophy question

Tim M
In reply to this post by Bill Dargel
> Of course the getter method vs. direct variable access debate is
> everlasting. I won't get into it, as I tend to use both, depending
> upon how the mood strikes me. ;-)

I heard James Robertson give a good argument for using lazy when writing
web applications, it went along the lines of if you want to patch your live
application, the lazy approach let you make corrections on the fly without
having to mutate instances already in existance.

It was an interesting point.

Tim


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Smalltalk OO design/philosophy question

Schwab,Wilhelm K
Tim M wrote:

>> Of course the getter method vs. direct variable access debate is
>> everlasting. I won't get into it, as I tend to use both, depending
>> upon how the mood strikes me. ;-)
>
> I heard James Robertson give a good argument for using lazy when writing
> web applications, it went along the lines of if you want to patch your
> live application, the lazy approach let you make corrections on the fly
> without having to mutate instances already in existance.
>
> It was an interesting point.

For _additions_ I agree, but if something is to be changed, one has to
reset the affected aspect(s) of any live instances so the revised lazy
init can run.

Unrelated, part of me thinks that anything important enough to be
patched live, is important enough to be confined to tests machines until
all of the tests pass.

Have a good one,

Bill


--
Wilhelm K. Schwab, Ph.D.
[hidden email]


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Smalltalk OO design/philosophy question

Chris Uppal-3
In reply to this post by Tim M
Tim,

> I heard James Robertson give a good argument for using lazy when writing
> web applications, it went along the lines of if you want to patch your
> live application, the lazy approach let you make corrections on the fly
> without having to mutate instances already in existance.

It's an argument for lazy initialisation, but I'm not so sure that it's a good
one.

I quite often patch the shape of already live systems (not deployed, though --
but the effect is the same) and don't routinely use lazy initialisation.

Here's an example. Say you have a bunch of complex objects, X, with instvars a,
b, and c (nice intention-revealing names ;-), and have decided to replace them
with LookupTable.  So:

1) Add an instvar for the LookupTable to X -- map, say.

2) Add a method to X:

    hack
        map := (LookupTable new)
                        at: #A put: a;
                        at: #B put: b;
                        at: #C put: c;
                        yourself.

3) X allSubinstances do: [:each | each hack].

4) Review and modify all references to a, b, and c.

5) Remove a, b, and c, and the #hack method.

Sorted.

Obviously more, or less, intricate variations on the same theme would be
appropriate for more, or less, demanding shape changes.

    -- chris


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Smalltalk OO design/philosophy question

Tim M
In reply to this post by Schwab,Wilhelm K
Hi Bill,

> Unrelated, part of me thinks that anything important enough to be
> patched live, is important enough to be confined to tests machines
> until all of the tests pass.

I agree - it makes me feel a bit uneasy too, however it was a point that
made me stop and pause.

I see that Chris has shown how easy it is to do with live instances anyway
(e.g. you can also live add a hack method and then update all instances).

I probably end up using Private setter/getters more out of laziness as there
is the menu item that generates them for me and then its easy in my class
construction method (by the way, whats the Smalltalk terminology for #new
like methods?) to do
 
  ^self new firstName: x; lastName: y; yourself

vs.
  ^self new initialiseFirst:x last y

But I really should create a menu item to do that second option as looks
conceptually better - but then I keep flip/flopping between the two. I noticed
that Blaine Buxton wrote about this in his blog - he said the second option
stopped him leaking private details of classes - i.e. put behavior where
it should go (which is something I noticed a lot in Java, and its where Mock
Objects came from).



Tim


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Smalltalk OO design/philosophy question

Schwab,Wilhelm K
Tim,

>> Unrelated, part of me thinks that anything important enough to be
>> patched live, is important enough to be confined to tests machines
>> until all of the tests pass.
>
> I agree - it makes me feel a bit uneasy too, however it was a point that
> made me stop and pause.
> I see that Chris has shown how easy it is to do with live instances
> anyway (e.g. you can also live add a hack method and then update all
> instances).

Again, please note that the argument as-given applies only to new
instance variables.


> I probably end up using Private setter/getters more out of laziness as
> there is the menu item that generates them for me and then its easy in
> my class construction method (by the way, whats the Smalltalk
> terminology for #new like methods?) to do
>
>  ^self new firstName: x; lastName: y; yourself
>
> vs.  ^self new initialiseFirst:x last y
>
> But I really should create a menu item to do that second option as looks
> conceptually better - but then I keep flip/flopping between the two.

Take a look at my CodeGenerator goodie.  It includes a constructor
method generating IDE extension (note that you will have to explicitly
activate it before it will work, and I believe it will work only in
shells opened after that??) for the CHB (I have never bothered to extend
it to the SB) that will launch a constructor generator.  Make a backup,
and give it a try on a dummy class with a several instance variables.
It will optionally create #initialize, #new (the form of which depends
on whether you asked for #initialize), and class and instance side
versions of a keyword method based on the instance variables you select.
  There is also a way to generate test case methods, but it is not quite
as slick, mostly because of the way it gets the TestCase subclass.


Have a good one,

Bill


--
Wilhelm K. Schwab, Ph.D.
[hidden email]


Loading...