Some peculiarities of the inheritance in Lingo have always seemed to me to be limiting factors. By the standard means we are not able to know (for sure) if a given object implements a specific handler. Also, we do not know (for sure) if a given object has a specific property. We can not find out what is the object type, nor we can inherit in an easy and flexible way a number of parents.
Let me show you the following example with two scripts – “Bullet” and “FlyingObject”. The script called “Bullet” inherits “FlyingObject”.
—————————————–
— parent script "Bullet"
—————————————–
property ancestor
property type
property damage
on new( me, t )
ancestor = script( "FlyingObject" ).rawNew()
callAncestor( #new, me )
type = t
case( t ) of
5.45: damage = 2.7
7.62: damage = 3.1
end case
return me
end new
on shoot( me )
nothing
end shoot
on explode( me )
nothing
end explode
—————————————–
— parent script "FlyingObject"
—————————————–
property speed
on new( me, s )
speed = s
return me
end new
on fly( me )
nothing
end fly
on collide( me )
nothing
end collide
If we execute the following lines in the Message window,
myBullet = script("Bullet").new()
trace( myBullet.handlers() ) — [#new, #shoot, #explode]
we shall see that handlers() function “sees” only the handlers which are defined in the “Bullet” script. If we decide to “reel” all the properties of myBullet and to check their values, we shall discover that we can see only the properties defined in the “Bullet” script.
on showProperties()
myBullet = script("Bullet").new( 5.45 )
repeat with i = 1 to count( myBullet )
p = myBullet.getPropAt( i )
v = myBullet.getAprop( p )
put p & ": " & v
end repeat
end showProperties
— "ancestor: " <offspring "FlyingObject" 3 85c751c>
— "damage: " 2.7000
— "type: " 5.4500
Not the last, in Lingo we can not find out the exact type of a given custom object. The ilk() function always returns #instance.
put ilk( script( "Bullet" ).new() ) — #instance
put ilk( script( "FlyingObject" ).new() ) — #instance
The “ExtendedObject” script tries to solve some of the above mentioned problems. From the moment when a given script inherits “ExtendedObject”, we can use the following new or modified* functions:
-- extends() -- implements() -- typeOf() -- is() -- properties() -- hasProperty() -- getHandlers() -- hasHandler()
So, how to take an advantage of the “ExtendedObject” script? Simply every one of the scripts, which we plan to be inherited as a part of the “multiple inheritance” or the scripts, which we like to have the above described functionality must inherit the “ExtendedObject” script. Let us look back at the previous example. In the constructor of the “Bullet” script we delete the lines:
ancestor = script( "FlyingObject" ).rawNew()
callAncestor( #new, me )
and on their place we write:
ancestor = script("ExtendedObject").rawNew()
callAncestor( #new, me, me )
me.extends( script("FlyingObject").new( 1000, 3.5 ) )
In the “FlyingObject” constructor we add the following lines:
on new( me, s )
ancestor = script("ExtendedObject").rawNew()
callAncestor( #new, me, me )
speed = s
return me
end new
Now the things look different:
myBullet = script("Bullet").new( 7.62 )
trace( myBullet.getHandlers() ) — [#new, #shoot, #explode, #dispose, #typeOf, #implements, #is, #getHandlers, #properties, #hasProperty, #hasHandler, #extends, #fly, #collide]
trace( myBullet.hasHandler( #fly ) ) –TRUE
trace( myBullet.properties() ) — [#ancestor, #type, #damage, #successor, #mass, #speed]
trace( myBullet.hasProperty( #mass ) ) –TRUE
trace( myBullet.typeOf() ) — #Bullet
trace( myBullet.is( #bullet ) ) — TRUE
trace( myBullet.is( #FlyingObject ) ) — TRUE
trace( myBullet.is( #VisualItem ) ) — FALSE
trace( myBullet.implements() )– [#Bullet, #ExtendedObject, #FlyingObject]
The new extends() function makes it possible for us to make “multiple inheritance”. Let me introduce another script in this example – “VisualItem”. We add a line to the “Bullet” script constructor:
ancestor = script("ExtendedObject").rawNew()
callAncestor( #new, me, me )
me.extends( script("FlyingObject").new( 1000, 3.5 ) )
me.extends( script( "VisualItem" ).new( 5, 10, color(255,0,0) ) )
As “VisualItem” will be a part of the “multiple inheritance”, it also inherits “ExtendedObject”. We add the following lines to the “VisualItem” constructor:
—————————————–
— parent script "VisualItem"
—————————————–
property ancestor
property width
property height
property color
on new( me, w, h, c )
ancestor = script( "ExtendedObject" ).rawNew()
callAncestor( #new, me, me )
width = w
height = h
color = c
return me
end new
on render( me )
nothing
end render
We can try again in the Message window:
myBullet = script("Bullet").new( 7.62 )
trace( myBullet.getHandlers() ) — [#new, #shoot, #explode, #dispose, #typeOf, #implements, #is, #getHandlers, #properties, #hasProperty, #hasHandler, #extends, #fly, #collide, #render]
trace( myBullet.hasHandler( #fly ) ) –TRUE
trace( myBullet.hasHandler( #render ) ) –TRUE
trace( myBullet.properties() ) — [#ancestor, #type, #damage, #successor, #mass, #speed, #width, #height, #color]
trace( myBullet.hasProperty( #mass ) ) –TRUE
trace( myBullet.typeOf() ) — #Bullet
trace( myBullet.is( #bullet ) ) — TRUE
trace( myBullet.is( #FlyingObject ) ) — TRUE
trace( myBullet.is( #VisualItem ) ) — TRUE
trace( myBullet.implements() ) — [#Bullet, #ExtendedObject, #FlyingObject, #VisualItem]
This is how the final result should look in the Object Inspector:
Finally, please, pay attention that there is a possibility a two-way communication to be created between the child and the ancestor. If you need this functionality, you have to pass the “me” parameter when you call the constructor of the ancestor script.
ancestor = script( "ExtendedObject" ).rawNew()
callAncestor( #new, me, me )
When we end working with the myBullet object, we call the dispose() method, which destroys the whole “chain of ancestors”.
There is the script itself:
[code lang=”lingo” gutter=”true” toolbar=”true” collapse=”false”]
— ExtendedObject parent script
— Author: Vladin M. Mitov
— August, 2011
— https://www.ed-multimedia.com
— Serves to be inherited by every script, which is a part of the "chain of ancestors".
— Adds the following functionality:
— typeOf() – Returns the "type" of the object as a symbol.
— implements() – Returns a list of the types of all the object’s ancestors.
— is() – Checks if the object inherits a given script;
— Example: put myObj.is( #VisualItem )
— getHandlers() – Returns a list of all the public methods of all the ancestors down the chain.
— properties() – Returns a list of all properties of all ancestors down the chain.
— hasProperty() – Checks if the object has a such a property.
— hasHandler() – Checks if the object has such a handler.
— extends() – Adds an ancestor to the chain.
—————————————————————
— PROPERTIES
—————————————————————
property ancestor
property successor
—————————————————————
— CONSTRUCTOR / DESTRUCTOR
—————————————————————
on new( me, s )
successor = s
return me
end new
on dispose( me )
if( ilk ( ancestor ) = #instance or \
ilk ( ancestor ) = #script ) then callAncestor( #dispose, [ me ] )
successor = void
end dispose
—————————————————————
— PUBLIC METHODS
—————————————————————
on typeOf( me )
return me._getType( me )
end typeOf
on implements( me, asorted )
retval = list( #ExtendedObject )
tmp = max( successor, me )
repeat while not voidP( tmp )
if( ilk( tmp.getAprop( #ancestor ) ) = #instance ) then
if( not( tmp.ancestor.handlers().getPos( #typeOf ) = 0 ) ) then
typ = tmp.typeOf()
end if
else
typ = me._getType( tmp )
end if
if( retval.getPos( typ ) = 0 ) then
retval.append( typ )
end if
tmp = tmp.getAProp( #ancestor )
end repeat
if ( asorted = TRUE ) then
retval.sort()
end if
return retval
end implements
on is( me, thetype )
myTypes = me.implements()
return ( myTypes.getPos( thetype ) <> 0 )
end is
on getHandlers( me, asorted )
retval = list()
tmp = max( successor, me )
repeat while not voidP( tmp )
hndlrs = tmp.handlers()
repeat with i = 1 to count( hndlrs )
hand = hndlrs[ i ]
if( string( hand ) starts "_" ) then next repeat
if( retval.getPos( hand ) = 0 ) then
retval.append( hand )
end if
end repeat
tmp = tmp.getAProp( #ancestor )
end repeat
if ( asorted = TRUE ) then
retval.sort()
end if
return retval
end getHandlers
on properties( me, asorted )
tmp = max( successor, me )
retval = list()
repeat while not voidP( tmp )
repeat with i = 1 to count( tmp )
propname = tmp.getPropAt( i )
if( retval.getPos( propname ) = 0 ) then
retval.add( propname )
end if
end repeat
tmp = tmp.getAProp( #ancestor )
end repeat
if ( asorted = TRUE ) then
retval.sort()
end if
return retval
end properties
on hasProperty( me, propname )
tmp = max( successor, me )
repeat while not voidP( tmp )
repeat with i = 1 to count( tmp )
if( tmp.getPropAt( i ) = propname ) then
return TRUE
end if
end repeat
tmp = tmp.getAProp( #ancestor )
end repeat
return FALSE
end hasProperty
on hasHandler( me, handlername )
tmp = max( successor, me )
repeat while not voidP( tmp )
hndlrs = tmp.handlers()
repeat with i = 1 to count( hndlrs )
if( hndlrs[ i ] = handlername ) then
return TRUE
end if
end repeat
tmp = tmp.getAProp( #ancestor )
end repeat
return FALSE
end hasHandler
on extends( me, obj, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z )
if voidP( ancestor ) then
case( ilk( obj ) ) of
#script:
ancestor = obj.rawNew()
callAncestor( #new, [me], a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z )
otherwise:
ancestor = obj
end case
else
case( ilk( obj ) ) of
#instance, #script:
callAncestor( #extends, [me], obj, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z )
otherwise:
nothing
end case
end if
end extends
—————————————————————
— PRIVATE METHODS
—————————————————————
on _getType( me, obj )
the itemdelimiter = quote
if( ilk( obj ) <> #instance ) then return #undefined
return symbol( string( obj ).item[ 2 ] )
end _getType
[/code]
Extended Object (5328 downloads )