SOLID programming

General discussion for topics related to the FreeBASIC project or its community.
dodicat
Posts: 7983
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: SOLID programming

Post by dodicat »

IMO
oop in general is memory hungry.
Even if new/delete are applied properly (which I frequently don't due mostly to carelessness), up and down casting different types eats at the stack unless careful provision is made.
Much better to convert your program to purely procedural if possible.
Simple examples
32 bit -gen gas
(-gen gcc stacks out much quicker)
1) OOP
Stacks out at about 65000 calls

Code: Select all

 
type main extends object
    declare abstract sub show
end type

dim shared as long flag,ctr

type one extends main
    declare  sub show 
end type

type two extends main
  declare  sub show
end type

type three extends main
   declare  sub show
end type

type four extends main
 declare  sub show
end type

type five extends main
 declare  sub show
end type

type six extends main
 declare  sub show
end type

type seven extends main
 declare  sub show
end type

type eight extends main
 declare  sub show
end type

type nine extends main
 declare  sub show
end type



sub one.show
    ctr+=1
    locate 1
    print
    print "one",,ctr
    flag=1
     *cast(main ptr,@two).show
end sub
sub two.show
      ctr+=1
    print "two"
    if flag=1 then
     *cast(main ptr,@three).show
  else
  *cast(main ptr,@one).show
  end if
     
end sub
sub three.show
      ctr+=1
    print "three"
    if flag=1 then
    *cast(main ptr,@four).show
else
     *cast(main ptr,@two).show
     end if
     
end sub
sub four.show
      ctr+=1
    print "four"
    if flag=1 then
     *cast(main ptr,@five).show
 else
     *cast(main ptr,@three).show
     end if
     
end sub
sub five.show
      ctr+=1
    print "five"
    if flag=1 then
     *cast(main ptr,@six).show
 else
      *cast(main ptr,@four).show
      end if
    
end sub
sub six.show
      ctr+=1
    print "six"
    if flag=1 then
      *cast(main ptr,@seven).show
      else
      *cast(main ptr,@five).show
      end if
end sub
sub seven.show
      ctr+=1
    print "seven"
    if flag=1 then
      *cast(main ptr,@eight).show
  else
      *cast(main ptr,@six).show
   end if   
end sub
sub eight.show
      ctr+=1
    print "eight"
    if flag=1 then
      *cast(main ptr,@nine).show
  else
     *cast(main ptr,@seven).show
     end if
     
end sub
sub nine.show
      ctr+=1
    print "nine"
    flag=0
    *cast(main ptr,@eight).show 
    end sub

'=============================

dim  as main ptr b=@one

 
b->show

print

sleep

  
2) procedural
keeps going to about 260000 calls

Code: Select all


declare sub oneshow
declare sub twoshow
declare sub threeshow
declare sub fourshow
declare sub fiveshow
declare sub sixshow
declare sub sevenshow
declare sub eightshow
declare sub nineshow
dim shared as long flag,ctr

sub oneshow
    ctr+=1
    locate 1
    print
    print "one",,ctr
    flag=1
     twoshow
end sub
sub twoshow
      ctr+=1
    print "two"
    if flag=1 then
     threeshow
  else
  oneshow
  end if
     
end sub
sub threeshow
      ctr+=1
    print "three"
    if flag=1 then
    fourshow
else
     twoshow
     end if
     
end sub
sub fourshow
      ctr+=1
    print "four"
    if flag=1 then
     fiveshow
 else
     threeshow
     end if
     
end sub
sub fiveshow
      ctr+=1
    print "five"
    if flag=1 then
     sixshow
 else
      fourshow
      end if
    
end sub
sub sixshow
      ctr+=1
    print "six"
    if flag=1 then
      sevenshow
      else
      fiveshow
      end if
end sub
sub sevenshow
      ctr+=1
    print "seven"
    if flag=1 then
      eightshow
  else
      sixshow
   end if   
end sub
sub eightshow
      ctr+=1
    print "eight"
    if flag=1 then
      nineshow
  else
     sevenshow
     end if
     
end sub
sub nineshow
      ctr+=1
    print "nine"
    flag=0
    eightshow 
    end sub

'=============================
oneshow


print

sleep

 
The idea I suppose is not to use temporary variables in OOP.
Also not to use -gen gcc or the 64 bit compiler for a program running purely on procedure calls.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: SOLID programming

Post by fxm »

