Interfaces, Abstract classes etc

This forum is meant for anything you would like to share with other visitors
Post Reply
User avatar
Chris
Posts: 4906
Joined: Thu Oct 08, 2015 7:48 am
Location: Greece

Interfaces, Abstract classes etc

Post by Chris »

Hello all!

After watching the great presentation from Stefan Hirsch (thanks Stefan!) here (<will post link as soon as it's uploaded to youtube>), there were some questions about why and when to use interfaces and abstract methods. I would like to add another sample about this:

Similar to Stefan's sample code, let's say we need to implement export to xml functionality in many objects of our application. For example we would like a certain window to be able to export its contents to an xml file, also one of our dbservers should do this, also a "CustomerDetails" class should be able to export the customer data to xml. Without interfaces, we could do this by simply adding a "ExportToXml" method in each class that needs to do this:

Code: Select all

CLASS MySpecialWIndow INHERIT DataWindow
METHOD ExportToXml(cFileName AS STRING) AS LOGIC
...
RETURN lSuccess

CLASS MySpecialDBServer INHERIT DBServer
METHOD ExportToXml(cFileName AS STRING) AS LOGIC

CLASS CustomerDrtails
EXPORT FirstName,LastName AS STRING
METHOD ExportToXml(cFileName AS STRING) AS LOGIC
and then it makes sense to have a single function for exporting to xml, which asks the user for a filename etc. Without interfaces, we would use an untyped parameter:

Code: Select all

FUNCTION GeneralExportToXmlFunction(oObject AS USUAL) AS LOGIC
LOCAL cFilename AS STRING
LOCAL lResult AS LOGIC
cFileName := OpenDialogThatAsksForFilename()
IF .not. Empty(cFileName)
   lResult := oObject:ExportToXml()
ELSE
   lResult := FALSE
ENDIF
RETURN lResult
and now we can call this function with any object. If the object we pass it to does have the ExportToXml() method, then it will execute correctly doing it's job, and that's what we are doing most of the time in VO. But what happens if we pass it (by accident, by typo or for any other reason) an object that does not have this method? The compiler will not complain at all, but we will get an error at rutnime. We can even accidentally do this

Code: Select all

GeneralExportToXmlFunction("sorry, mistake!")
and we will have no idea that there is a problem in our code. The compiler (both in VO and X#) will accept it, we can deliver the application to our customers, and only let's say a week later one of our users will give us a call complaining that the export to xml functionality is not working for a particular item of our program, but they are instead getting an application crash when they try to use this.

But, if we had interfaces available in VO (as we do in X#), we could had defined one like that:

Code: Select all

INTERFACE IExportToXml
METHOD ExportToXml(cFileName AS STRING) AS LOGIC
END INTERFACE
and make our classes implement this interface:

Code: Select all

CLASS MySpecialWIndow INHERIT DataWindow IMPLEMENTS IExportToXml
METHOD ExportToXml(cFileName AS STRING) AS LOGIC

CLASS MySpecialDBServer INHERIT DBServer IMPLEMENTS IExportToXml
METHOD ExportToXml(cFileName AS STRING) AS LOGIC

CLASS CustomerDrtails IMPLEMENTS IExportToXml
METHOD ExportToXml(cFileName AS STRING) AS LOGIC
and also strongly type the GeneralExportToXmlFunction() :

Code: Select all

FUNCTION GeneralExportToXmlFunction(oObject AS IExportToXml) AS LOGIC
LOCAL cFilename AS STRING
LOCAL lResult AS LOGIC
cFileName := OpenDialogThatAsksForFilename()
IF .not. Empty(cFileName)
   lResult := oObject:ExportToXml()
ELSE
   lResult := FALSE
ENDIF
RETURN lResult
What we gained now, is that we are absolutely sure we can pass to this function only the correct objects. We cannot any longer accidentally pass it a string, an int, or any class that is not guaranteed to have a ExportToXml() method. So if one of our programmers used a wrong class, for example

Code: Select all

LOCAL oObject AS AnotherWindow
oObject := AnotherWindow{}
GeneralExportToXmlFunction(oObject)
the compiler will now find the problem at compile time and tell us that AnotherWIndow does not implement IExportToXml. So we have found the problem at our own office, before delivering our app, instead of having the users discover it. So now we can either fix the code (maybe "MySpecialWIndow" was intended to be used instead of "AnotherWindow", or maybe we had just forgotten to add a ExportToXml() method (implement the interface) to the AnotherWindow class), and now that we were warned, we will do it.

It's like all cases where we have the choice to use strongly typing or not. Strong typing indeed does require to write a bit more code, on the other hand it results to faster execution and helps us find problems (with the help of the compiler) at compile time, instead of having users (or testers) find them at runtime, possibly after a long time.

Note that using a common base (ABSTRACT would be best) class can also be used often in cases like that. But not in our case, because it would require all our objects to inherit from that class. But by using an interface, we can have a class inheriting from DataWindow, another from DBServer, and another with no parent at all, all very different classes one to the other, but which all do one common things, they are all guaranteed to have a method named ExportToXml() that we can call with strongly typed, efficient and robust code.

Hope this further helps a bit to explain the beauty behind interfaces!
Chris Pyrgas

XSharp Development Team
chris(at)xsharp.eu
ic2
Posts: 1858
Joined: Sun Feb 28, 2016 11:30 pm
Location: Holland

Interfaces, Abstract classes etc

Post by ic2 »

Hello Chris,

This is a very useful addition to the much appreciated presentation of Stefan. And for the situation you describe I can clearly see that an interface is just as useful as strong typing in methods/classes.

What I find more difficult to imagine is if I would ever write something that would benefit from an interface. In your sample, I would create an ExportToXML method SomeWindow (one for a class where I create XML that needs exporting) which would collect the specific data using the specific requirements in that class or that window, and then call a strong typed function say ExportXMLNow(sXML as string) as logic instead of passing the source object where the xml should be created, to that function. That should solve the problem much more elegantly, with less code and the same level of compiler checking, I would say....

So far, almost (or all) every example of how & when to use an interface I saw could be solved better with less code without one, as in the sample above.

In my VO programs, there are just a few vulnerable pieces of code:

1 Send() calls, which could easily send something not or wrong without the compiler seeing that. Earlier we discussed that this can be solved better in X#
2 Untyped methods because the method is not in the same library as the class. That can't be solved at all in DotNet and needs rewriting, essentially
3 Just this week we had a run time error at a client which actually surprised me.

oFTP:=FtpAX{cFtp, dwPoort, dwTimeout, cRemoteDir, cUser, cPW,TRUE,FALSE,FALSE,FALSE}

missed the last 3 parameters (in bold). But the underlying INIT of FtpAX is fully strong typed!

Not sure why the compiler doesn't complain. Probably also something which wouldn't happen in X#.

Still nothing (I would say) that an interface would have solved. Nevertheless, Stefan's and your explanation at least gave some more insight. Thank you both for that.

Dick
User avatar
Chris
Posts: 4906
Joined: Thu Oct 08, 2015 7:48 am
Location: Greece

Interfaces, Abstract classes etc

Post by Chris »

Hi Dick,

An interface would be exactly what would solve the problem with the untyped methods, also with the Send() calls. You just need to define the interface (or interfaces) in one base library, used by everything else in your application and then make calls to objects typed as the above interfaces.

If you want, please give a specific sample of what's being defined and called in libraries say A,B,C and will show you how to do it with interfaces.
Chris Pyrgas

XSharp Development Team
chris(at)xsharp.eu
ic2
Posts: 1858
Joined: Sun Feb 28, 2016 11:30 pm
Location: Holland

Interfaces, Abstract classes etc

Post by ic2 »

Hello Chris,

This is a combination of both situations. We have an email library. There are a few methods of classes defined in other libraries in that email library, e.g.one to send a new mail, here called from a document management window where the link to the mail to create is stored in the document database:

METHOD SendEMails(cNr, lSilent,lSMTP) CLASS DocVenster
(which we can't strong type as the class definition is not in that libvrary)

called from the document window in the other library by:

Send(SELF,#SendEMails,SELF:Server:FIELDGET(#nr),false,SELF:oDCcbSMTP:Checked)

Would be interesting to see how I would solve that better in X#.

Do you know why the VO compiler accepted omitting the 3 parameters on the strong typed INIT (the 3rd sample)?

Dick
User avatar
Chris
Posts: 4906
Joined: Thu Oct 08, 2015 7:48 am
Location: Greece

Interfaces, Abstract classes etc

Post by Chris »

Hi Dick,

In this case, you just need simple inheritance. In the library define the base class that you can even mark abstract, so it does not get instantiated directly:

ABSTRACT CLASS DocVenster_Base
ABSTRACT METHOD SendEMails(<typed parameters>) CLASS DocVenster
METHOD OtherCodeInThisMEthod()
SELF:SendEMails() // this can still be called from here

and in the other library

CLASS DocVenster INHERIT DocVenster_Base
METHOD SendEMails(<typed parameters>) CLASS DocVenster
// this method contains the actual code

and then you instantiate only this subclass, not the base class DocVenster_Base.

Another option would be to simply move the SendEMails() code from the other library, to the library where you have declared this class. If this code is using some other class or a function not available in the library, so it is preventing you to define it there, you can still do it in X#, by using a delegate or an interface, depending on what the code does. All completely strongly typed. If you can post a full sample we can show the exact solution.

About the problem the VO compiler did not detect, I am not sure, but maybe it is related to what Robert mentioned in the beginning of the session (just before you joined), that for performance reasons indeed the compiler does not make some checks. You can watch this again in the X# youtube channel, once the presentation is uploaded there.
Chris Pyrgas

XSharp Development Team
chris(at)xsharp.eu
Post Reply