Friday, October 23, 2009

14.3. Problems



14.3. Problems


Public inheritance means "is-a." That requires
careful thinking from the part of the programmer to come up with class
hierarchies that really fit this pattern. If you have a class with a method and
a subclass where you realize that method makes no sense, it is beyond the scope
of public inheritance, and it should be a sign of bad design. Languages,
however, prove accommodating.


In C++, you can get away with it by
making the nonsensical method either return an error or throw an exception. The
prototypical example concerns birds (Meyers 2005, item 32):

class Bird {
public:
virtual void fly(); // birds can fly
// ...
};

class Penguin: public Bird { // penguins are birds
public:
virtual void fly() { error("Attempt to make a penguin fly!"); }
// ...
};


C++ programmers may
alternatively hide the offending method:

class Base {
public:
virtual void f()=0;
};

class Derived: public Base {
private:
virtual void f() {
}
};


So, Derived is no longer an
abstract class, but it still does not have an f()
function that can be of any use. Such shenanigans are to be avoided.


In Java, you can get away with it again
by returning an error or throwing an exception; you can also making the
nonsensical method abstract in the subclass, thus making the class hierarchy
abstract from that point downward until you remake the method concrete. Again,
such shenanigans are to be avoided.


The usual way to design around
the problem of inapplicable or irrelevant methods in a class hierarchy is to
redesign that hierarchy. A class hierarchy that would better reflect the
peculiarities of the avian world would introduce a FlyingBird subclass of Birds, for those birds who do fly, and make
Penguin a direct subclass of Bird, and not of a FlyingBird.


In Squeak we find a mere 45 methods that
send the shouldNotImplement
message, which is used when a method inherited by a superclass is not applicable
in the current class. This is a very small proportion of the total number of
methods and objects in Smalltalk, so the language is not fraught with badly
designed class hierarchies. However, even the shouldNotImplement message is actually an implementation. This hints at a
deeper issue in Smalltalk, which is that we do not have real abstract classes or
methods. Methods are abstract by convention; there are no methods that have no
implementation at all.


Instead of using the shouldNotImplement message, we could specify that a given method is the
responsibility of a subclass, which we have seen is what the
subclassResponsibility message is for. The class
from which we send subclassResponsibility is then by convention
abstract. For instance, the Collection
class gives a generic interface for adding and removing objects, but does not
provide implementation, as the implementation varies depending on the subclass
we are dealing with (it could be a dictionary, an array, a list...). Method
add: is to be implemented in the subclasses:

add: newObject
"Include newObject as one of the receiver's elements. Answer newObject.
ArrayedCollections cannot respond to this message."

self subclassResponsibility


This "abstract" definition of add: even allows us to define in the Collection methods that use it, such as add:withOccurrences:,
which is:

add: newObject withOccurrences: anInteger
"Add newObject anInteger times to the receiver. Answer newObject."

anInteger timesRepeat: [self add: newObject].
^ newObject


We can even do without defining
add: at all; add:withOccurrences:
would still be defined as just shown, and Smallktalk will not balk as long as at
runtime the receiving object has add: defined. (By the way,
add:WithOccurrences: is a nice little
implementation of the Strategy pattern.) At the same time, the comment in
add: points out that some subclasses of Collection, those
rooted at its ArrayedCollection subclass,
should not implement the message at all. This, again, is enforced only at
runtime, by using shouldNotImplement:

add: newObject
self shouldNotImplement


There is nothing
inherently wrong in using conventions in programming; part of the art is
mastering conventions. What can be problematic, however, is depending solely on
collections to obtain the required result. Smallktalk will not warn us if we
forget not to implement add: in ArrayedCollection. We will only fail miserably at runtime.


We saw earlier how easy
it is to implement a proxy class in Smalltalk. The truth is, though, that if we
actually want a proxy class that stands as a proxy for only a small number of
methods, things get more complicated. The reason has to do with the absence of
real abstract classes. A proxy class may be a subclass of the class we want to
proxy, in which case it will inherit all the methods of the proxied class, and
not just those methods that we want to proxy. Alternatively, we may employ
latent typing and define in the proxy class only the proxied methods; the
problem is that, since everything is an object, the proxy class will inherit all
the methods of the Object class. Ideally, we
would like a class that proxies two methods to have these two methods only, but
it is not obvious how we can achieve that. The class will have all the methods
inherited by its ancestors. We can resort to tricks to minimize the number of
inherited methods; for example, in some Smalltalk dialects it is possible to
make a class a subclass of nil instead of Object. In this way nothing is inherited, but we need to copy
and paste some necessary methods from Object
(Alpert et al. 1998, p. 215). In Squeak, we can make it a subclass of
ProtoObject instead of Object.