Your code example for OOP is not relevant because there is no reason for a derived type to access a member field of another derived type horizontally.
Privileged access is only vertical.
dodicat
Posts: 7983
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: SOLID programming

Post by dodicat »

But it works anyway for a while.
If it is not allowed there should be a warning, or an easily seen explanation in the help file.

Anyway I see many on the forum putting down quisckbasic as some sort of lesser beast than freebasic.
But with quickbasic you can clear the stack by dipping into the main module and calling clear.
Clear has been re- cast in freebasic.
Lost Zergling
Posts: 538
Joined: Dec 02, 2011 22:51
Location: France

Re: SOLID programming

Post by Lost Zergling »

Even if my technical level is less, I wish to give my point of view (which still keeps the hindsight of the beginner). Having experienced the advent of object programming, I understand the logic that is behind with in particular the search for a nominal encapsulation. The problem of optimization makes that this "encapsulation" (or whatever else) can be seen according to the conceptual angle (high level) or technical angle (low level). The way I perceive it (but I can be wrong) is that encapsulation at the conceptual level can be technically translated either by a load in the stack or by a test. However, the object language, it seems to me, at the conceptual level, does not attach importance to this distinction, which is the work of the compiler. To force the non-loading of the stack, it would then be necessary to consider being able to force the linearization of the encapsulation, which would imply for the compiler to be able to translate the object code thus indicated into procedural code, with a necessarily different functioning, but retaining conceptually the object syntax in the description. I come to the conclusion that if we want to go to object programming functionalities, it may be interesting to ask the question of the enrichment of object functions to be able to address technical and conceptual issues specified on request by the programmer. What I mean is that the problem might be unresolved if it is only approached from an overly technical angle.
dodicat
Posts: 7983
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: SOLID programming

Post by dodicat »

You can overcome the stack problem by dipping back into the main module.
Both with the pure procedures and the not revelant OOP methods.
I use goto freely here because the C source converts all while, do, for, to goto label anyway, so there is no sense in using anything else (as far as I can tell) for speed.
Here I use the OOP methods for a stack check:

Code: Select all

 

type main extends object
    declare abstract sub show
end type

dim shared as long flag,ctr

type one extends main
    declare  sub show 
end type

type two extends main
  declare  sub show
end type

type three extends main
   declare  sub show
end type

type four extends main
 declare  sub show
end type

type five extends main
 declare  sub show
end type

type six extends main
 declare  sub show
end type

type seven extends main
 declare  sub show
end type

type eight extends main
 declare  sub show
end type

type nine extends main
 declare  sub show
end type



sub one.show
    ctr+=1
    locate 1
    print
    print "one",,ctr
    flag=1
     *cast(main ptr,@two).show
end sub
sub two.show
      ctr+=1
    print "two"
    if flag=1 then
     *cast(main ptr,@three).show
  else
  *cast(main ptr,@one).show
  end if
     
end sub
sub three.show
      ctr+=1
    print "three"
    if flag=1 then
    *cast(main ptr,@four).show
else
     *cast(main ptr,@two).show
     end if
     
end sub
sub four.show
      ctr+=1
    print "four"
    if flag=1 then
     *cast(main ptr,@five).show
 else
     *cast(main ptr,@three).show
     end if
     
end sub
sub five.show
      ctr+=1
    print "five"
    if flag=1 then
     *cast(main ptr,@six).show
 else
      *cast(main ptr,@four).show
      end if
    
end sub
sub six.show
      ctr+=1
    print "six"
    if flag=1 then
      *cast(main ptr,@seven).show
      else
      *cast(main ptr,@five).show
      end if
end sub
sub seven.show
      ctr+=1
    print "seven"
    if flag=1 then
      *cast(main ptr,@eight).show
  else
      *cast(main ptr,@six).show
   end if   
end sub
sub eight.show
      ctr+=1
    print "eight"
    if flag=1 then
      *cast(main ptr,@nine).show
  else
     *cast(main ptr,@seven).show
     end if
     
end sub
sub nine.show
      ctr+=1
    print "nine"
    flag=0
    '*cast(main ptr,@eight).show 
    end sub

'=============================

dim  as main ptr b=@one

 lbl:
b->show
*cast(main ptr,@eight).show 
goto lbl
print

sleep


 
Moral if any:
Don't use the C style main to run your procedures, but keep any running loop (game loop whatever) in the main module.
I am running my rotating shape program just now (from fmain).
I reckon an hour and it will be out of stack.
if it is not then disregard this post.
paul doe
Moderator
Posts: 1732
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: SOLID programming

