Interfaces, Abstract classes etc
Posted: Thu May 20, 2021 4:14 pm
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:
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:
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
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:
and make our classes implement this interface:
and also strongly type the GeneralExportToXmlFunction() :
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
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!
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
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
Code: Select all
GeneralExportToXmlFunction("sorry, mistake!")
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
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
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
Code: Select all
LOCAL oObject AS AnotherWindow
oObject := AnotherWindow{}
GeneralExportToXmlFunction(oObject)
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!