OOP Interface Example With Inheritance

Share your advanced PureBasic knowledge/code with the community.
User avatar
mk-soft
Always Here
Always Here
Posts: 6242
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

OOP Interface Example With Inheritance

Post by mk-soft »

To create a simple interface, you can place the virtual table in a DataSection.

With inheritance, the situation is somewhat different.
Here the virtual table must be managed in a class. A map is a good place to store the class name and the virtual table.
The base class must always have the pointer to the virtual table as the first entry. This can then be inherited. Even multiple times.
When creating a new class with inheritance, the methods of the virtual table are then copied to the new table.
This allows you to overwrite the inherited methods without changing the original methods.

Update v1.05

Code: Select all

;-TOP
; Comment : OOP Interface Example With Inheritance
; Author  : mk-soft
; Version : v1.05
; Create  : 18.12.2024
; Update  : 22.12.2024

; Link    : https://www.purebasic.fr/english/viewtopic.php?t=85960

EnableExplicit

DeclareModule ClassCommon
  
  Structure sMethod
    *Index[0]
  EndStructure
  
  Structure sClass
    ClassName.s       ; Name of class
    Size.i            ; Size of interface in bytes 
    *Methods.sMethod  ; Pointer to method table
  EndStructure
  
  Declare NewClass(ClassName.s, SizeOfInterface, ClassExtends.s = "")
  
  Macro SetMethod(Method, Class=*Class, iMethods=iMethods)
    Class\Methods\Index[OffsetOf(iMethods\Method()) / SizeOf(Integer)] = @Method()
  EndMacro
  
EndDeclareModule

Module ClassCommon
  
  EnableExplicit
  
  Global NewMap Classes.sClass()
  
  Procedure NewClass(ClassName.s, SizeOfInterface, ClassExtends.s = "")
    Protected *Class.sClass, *ClassExtends.sClass
    
    With Classes()
      ; Check class is always exists
      If FindMapElement(Classes(), ClassName)
        Debug "ClassCommon Error: Class always exists: " + ClassName
        ProcedureReturn 0
      EndIf
      
      ; If use extends, check exists extends class
      If ClassExtends
        *ClassExtends = FindMapElement(Classes(), ClassExtends)
        If Not *ClassExtends
          Debug "ClassCommon Error: Extends class not exists: " + ClassExtends
          ProcedureReturn 0
        EndIf
      EndIf
      
      ; Create new class
      *Class = AddMapElement(Classes(), ClassName)
      If *Class
        ; Store class name
        \ClassName = ClassName
        ; Store byte size of method table
        \Size = SizeOfInterface
        ; Allocate memory for method table
        \Methods = AllocateMemory(SizeOfInterface)
        If Not \Methods
          Debug "ClassCommon Error: Out Of Memory:" + ClassName
          DeleteMapElement(Classes())
          ProcedureReturn 0
        EndIf
        ; If use extends, copy extends methods to new method table
        If *ClassExtends
          CopyMemory(*ClassExtends\Methods, *Class\Methods, *ClassExtends\Size)
        EndIf
        ProcedureReturn *Class
      Else
        Debug "ClassCommon Error: Out Of Memory:" + ClassName
        ProcedureReturn 0
      EndIf
    EndWith
  EndProcedure
  
EndModule

; ****

