Extended Object

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:

Show code

— 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

 

Extended Object (4799 downloads)
 
Comments

No comments yet.

Leave a Reply