Extended Object

Some pecu­liar­i­ties of the inher­i­tance in Lin­go have always seemed to me to be lim­it­ing fac­tors. By the stan­dard means we are not able to know (for sure) if a given object imple­ments a speci­fic han­dler. Also, we do not know (for sure) if a given object has a speci­fic prop­er­ty. We can not find out what is the object type, nor we can inher­it in an easy and flex­i­ble way a num­ber of par­ents.
Let me show you the fol­low­ing exam­ple with two scripts — “Bul­let” and “Fly­in­gOb­ject”. The script called “Bul­let” inher­its “Fly­in­gOb­ject”.

-----------------------------------------
-- 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 exe­cute the fol­low­ing lines in the Mes­sage win­dow,

myBullet = script("Bullet").new()
trace( myBullet.handlers() )  -- [#new, #shoot, #explode]

we shall see that han­dlers() func­tion “sees” only the han­dlers which are defined in the “Bul­let” script. If we decide to “reel” all the prop­er­ties of myBul­let and to check their val­ues, we shall dis­cov­er that we can see only the prop­er­ties defined in the “Bul­let” 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 Lin­go we can not find out the exact type of a given cus­tom object. The ilk() func­tion always returns #instance.

put ilk( script( "Bullet" ).new() ) -- #instance
put ilk( script( "FlyingObject" ).new() ) -- #instance

The “Extende­dOb­ject” script tries to solve some of the above men­tioned prob­lems. From the moment when a given script inher­its “Extende­dOb­ject”, we can use the fol­low­ing new or mod­i­fied* func­tions:

-- extends()
-- implements()
-- typeOf()
-- is()
-- properties()
-- hasProperty()
-- getHandlers()
-- hasHandler()

So, how to take an advan­tage of the “Extende­dOb­ject” script? Sim­ply every one of the scripts, which we plan to be inherit­ed as a part of the “mul­ti­ple inher­i­tance” or the scripts, which we like to have the above described func­tion­al­i­ty must inher­it the “Extende­dOb­ject” script. Let us look back at the pre­vi­ous exam­ple. In the con­struc­tor of the “Bul­let” 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 “Fly­in­gOb­ject” con­struc­tor we add the fol­low­ing lines:

on new( me, s )
  ancestor = script("ExtendedObject").rawNew()
  callAncestor( #new, me, me )
  speed = s
  return me
end new

Now the things look dif­fer­ent:

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() func­tion makes it pos­si­ble for us to make “mul­ti­ple inher­i­tance”. Let me intro­duce anoth­er script in this exam­ple — “Visu­alItem”. We add a line to the “Bul­let” script con­struc­tor:

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 “Visu­alItem” will be a part of the “mul­ti­ple inher­i­tance”, it also inher­its “Extende­dOb­ject”. We add the fol­low­ing lines to the “Visu­alItem” con­struc­tor:

-----------------------------------------
-- 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 Mes­sage win­dow:

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 Inspec­tor:

Final­ly, please, pay atten­tion that there is a pos­si­bil­i­ty a two-way com­mu­ni­ca­tion to be cre­at­ed between the child and the ances­tor. If you need this func­tion­al­i­ty, you have to pass the “me” para­me­ter when you call the con­struc­tor of the ances­tor script.

ancestor = script( "ExtendedObject" ).rawNew()
callAncestor( #new, me, me )

When we end work­ing with the myBul­let object, we call the dis­pose() method, which destroys the whole “chain of ances­tors”.
There is the script itself:

Show code
-- 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

 

Extend­ed Object (4467 down­loads)
 
Comments

No comments yet.