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:

-- ExtendedObject parent script
-- Author: Vladin M. Mitov
-- August, 2011
-- http://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 ObjectExtended Object ( 37 )
1 Star2 Stars3 Stars4 Stars5 Stars (12 votes, average: 4.83 out of 5)
Loading ... Loading ...
589 views

No Comments

Leave a Reply

Your email is never shared.