Using a class inside another class

I need to use a class inside another class.
Which is better (and why):
a) create a reference to the class;
b) declare the class inside;
c) declare the 2nd class as a child;

thanks for any insight in this matter.

Is there a question?

1 Like

yes, there is.
Are

yes, there is.
And any advantage in using one or the other method? are they all valid?

I always do (a)

Do a new in construct and dispose in destruct.

2 Likes

Variant (b) is invalid by syntax. (a) and (c) are valid. Which one is better depends from particular situation:the algorithm you’ve chosen, organization of data, how organized used 3rd party stuff, your habits, etc. Exact answer is impossible.

1 Like

I certainly use all 3 of the approaches in my code
Each have their place
It all depends on what you’re doing.

Below are some coding examples of what I think you mean by A,B & C


a) create a reference to the class;

  INCLUDE('ctNested.inc'),ONCE

ctParent CLASS,TYPE,MODULE('ctParent.clw'),LINK('ctParent.clw')
NestedClass  &ctNested
OtherClass   &OtherClass
CONSTRUCT    PROCEDURE
DESTRUCT     PROCEDURE
Init         PROCEDURE(*OtherClass xClass)
 END 

   MEMBER
   INCLUDE('ctParent.inc'),ONCE
   MAP
   END 
   
ctParent.Construct PROCEDURE
   CODE
   SELF.NestedClass &= NEW ctNested

ctParent.Destruct PROCEDURE
   CODE
   DISPOSE(  SELF.NestedClass )

ctParent.Init         PROCEDURE(*OtherClass xClass)
  CODE 
   SELF.OtherClass &= xClass
   ! I use the term "Injected" to describe this process
   ! xClass is injected into this instance of ctParent
   ! notice how we do NOT call DISPOSE( SELF.OtherClass )


b) declare the class inside;

ctParent.SomeMethod PROCEDURE()

ChildA       CLASS()
PropertyA       LONG
MethodA         PROCEDURE()
             END

ChildB       CLASS(DerivedFromSomeClass)  
PropertyA       LONG
MethodA         PROCEDURE()
             END
   ! NOTE:   if  DerivedFromSomeClass has a MODULE attribute
   !         then to avoid a runtime GPF
   !            the include for DerivedFromSomeClass needs to be at the module level

   ! you could declare ChildA and/or ChildB as ,TYPE
   ! and then instantiate anywhere inside of ctParent.SomeMethod
   ! but they do not exist outside of the scope of ctParent.SomeMethod

   ! you can have code run AFTER the RETURN of ctParent.SomeMethod
   ! via the .DESTRUCT of a class where the instance is not a reference

   CODE 
   Do Something

Something ROUTINE 
   ! something

ChildA.MethodA PROCEDURE()
   CODE 
   ! something

ChildB.MethodA PROCEDURE()
    CODE 
    ! something


c) declare the 2nd class as a child;

 INCLUDE('ctParent.inc'),ONCE

ctChild CLASS(ctParent),TYPE,MODULE('ctChild.clw'),LINK('ctChild.clw)
AnotherProperty LONG
AnotherMethod   PROCEDURE()
Init            PROCEDURE(*OtherClass xClass),DERIVED  
                  !  NOTE: by using ,DERIVED 
                  !  this will create a compile time error, 
                  !  unless there is a method with the same name and prototype in the PARENT class
            END 


ctChild.Init            PROCEDURE(*OtherClass xClass) 
   CODE 
   ! stuff
   PARENT.INIT( xClass )
   ! more stuff

   ! NOTE: the parent call is NOT required
1 Like

Related… IIRC if you declare both classes inside the same .CLW file then they are “Friends” so they can access any Private members and properties in that CLW.

I think DAB called this “a confined but unconstrained encapsulation leakage”

I would favor choice A, having the child class separate with the parent class using a reference. The child class can be completely defined in the CLW.

In example for the variant (b) you have classes declared inside a method of other class rather than in other class itself. Local classes are using widely in ABC templates but I can’t recommend to use them without necessity because of mistake in semantics made during design of classes in Clarion. For example, instances of local classes in TS Modula-2 are completely encapsulated inside procedure/class where they are declared. They can’t be, for example, passed as an actual parameter to the call of a procedure declared in outer scope. Clarion has no such restriction and instances of local classes can be passed outside the scope where class is declared. This can cause unexpected run-time errors.

also: “Variant (b) is invalid by syntax”
You can use this variant if you put the declaration inside the module with the procedure declarations.
It compile and execute ok.

mark, thanks, and let me digest the info you provided.

How these statement is related to your words “inside another class”?

Words "I need to use a class inside another class... b) declare the class inside;" means for me declaration a class field with a type of some other class. This is impossible.

My nursery nanny used to say: If your class extends another class functionality, then inherit.

I think the OP only said “I need to use a class inside another class” which does not sound like inheritance would apply. Something like the Utility classes inside ABUtil.clw come to mind. But not enough is know, so extending may apply.

Other considerations…

Could “use a class” work better via an Interface as seen with ABC Window Components.

Could “use a class” work by defining Worker class as Abstract i.e. with some, or all, processing done inside Virtual methods that are simple stubs which do nothing until Derived by the Parent.

This was seen recently on Clarion Live for the Invoice Example. An Abstract class was made that did a Set/Loop/Next and called a TakeRecord Virtaul Method. The main Invoice process class Method derived that class and provided a Take Record. This code is in Invoice017.clw.

!--- DATA ---
InvoiceDetailReader           CLASS,TYPE   !Abstract class to process details
Error                           STRING(256)
ErrorCode                       LONG
FileError                       STRING(256)
FileErrorCode                   LONG
Process                         PROCEDURE
ClearErrors                     PROCEDURE
SetErrors                       PROCEDURE
TakeRecord                      PROCEDURE,VIRTUAL   !<--- Must Derive to do anything
                              END

LoadDetailReader    CLASS(InvoiceDetailReader)  !<-- Derived class
TakeRecord            PROCEDURE,DERIVED  
                    END

!--- CODE ---
InvoiceDetailReader.Process   PROCEDURE
  CODE
  SELF.ClearErrors()
  OPEN(InvoiceDetailView)
  InvoiceDetailView{PROP:Filter} = 'UPPER(InvDet:InvoiceGuid) = <39>'& UPPER(Inv:Guid) &'<39>'
  InvoiceDetailView{PROP:Order } = 'InvDet:LineNumber'
  SET(InvoiceDetailView)
  LOOP WHILE SELF.Errorcode = NoError
     NEXT(InvoiceDetailView)
     IF ERRORCODE() <> NoError THEN BREAK.
     SELF.TakeRecord()
  END
  CLOSE(InvoiceDetailView)

InvoiceDetailReader.TakeRecord    PROCEDURE
  CODE
  !Empty Virtual in what is an Abstract class

!-- Drived InvoiceDetailReader having its Virtual TakeRecord() derived and defined
LoadDetailReader.TakeRecord   PROCEDURE
  CODE
  InvoiceDetailCache.AssignRecordToQueue()
  ADD(InvoiceDetailQueue) !ADD(Queue) never fails, except when your computer is about to crash anyway!!!!

!--- Derived LoadDetailReader class being used
InvoiceDetailCache.LoadDetail PROCEDURE
  CODE
  FREE(InvoiceDetailQueue)
  LoadDetailReader.Process()  !<-- calls LoadDetailReader.TakeRecord() eventually