Post by paul doe »

fxm wrote:It is true that I generally prefer to have the New/Delete calls at the same level of code.
It is easier to track, agreed, but then you won't be able to use abstract factories to provide the dependencies.
fxm wrote:For example in this case, the New/Delete call respectively in the constructor/destructor of RenderableRectangle, but this requires passing through a temporary object of RenderableRectangle if we want to keep the same type of syntax for the user.
This goes beyond simply keeping the same syntax: the semantics change. In my code, I simply inject the dependency onto the class by new()ing it. In your code, the dependency is supplied by using a (somewhat implicit) prototype: by passing a reference to a temporal instance, and then copy constructing it inside the class, you're effectively cloning a prototypical instance. Both examples are hard-coded, of course. In real code, the dependency is handed to the class by using an explicit prototypical instance; much like your code does, only the prototype is explicitly requested to an abstract factory.
Last edited by paul doe on Oct 02, 2018 12:43, edited 1 time in total.
paul doe
Moderator
Posts: 1732
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: SOLID programming

Post by paul doe »

dodicat wrote:Even if new/delete are applied properly (which I frequently don't due mostly to carelessness), up and down casting different types eats at the stack unless careful provision is made.
Much better to convert your program to purely procedural if possible.
Or, simply don't use upcasting at all. If you need to upcast a class in some part of the code, it means that the code that's doing the upcast don't have to mess with that object, since it's not its responsibility.
dodicat wrote:The idea I suppose is not to use temporary variables in OOP.
It seems to me that you've grasped the concept of polymorphism the other way around. You see, it's not about treating a single kind of object as if it were many different ones (upcasting), but to treat many different kinds of objects as if they were a single kind. That's why downcasting is automatically performed for you, while upcasting isn't.

The general idea behind OOP is to not care which object does the functionality: you simply state what you want to do; who does it, or how it does it, is not your concern. The reason why some people struggle to grasp these concepts is that procedural programming is mostly imperative, while [well understood and implemented]OOP is somewhat declarative.

There are other ways to do what you're trying to do, whatever it is (see the chain of responsibility pattern)
paul doe
Moderator
Posts: 1732
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: SOLID programming

Post by paul doe »

dafhi wrote:my starting point, following your example (paul) probably is something like this
Ok, so you're trying to abstract the screen into a class, and then use other objects (such as images) to perform several other tasks (blitting, etc.). All this with the purpose of implementing a fully abstract renderer, which you can later replace with another one (an OpenGL renderer, for example). Am I correct?
paul doe
Moderator
Posts: 1732
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: SOLID programming

Post by paul doe »

dodicat wrote:...
Much better to convert your program to purely procedural if possible...
Really? Why? If I want to shoot myself in the foot, I can do it in OOP, procedural, functional, and virtually any programming paradigm known to mankind.
dodicat wrote:But it works anyway for a while.
If it is not allowed there should be a warning, or an easily seen explanation in the help file.
Anything is allowed. Whether it's useful or not is a different matter.
dodicat wrote:...Anyway I see many on the forum putting down quisckbasic as some sort of lesser beast than freebasic...
QuickBASIC IS a lesser beast. It should be, once and for all, killed with an iron stake through its hearth, beheaded, burned and buried in holy ground. Perhaps that way it'll finally die and cease to haunt us all =D
dafhi
Posts: 1641
Joined: Jun 04, 2005 9:51

Re: SOLID programming

Post by dafhi »

paul doe wrote:
dafhi wrote:my starting point, following your example (paul) probably is something like this
Ok, so you're trying to abstract the screen into a class, and then use other objects (such as images) to perform several other tasks (blitting, etc.). All this with the purpose of implementing a fully abstract renderer, which you can later replace with another one (an OpenGL renderer, for example). Am I correct?
Correct. A general-purpose, fbc-metrics system that handles screen & image. a solution is not yet obvious but i will figure it out eventually
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: SOLID programming

Post by fxm »

Code: Select all

dim  as main ptr b=@one
b->show
This is unsafe because One is a temporary instance that is destructed at the end of the statement defining it, so before the next statement that uses it.

Simple example:

Code: Select all

Type UDT
  Declare Constructor ()
  Declare Destructor ()
  Declare Sub test()
  Dim As Integer I
End Type

Constructor UDT ()
  Print "UDT.Constructor()"
End Constructor

Destructor UDT ()
  Print "UDT.Destructor()"
End Destructor

Sub UDT.test ()
  Print "UDT.test()"
End Sub


Scope
  Dim As UDT Ptr p = @UDT()
  p->test()
End Scope

Sleep

Code: Select all

UDT.Constructor()
UDT.Destructor()
UDT.test()
At the opposite, the following is safe:

Code: Select all

Type UDT
  Declare Constructor ()
  Declare Destructor ()
  Declare Sub test()
  Dim As Integer I
End Type

Constructor UDT ()
  Print "UDT.Constructor()"
End Constructor

Destructor UDT ()
  Print "UDT.Destructor()"
End Destructor

Sub UDT.test ()
  Print "UDT.test()"
End Sub


Scope
  (@UDT())->test()  '' or simply UDT().test()
End Scope

Sleep

Code: Select all

UDT.Constructor()
UDT.test()
UDT.Destructor()
Last edited by fxm on Oct 02, 2018 16:22, edited 1 time in total.
dodicat
Posts: 7983
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: SOLID programming

Post by dodicat »

But I don't use a destructor, and if I did what would I put in it?
Surely to free udt pointer memory a dedicated procedure is required?

Are constructors/destructors not supposed to do a task?
What is the point of saying "Hello, I am here, just passing through" or similar.
And I was extending object, so I don't need a constructor anyway.
Surely a proof of validity is a run.
Else how can we tell.
(I am not saying anything about me being correct of course, just an observation)

Code: Select all

Type UDT extends object
  Declare Constructor ()
  Declare Destructor ()
  Declare Sub test()
  Dim As Integer I
  as double d(10000)
End Type

Constructor UDT ()
  Print "UDT.Constructor()"
End Constructor

Destructor UDT ()
  Print "UDT.Destructor()",this.i
  'DO WHAT??
End Destructor

Sub UDT.test ()
  Print "UDT.test()",this.i
End Sub

dim as long k

do
    k+=1
    locate 1,0,0
Scope
  Dim  As UDT Ptr p = @UDT()
  p->d(13)=k
  p->i= p->d(13)
  p->test()
End Scope

loop

Sleep  
paul doe
Moderator
Posts: 1732
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: SOLID programming

Post by paul doe »

dafhi wrote:Correct. A general-purpose, fbc-metrics system that handles screen & image. a solution is not yet obvious but i will figure it out eventually
I see. We'll talk about this soon, then. In the meantime, I'll prepare a little something that perhaps will help wrap the concepts seen so far.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: SOLID programming

Post by fxm »

@dodicat,
I said it was dangerous (because in the general case it's a bad habit), not that it did not work for your example.
As long as there is no destructor (explicit or implicit) called, that works but it's not solid code (because depending on the Type structure).


[edit]
Updated documentation:
KeyPgTypeTemp → fxm [Added preciser sentence on releasing of temporary objects]
Last edited by fxm on Oct 04, 2018 9:10, edited 2 times in total.
paul doe
Moderator
Posts: 1732
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: SOLID programming

Post by paul doe »

Ok dafhi, let's review a simpler example of the Dependency Inversion Principle, perhaps it's easier this way for you to grasp the concept, and will help you to find the abstractions you need. I'll be using a similar example to the one proposed on chapter 11 of the book 'Agile Principles, Patterns and Practices in C#', by Robert C. Martin.

Suppose you want to write code that allows you to switch devices on and off with the keyboard. So far, you have a single device: a lamp. So, if you were to express a solution using OO techniques, you can write something like this:

Code: Select all

type Lamp extends Object
  public:
    declare constructor()
    declare destructor()
    
    declare sub turnOn()
    declare sub turnOff()
end type

constructor Lamp()
end constructor

destructor Lamp()
end destructor

sub Lamp.turnOn()
  ? "Lamp has been turned on"
end sub

sub Lamp.turnOff()
  ? "Lamp has been turned off"
end sub

type Switch
  public:
    declare constructor()
    declare destructor()
    
    declare sub on()
    declare sub off()
  
  private:    
    m_lamp as Lamp
end type

constructor Switch()
end constructor

destructor Switch()
end destructor

sub Switch.on()
  m_lamp.turnOn()
end sub

sub Switch.off()
  m_lamp.turnOff()
end sub

/'
  Main code
'/
var aSwitch = Switch()

dim as string keyPressed

do
  keyPressed = input( 1 )
  
  if( keyPressed = "L" ) then
    aSwitch.off()
  end if
  
  if( keyPressed = "l" ) then
    aSwitch.on()
  end if
  
  sleep( 1, 1 )
loop until( keyPressed = chr( 27 ) )
That does it, but is somewhat inflexible. In this case, the Switch 'composes' a Light instance, but it is a hardcoded one. I can't change which lamp the Switch class switches, nor can I use it to switch other models of lamp, or other devices that can need a switch. So, we can refactor the design to accept a dependency to a Lamp, instead of hardcoding it:

Code: Select all

type Lamp extends Object
  public:
    declare constructor()
    declare destructor()
    
    declare sub turnOn()
    declare sub turnOff()
end type

constructor Lamp()
end constructor

destructor Lamp()
end destructor

sub Lamp.turnOn()
  ? "Lamp has been turned on"
end sub

sub Lamp.turnOff()
  ? "Lamp has been turned off"
end sub

type Switch
  public:
    declare constructor( _
      byref as Lamp )
    declare destructor()
    
    declare sub on()
    declare sub off()
  
  private:    
    declare constructor()
    
    m_lamp as Lamp = any
end type

constructor Switch()
end constructor

constructor Switch( _
  byref aLamp as Lamp )
  
  m_lamp = aLamp
end constructor

destructor Switch()
end destructor

sub Switch.on()
  m_lamp.turnOn()
end sub

sub Switch.off()
  m_lamp.turnOff()
end sub

/'
  Main code
'/
var aLamp = Lamp()
var aSwitch = Switch( aLamp )

dim as string keyPressed

do
  keyPressed = input( 1 )
  
  if( keyPressed = "L" ) then
    aSwitch.off()
  end if
  
  if( keyPressed = "l" ) then
    aSwitch.on()
  end if
  
  sleep( 1, 1 )
loop until( keyPressed = chr( 27 ) )
So now we can use the Switch class to switch any Lamp. However, the dependency of Switch upon Lamp can (and should) be abstracted. This is because the concept that the Switch class encapsulates doesn't need to know, nor it has to care, what it is switching. In this case, we are violating the DIP by making a class (Switch) depend upon details (how the Lamp class is implemented). We can improve this by inverting the dependency relationship: instead of Lamp being depended on by Switch, we make Lamp the depending class:

Code: Select all

type ISwitchableDevice extends Object
  declare virtual destructor()
  
  declare abstract sub turnOn()
  declare abstract sub turnOff()
end type

destructor ISwitchableDevice()
end destructor

type Lamp extends ISwitchableDevice
  public:
    declare constructor()
    declare destructor() override
    
    declare sub turnOn() override
    declare sub turnOff() override
end type

constructor Lamp()
end constructor

destructor Lamp()
end destructor

sub Lamp.turnOn()
  ? "Lamp has been turned on"
end sub

sub Lamp.turnOff()
  ? "Lamp has been turned off"
end sub

type Switch
  public:
    declare constructor( _
      byref as ISwitchableDevice )
    declare destructor()
    
    declare sub on()
    declare sub off()
  
  private:    
    declare constructor()
    
    m_device as ISwitchableDevice ptr
end type

constructor Switch()
end constructor

constructor Switch( _
  byref aSwitchableDevice as ISwitchableDevice )
  
  m_device = @aSwitchableDevice
end constructor

destructor Switch()
end destructor

sub Switch.on()
  m_device->turnOn()
end sub

sub Switch.off()
  m_device->turnOff()
end sub

/'
  Main code
'/
var aLamp = Lamp()
var aSwitch = Switch( aLamp )

dim as string keyPressed

do
  keyPressed = input( 1 )
  
  if( keyPressed = "L" ) then
    aSwitch.off()
  end if
  
  if( keyPressed = "l" ) then
    aSwitch.on()
  end if
  
  sleep( 1, 1 )
loop until( keyPressed = chr( 27 ) )
By creating an abstract interface to handle the dependency, we have effectively inversed the relationship: Lamp is now doing the depending (since it has to implement the ISwitchableDevice interface to allow it to be used with a Switch), rather than being depended on. Also, Switch will be able to handle devices that aren't invented yet, without changing its implementation:

Code: Select all

type ISwitchableDevice extends Object
  declare virtual destructor()
  
  declare abstract sub turnOn()
  declare abstract sub turnOff()
end type

destructor ISwitchableDevice()
end destructor

type Lamp extends ISwitchableDevice
  public:
    declare constructor()
    declare destructor() override
    
    declare sub turnOn() override
    declare sub turnOff() override
end type

constructor Lamp()
end constructor

destructor Lamp()
end destructor

sub Lamp.turnOn()
  ? "Lamp has been turned on"
end sub

sub Lamp.turnOff()
  ? "Lamp has been turned off"
end sub

type Motor extends ISwitchableDevice
  public:
    declare constructor()
    declare destructor() override
    
    declare sub turnOn() override
    declare sub turnOff() override
end type

constructor Motor()
end constructor

destructor Motor()
end destructor

sub Motor.turnOn()
  ? "The motor has been turned on"
end sub

sub Motor.turnOff()
  ? "The motor has been turned off"
end sub

type Switch
  public:
    declare constructor( _
      byref as ISwitchableDevice )
    declare destructor()
    
    declare sub on()
    declare sub off()
  
  private:    
    declare constructor()
    
    m_device as ISwitchableDevice ptr
end type

constructor Switch()
end constructor

constructor Switch( _
  byref aSwitchableDevice as ISwitchableDevice )
  
  m_device = @aSwitchableDevice
end constructor

destructor Switch()
end destructor

sub Switch.on()
  m_device->turnOn()
end sub

sub Switch.off()
  m_device->turnOff()
end sub

/'
  Main code
'/
var aLamp = Lamp()
var aMotor = Motor()

var aSwitch = Switch( aLamp )
var anotherSwitch = Switch( aMotor )

dim as string keyPressed

do
  keyPressed = input( 1 )
  
  if( keyPressed = "M" ) then
    anotherSwitch.off()
  end if
  
  if( keyPressed = "m" ) then
    anotherSwitch.on()
  end if

  if( keyPressed = "L" ) then
    aSwitch.off()
  end if
  
  if( keyPressed = "l" ) then
    aSwitch.on()
  end if
  
  sleep( 1, 1 )
loop until( keyPressed = chr( 27 ) )
You can also provide the dependency to Switch by using a different injection scheme:

Code: Select all

type ISwitchableDevice extends Object
  declare virtual destructor()
  
  declare abstract sub turnOn()
  declare abstract sub turnOff()
end type

destructor ISwitchableDevice()
end destructor

type Lamp extends ISwitchableDevice
  public:
    declare constructor()
    declare destructor() override
    
    declare sub turnOn() override
    declare sub turnOff() override
end type

constructor Lamp()
end constructor

destructor Lamp()
end destructor

sub Lamp.turnOn()
  ? "Lamp has been turned on"
end sub

sub Lamp.turnOff()
  ? "Lamp has been turned off"
end sub

type Motor extends ISwitchableDevice
  public:
    declare constructor()
    declare destructor() override
    
    declare sub turnOn() override
    declare sub turnOff() override
end type

constructor Motor()
end constructor

destructor Motor()
end destructor

sub Motor.turnOn()
  ? "The motor has been turned on"
end sub

sub Motor.turnOff()
  ? "The motor has been turned off"
end sub

type Switch extends Object
  public:
    declare constructor()
    declare destructor()
    
    declare sub on( _
      byval as ISwitchableDevice ptr )
    declare sub off( _
      byval as ISwitchableDevice ptr )
end type

constructor Switch()
end constructor

destructor Switch()
end destructor

sub Switch.on( _
  byval aDevice as ISwitchableDevice ptr )
  
  aDevice->turnOn()
end sub

sub Switch.off( _
  byval aDevice as ISwitchableDevice ptr )
  
  aDevice->turnOff()
end sub

/'
  Main code
'/
var aLamp = Lamp()
var aMotor = Motor()

var aSwitch = Switch()

dim as string keyPressed

do
  keyPressed = input( 1 )
  
  if( keyPressed = "M" ) then
    aSwitch.off( @aMotor )
  end if
  
  if( keyPressed = "m" ) then
    aSwitch.on( @aMotor )
  end if

  if( keyPressed = "L" ) then
    aSwitch.off( @aLamp )
  end if
  
  if( keyPressed = "l" ) then
    aSwitch.on( @aLamp )
  end if
  
  sleep( 1, 1 )
loop until( keyPressed = chr( 27 ) )
I hope that you're able to see how the different concepts fit into what you're trying to accomplish.
Post Reply