CONTENTS | PREV | NEXT
14D. methods and virtual methods
--------------------------------
A method is much like a PROC, only now it's part of an OBJECT. It
also allows you to exploit Polymorhism on objects, as we'll see
below. definition of a method:
OBJECT blerk PRIVATE
x:PTR TO blerk, y:INT, z
ENDOBJECT
PROC getx() OF blerk IS self.x
the 'OF blerk' part tells the compiler it belongs to the object
'blerk'. Note that apart from the 'OF' the syntax is completely
like a 'PROC', however, it can't be invoked as one, and also
functions quite differently.
'self' is a local variable that is available in every method, and
is a pointer to the object that the method belongs to (in this
case 'self:PTR TO blerk'). This function just returns the value
of the x field of blerk, which actually makes sense, given that
this allows you to later change whatever x represents.
we may call methods similar to object selections ".":
DEF a:PTR TO blerk
NEW a
...
a.getx() -> invoke method getx() on object a
in this example, upon invocation `a' becomes the value of `self'
during the execution of getx().
so far the use of methods has been nice, but hasn't show us its
real power, which only comes when used with inheritance.
If I inherit an object that has methods, I automatically get
those in the new object:
OBJECT burp OF blerk PRIVATE -> same as blerk, + extra field
prut:INT
ENDOBJECT
DEF b:PTR TO burp
NEW b
...
b.getx() -> same method
The interesting thing is now, that instead of inheriting a method,
you may also redefine it:
PROC getx() OF burp IS self.x+1
(it goes without saying that we may also add new methods)
so where appropriate, we can choose to modify slightly the
behaviour of methods we get from other objects, while the
interface to it (i.e. 'getx()') stays the same. Not only
does this allow us to reuse code selectively, we can also make use
of polymorhism:
PROC dosomething(o:PTR TO blerk)
...
o.getx()
...
ENDPROC
dosomething(a)
dosomething(b)
we may call that PROC with both a and b, since both are compatible
with a blerk object. But which of the two method implementations of
getx() is invoked at o.getx()? Answer: both are. Method calls in E
are what virtual method calls are in other languages: they dynamically
act on the real type of an object (o) and call the appropriate method.
A more clear example:
-> classical OO polymorphic example
OBJECT loc
PRIVATE xpos:INT, ypos:INT
ENDOBJECT
OBJECT point OF loc
PRIVATE colour:INT
ENDOBJECT
OBJECT circle OF point
PRIVATE radius:INT
ENDOBJECT
PROC show() OF loc IS WriteF('I''m a Location!\n')
PROC show() OF point IS WriteF('I''m a Point!\n')
PROC show() OF circle IS WriteF('I''m a Circle!\n')
PROC main()
DEF x:PTR TO loc,l:PTR TO loc,p:PTR TO point,c:PTR TO circle
ForAll({x},[NEW l,NEW p,NEW c],`x.show())
ENDPROC
In the above, x is a PTR TO loc, so many would expect x.show() to
write 'I'm a Location' three times, but instead it writes the
right string for each object.
If one would write this example in a non-OO language, one would need
a SELECT for every operation like show(), testing some value
present in the object to see what it is. If I would add a new shape
to this, say:
OBJECT ellipse OF circle
I would need to change all SELECTs throughout my app to account for
it. With object-polymorhism, I can just write a show() method, and
ALL code throughout my app that calls x.show() will act correctly
when x is a PTR TO ellipse, even without recompilation!
It's difficult to show why this is powerful in a few examples, and
best to discover this is using it in real life apps. and: like I said,
read a book on it.
How does polymorphism work?
In the above examples, it's clear the compiler can't know
what method it's going to call. that's why E uses a 'class
object', and every object created gets a ptr to this object.
In the class object, all information is stored that is common
for all objects of that type, such as pointers to methods.
when the E compiler sees a call like x.show(), instead of
looking directly at the show() that belongs to the type of
x (i.e. loc), it will generate code to retrieve the pointer
to the show() method from loc's class object. since the
class object for point looks the same as loc (only maybe
slightly larger), that code will automatically call point's
show(), when x is really a point object. This is sometimes
called runtime binding.
Objects that have methods therefore always are 4 bytes larger
than you expect them to be, since they contain a class object
pointer. This pointer is automatically installed by NEW, which
is the reason _currently_ NEW is the only way to create such
an object (!).
If a method is declared with the sole purpose of enabling
subclasses to redefine it (this type of class is known as
a virtual baseclass in some languages), one may use EMPTY:
PROC bla() OF obj IS EMPTY
it may then be redefined in subclasses. Since a programmer
might not implement all methods at once, it is not an error
when the above method is executed. it will just return 0 or NIL.
One may effectively add methods to system OBJECTs:
OBJECT mygadget OF gadget -> from intuition!
-> extra fields here
ENDOBJECT
PROC creategadget() OF mygadget IS ...
A pointer to an object such as mygadget above is then compatible
with a normal gadget pointer, i.e. it may be added directly to a
window etc.