CompilerIf #PB_Compiler_IsMainFile
  
  ;- *** Example ***
  
  ;- Userbase
  
  DeclareModule UserBase
    
    Interface iUserBase
      Destroy()
      GetFirstName.s()
      SetFirstName(Name.s)
      GetLastName.s()
      SetLastName(Name.s)
      GetLabel.s()
    EndInterface
    
    Structure sUserBase
      *vTable ; Allways first entry
      FirstName.s
      LastName.s
    EndStructure
    
    Declare New(FirstName.s = "", LastName.s = "")
    
  EndDeclareModule
  
  Module UserBase
    
    EnableExplicit
    
    UseModule ClassCommon
    
    ; Static pointer to *class
    Global *Class.sClass
    
    ; Macro Interface Helper
    Interface iMethods Extends iUserBase
    EndInterface
    
    Procedure New(FirstName.s = "", LastName.s = "")
      Protected *This.sUserBase
      
      If *Class
        *This = AllocateStructure(sUserBase)
        If *This
          *This\vTable = *Class\Methods
          *This\FirstName = FirstName
          *This\LastName = LastName
        EndIf
      EndIf
      ProcedureReturn *This
    EndProcedure
    
    Procedure Destroy(*This.sUserBase)
      Debug *Class\ClassName + ": Destroy was called"
      ; Free all resources here:
      ; - Do not forget to delete fonts, images, files, etc. 
      
      FreeStructure(*This)
    EndProcedure
    
    Procedure.s GetFirstName(*This.sUserBase)
      With *This
        ProcedureReturn \FirstName
      EndWith
    EndProcedure
    
    Procedure SetFirstName(*This.sUserBase, Name.s)
      With *This
        \FirstName = Name
      EndWith
    EndProcedure
    
    Procedure.s GetLastName(*This.sUserBase)
      With *This
        ProcedureReturn \LastName
      EndWith
    EndProcedure
    
    Procedure SetLastName(*This.sUserBase, Name.s)
      With *This
        \LastName = Name
      EndWith
    EndProcedure
    
    Procedure.s GetLabel(*This.sUserBase)
      Protected r1
      With *This
        ProcedureReturn \LastName + ", " + \FirstName
      EndWith
    EndProcedure
    
    Procedure InitClass()
      ; Create new class
      *Class = NewClass("UserBase", SizeOf(iUserBase))
      If *Class
        ; Set method with macro
        SetMethod(Destroy) ; Macro -> *Class\Methods\Index[OffsetOf(iMethods\Destroy()) / SizeOf(Integer)] = @Destroy()
        SetMethod(GetFirstName)
        SetMethod(SetFirstName)
        SetMethod(GetLastName)
        SetMethod(SetLastName)
        SetMethod(GetLabel)
      Else
        Debug "Error: NewClass UserBase"
        CallDebugger
      EndIf
    EndProcedure : InitClass()
  EndModule
  
  ;- UserAddress
  
  DeclareModule UserAddress
    
    Interface iUserAddress Extends UserBase::iUserBase
      SetAddress(Country.s, City.s, Street.s)
      GetCountry.s()
      GetCity.s()
      GetStreet.s()
    EndInterface
    
    Structure sUserAddress Extends UserBase::sUserBase
      Country.s
      City.s
      Street.s
    EndStructure
    
    Declare New(FirstName.s = "", LastName.s = "")
    
  EndDeclareModule
  
  Module UserAddress
    
    EnableExplicit
    
    UseModule ClassCommon
    
    ; Static pointer to *class
    Global *Class.sClass
    
    ; Macro Interface Helper
    Interface iMethods Extends iUserAddress
    EndInterface
    
    Procedure New(FirstName.s = "", LastName.s = "")
      Protected *This.sUserAddress
      
      If *Class
        *This = AllocateStructure(sUserAddress)
        If *This
          *This\vTable = *Class\Methods
          *This\FirstName = FirstName
          *This\LastName = LastName
        EndIf
      EndIf
      ProcedureReturn *This
    EndProcedure
    
    Procedure Destroy(*This.sUserAddress)
      Debug *Class\ClassName + ": Destroy was called"
      ; Free all resources here:
      ; - Do not forget to delete fonts, images, files, etc. 
      
      FreeStructure(*This)
    EndProcedure
    
    Procedure SetAddress(*This.sUserAddress, Country.s, City.s, Street.s)
      With *This
        \Country = Country
        \City = City
        \Street = Street
      EndWith
    EndProcedure
    
    Procedure.s GetCountry(*This.sUserAddress)
      With *This
        ProcedureReturn \Country
      EndWith
    EndProcedure
    
    Procedure.s GetCity(*This.sUserAddress)
      With *This
        ProcedureReturn \City
      EndWith
    EndProcedure
    
    Procedure.s GetStreet(*This.sUserAddress)
      With *This
        ProcedureReturn \Street
      EndWith
    EndProcedure
    
    Procedure.s GetLabel(*This.sUserAddress)
      Protected r1.s
      With *This
        r1 = "ADDRESS"
        r1 + #LF$ + \LastName + ", " + \FirstName
        r1 + #LF$ + \Street
        r1 + #LF$ + \City
        r1 + #LF$ + \Country
        ProcedureReturn r1
      EndWith
    EndProcedure
    
    
    Procedure InitClass()
      ; Create new class
      *Class = NewClass("UserAddress", SizeOf(iUseraddress), "UserBase")
      If *Class
        ; Set method with macro
        SetMethod(Destroy) ; Macro -> *Class\Methods\Index[OffsetOf(iMethods\Destroy()) / SizeOf(Integer)] = @Destroy()
        SetMethod(SetAddress)
        SetMethod(GetCountry)
        SetMethod(GetCity)
        SetMethod(GetStreet)
        ; Overwrite method with macro
        SetMethod(Destroy)
        SetMethod(GetLabel)
      Else
        Debug "Error: NewClass UserAddress"
        CallDebugger
      EndIf
    EndProcedure : InitClass()
  EndModule
  
  ;-Test
  
  Define *Obj1.UserBase::iUserBase = UserBase::New("Tom")
  If *Obj1
    *Obj1\SetLastName("Clancy")
    Debug "" + *Obj1\GetLastName()
    Debug *Obj1\GetLabel()
    *Obj1\Destroy() : *Obj1 = 0
  EndIf
  
  Debug "----"
  
  Define *Obj2.UserAddress::iUserAddress = UserAddress::New("Tom", "Unknown")
  If *Obj2
    *Obj2\SetAddress("German", "Unknown City", "Unknown Street")
    Debug *Obj2\GetLabel()
    *Obj2\Destroy() : *Obj2 = 0
  EndIf
  
CompilerEndIf
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
threedslider
Enthusiast
Enthusiast
Posts: 396
Joined: Sat Feb 12, 2022 7:15 pm

Re: OOP Interface Example With Inheritance

Post by threedslider »

@mk-soft : Very nice and thank you a lot from your sharing :mrgreen:

I will study that and it is useful for me :shock:

Happy coding !
SMaag
Enthusiast
Enthusiast
Posts: 324
Joined: Sat Jan 14, 2023 6:55 pm
Location: Bavaria/Germany

Re: OOP Interface Example With Inheritance

Post by SMaag »

@mk-soft

once more, some of us work on the same things.
I started some time ago a proof of concept for OOP with Inheritance and Overwrite
inspired by your OOP Module
http://www.purebasic.fr/english/viewtop ... 12&t=64305

My goal was to make some things more transparent. Just to share the code, so we can pick best of each.
My code is Beta or Developer state. It works, but is not 100% proofed.

https://github.com/Maagic7/PureBasicFra ... e/main/OOP
Post Reply