[Gambas-user] Inheritance and Signature checking in gambas 3

Benoît Minisini gambas at ...1...
Tue Sep 20 03:06:50 CEST 2011


> HI,
> 
> I thought I may have trouble with this area :-(
> 
> I am migrating quite a lot of gambas2 components that work well and most
> of the pain has arisen in this signature checking in the runtime, as in
> the "incorrectly overridden" message.
> 
> First, because this is only evident in the runtime, I have a fear that
> testing the converted components may miss the error when the override is
> in a rarely executed area of code.
> 
> Secondly, I can no longer override a method declared in a base class as
> Identifier(...) with a method in the child class with a real and
> restricted parameter set. To explain, I have a set of base classes in
> component X that provide the very fundamental features of an inheritance
> hierarchy.  As well, they prescribe a set of methods that must be
> implemented in the child classes.  In short, they are "stubs" for
> required methods.  All they do is raise an error if the method is not
> overridden in the child class.  Because of this, these stubs have no
> idea nor do they care what the parameters of the child class method are.
> 
> As an example, I have a persistence model that involves three levels of
> code:
> the [application] ----uses---> [a business_object library]
> ---inherits---> [base_persistence]
> The persistence of the data is entirely handled by the two lower level
> libraries, the application only has to call the Create, Read, Update or
> Delete methods on the business_object object and all it's persistence
> needs are handled.  The business_object library contains all the
> relevant business rules for its classes, e.g. whether objects can be
> deleted from the persistence or whether a cascaded delete is necessary
> etc etc.  However, the business_object layer has no idea exactly how the
> persistence is achieved, it just validates the action applies the
> relevant transformations between the object data model and the
> persistence data model and then passes the object data off to the low
> level persistence methods in it's parent class.  This is the high level
> design.
> 
> The implementation employs the following code model:  at the
> business_object library level, each business object has (at least) two
> classes, the main object class, e.g. "Account" and one or more mapping
> classes, say "_sqlaccount" and "_xmlaccount".   The main class is
> visible to the application, the mapping classes are hidden.  The mapping
> classes provide the two way transformation between the object data and
> the persistence data,  in short they have a "Marshall" method and an
> "Unmarshall" method.  These two methods are very importantly "visible"
> to the base_persistence library.
> 
> The last comment requires explanation.  In the base_persistence library
> there is a similar split between the "object" model and the
> "persistence" model.  There is a base "BusinessObject" class, from which
> all the classes in the business_object library inherit, and a base
> "persistor" from which all the mapping classes in the business_object
> library inherit.  (There is a tricky bit here that is important to the
> design but irrelevant to this message - there is a base persistor for
> each persistance type, e.g. postgresql, mysql, xml, flatfile etc.  Each
> of these is in separate libraries and is loaded explicitly at runtime by
> the BusinessObject class depending on the settings file for the
> business_object library. Suffice to say that it is the actual base
> persistor that provides the actual input/output methods to "add",
> "load", "save" and "remove" the data. Note the change from the CRUD
> method names!)
> 
> When the application instantiates a business object, say an Account
> object called "MyAccount", the constructor in the base_persistence
> library BusinessObject class i.e. BusinessObject._new() checks the
> persistence method in use and instantiates the relevant mapping class in
> the business_object library, i.e. it creates an actual persistor which
> is either a "_sqlaccount" or an "_xmlaccount".  How I do this is
> irrelevant, the important thing is that the BusinessObject has access
> to, via the gambas virtual dispatching feature, one of these things
> which as far as it is concerned is a "persistor".
> 
> The CRUD methods are actually public in the BusinessObject class, i.e.
> they are inherited by the classes in the business object library.  So
> when the application does a "MyAccount.Update, it is actually calling
> the BusinessObject.Update method.  This in turn calls its'
> persistor.save method.  In fact, what it does is call its'
> persistor.Marshall method and then its' persistor.save method.  Note the
> subtle inference here, the Marshall method is the the one in the active
> mapping class and the save method is the one in the base persistor class
> (there is no save method in the mapping class). However, in order to
> compile the base_persistence libraries there must be a "stub" method
> called "Marshall" at this level and this is the one that MUST be
> overridden at the higher level.  Further, at this level (in the base
> persistor libraries) I have no idea as to how the business_object
> library level "Marshall" method works, nor what parameters it needs to
> achieve that work - that is a matter for the specialised classes and the
> BusinessObject class.  All I know is that the method must be declared
> and it must be overridden, so it is:
> 
> Public Sub Marshall(...)
>     Error.Raise("Implementation fault - this method must be overridden
> in the business_object library")
> End
> 
> I hope you have managed to follow all this and can offer some advice.
> The persistence architecture model is not my own invention, it comes
> from work by Fowler and others.  It has worked for me for the last two
> years in gambas2 and (the code) is the result of significant!!! effort
> on my part.  I just can't see a solution now with this signature
> checking in the gambas3 runtime.
> 
> Finally, I still can't see the value in this signature checking.  This
> is only one area where I have successfully used method overrides in
> gambas2 which now have to be not only rewritten but redesigned for
> gambas3.  Why was it introduced?  If it is really necessary for
> something else then I'd like to ask for a way to turn it off, preferably
> at the gbx3 compile, not only for signature checking but also for result
> type checking.
> 
> regards
> Bruce
> 

Mmm. You should provide your code, or some schema, so that I am sure to really 
understand your design.

Now I will try to give you solution, but I'm not sure it is good as I'm not 
sure that I understood your description completely.

So...

1) Not ensuring that the signatures of a child method is exactly the same as 
the parent method was a Gambas 2 bug that can lead to interpreter crashes.

2) In other words, as soon as a parent class A has a Marshall() method, a 
children class B that overrides Marshall() must use the same signature. The 
contrary is, for me, completely illogical.

3) If you want the children class to all have different Marshall() signatures, 
then Marshal() cannot be a member of the parent class.

4) So I suggest that you rename all Marshal() child methods into something 
like "_Marshal()". And that you keep a Marshal(...) in the parent class that 
will call the _Marshal() method *dynamically* by using the Object.Call() 
method. Something like that:

	Public Sub Marshal(...)
	
	  Dim aParam As New Variant[Param.Count]
	  Dim I As Integer
	
	  ' I realise that a Param.All property would be a good idea!
	  For I = 0 To Param.Max
	    aParam[I] = Param[I]
	  Next
	
	  Object.Call(Me, "_Marshal", aParam)
	
	End

Regards,

-- 
Benoît Minisini




More information about the User mailing list