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.
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?
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.
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.
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
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 )
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
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
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