When we start looking into what
really gets inherited from where, things become intricate. As everything in
Smalltalk is an object, including classes, we may wonder what kind of instances
classes are. It turns out that classes are instances of metaclasses; a class's
metaclass is named after the class name with the word "class" after it. For
example, the metaclass of Object is Object class. This is a sensible convention, but it does not really answer
the question, as we may wonder what kind of instances metaclasses are. It turns
out that metaclasses are instances of class Metaclass (no
class suffix here). And Metaclass is an instance of another
class, called Metaclass class (class
suffix included), which again must be an instance of something, so it was made
an instance of Metaclass. (Readers should be
forgiven here for thinking they are reading part of the script of Terry
Gilliam's Twelve Monkeys.)
Things get even more intricate if we take into account the inheritance
relationships as well (we have been talking only about instances until now). There we
find that Object class is a subclass of Class, which is an instance of Class
class
; Class is a subclass of
ClassDescription, and that of Behavior, and that of
Object, and in Squeak we also have
ProtoObject at the apex of the hierarchy.
The situation is presented graphically in Figure 14-2. Note that we do not include any class below Object here. The reader can try and combine Figure
14-1 and Figure 14-2 (don't forget to add the metaclasses, starting from SmallInteger class and working your way up). It may be that we cannot have too
much of a good thing; it is nice to have everything an object, but following the
axiom to all its consequences does not necessarily lead to an easily accessible
structure. Fortunately, most Smalltalk programmers do not need to be concerned
with such matters.


There are other consequences
of the "everything is an object" and "everything is carried out by messages"
dicta that do concern everyday programmers. Any programmer with a minimum of
programming experience would expect that:

3 + 5 * 7 == 38


would be true, but alas, in Smalltalk
the following is true:

3 + 5 * 7 == 56


The reason is that the binary
arithmetic operators are in fact nothing but selectors of the number classes.
They are therefore parsed as any other message, and there is nothing dictating
that one binary message should have precedence over another. True, it makes
sense once you are familiar with the rules of the game. But it still makes one
wonder. It is easy to check arithmetic calculations in a workspace in a
Smalltalk environment, but it might be better if the programmer never really had
to think about such issues.


The Smalltalk environment itself is one of the great innovations
of Smallktalk. Back in the 1980s, when graphical displays were a rarity and most
programming was done on monochrome text terminals, Smalltalk implemented a
graphical user interface (GUI) for a language built on top of a virtual machine.
That might have been ahead of its time. Virtual machines had to wait another
decade for Java to bring them into mainstream programming. GUIs won the battle,
but only when hardware prices came down. In the meantime, an environment with
these requirements was deemed (and perhaps it was) unsuitable for most purposes
at the time. But there might also be something subtler going on.


The Smallktalk
environment is really an ecosystem of classes with which you not only can work, but with which you
have to work. The
only reasonable way to create a new class in Smalltalk is to use the appropriate
browser to find the class you want, from which you will subclass it. You may be
used to your favorite editor and your well-honed skills with build command-line
tools in several languages, but Smalltalk is different. You may love the class
libraries that Smalltalk designers thoughtfully provided for you, but if you
think otherwise, there is not much you can do. It is nice when you are inside,
but you do have to move inside to do anything with it. This may not be what
programmers are prepared to do. It is possible that, after reading the first few
pages here, you rushed to download and install a Smalltalk installation to see
for yourself, and that you threw your hands up in despair when you realized how
different and unfamiliar everything is.



Figure 14-2. Spaghetti à la
Smalltalk




Which hints at why Smallktalk never really
made it to the mainstream. Smalltalk is an adamant language; it did not make
compromises. It defined a new programming model, used concepts that were later
adopted by many other languages, set in many ways an example, and was imitated.
This is not very different from the world of architecture.


 


No comments: