Contrary to some object oriented programming gurus, I was not much into interfaces. The first uses were under utilized, as just a memory manager assistant (interfaces in object pascal have reference counting maintained by the compiler, contrary to classes).
With the progress of the PressObjects’ development, it becomes necessary more frequently to make some classes completely abstract in order to decouple the implementation of the classes. Something like (tersely summarized):
TIDEIntf = class(TObject) Public procedure ReadText(...); virtual; abstract; procedure SaveText(...); virtual; abstract; end;
Then, an implementation for Delphi, another for Lazarus and still another one for MSEide:
TDelphiIDEIntf = class(TIDEIntf) public procedure ReadText(...); override; procedure SaveText(...); override; end;
Two other identical declarations for Lazarus and MSEide, which differ from the Delphi version only in the implementation of each method. An advantage is to have a single routine that changes the IDE editor’s information (an Expert) through a common interface (the TIDEIntf class) and use this same expert in three different IDEs. The support of a new IDE is done by creating a new class that overloads these virtual methods.
This is a very simple example in which interface can be used instead of the abstract class. There is a disadvantage to the proposed model, which is exactly the propose of interfaces, and it’s not evident as the model does not become a limitation for the programmer (at least for now): the classes that implement the IDE functionality are required to descent from the TIDEIntf.
This disadvantage becomes much more evident when implementing decoupling between an MVP View and the VCL/LCL graphics library.
An MVP View is what is between the framework’s core and a visual control. There exists a View for ComboBox controls, which manages some events such as OnExit, OnDropDown, among others, and translates those to events that the framework understands. Same goes for a View that manages an Edit. Events such as OnEnter and OnChange are redirected.
Other than managing the events, a View has methods to access or manage and avoiding that the framework has direct access to the control (avoiding coupling). For example, a ComboBox View has a DropDown method, that makes the control drop down a list. The Edit and ComboBox have a property AsText, that reads and writes the control’s Text property.
Having understood how the View works, lets proceed to the next part. There is a necessity to decouple the View from the VCL classes. Why? Because there are other excellent options that can be used with the MVP instead of the VCL. Some of these are MSEgui, Kol/Mck, and for FPC (FreePascal Compiler) there is fpGUI (the LCL does not count as alternative, since it has the same interface as the VCL and its Views do use the same implementation).
My first idea was to create abstract classes, like the above examples, one for a ComboBox View and another for the Edit View. In simplified form it would be like this:
TEditView = class(TObject) public procedure Text(...); virtual; abstract; end; TComboBoxView = class(TObject) public procedure DropDown; virtual; abstract; procedure Text(...); virtual; abstract; end;
And the concrete classes would be like so:
TVCLEditView = class(TEditView) public procedure Text(...); override; end; TVCLComboBoxView = class(TComboBoxView) public procedure DropDown; override; procedure Text(...); override; end;
Different units would implement different set of classes. One for VCL/LCL, another for MSEgui, another for fpGUI, etc. To support a new graphics library, just implement a unit that overrides all these classes’ methods.
So we have virtual methods in the abstract class, and in another unit exists concrete classes that overrides such methods in order to reach the visual components. The framework’s core accesses this abstract class, and the concrete class is the one that really accesses the visual control as this is coupled to the graphics library. A class register indicates that the user will use VCL, MSE or any other library.
Now the problem: Note that some components share characteristics, in the above example both ComboBox and Edit have a Text that does the same thing.
To remove the coupling between the View and the VCL, allowing code sharing and behavior through inheritance, I decided to create a set of interfaces that the concrete classes need to implement. The implementation is like so:
ICustomView = interface(IInterface) [...] procedure Text(...); end; IEditView = interface(ICustomView) [...] end; IComboBoxView = interface(ICustomView) [...] procedure DropDown; end;
An the classes that implement the interface, reusing code and maintaining zero coupling:
TVCLCustomView = class(TMyIntfObject, ICustomView) public procedure Text(...); end; TVCLEditView = class(TVCLCustomView, IEditView) end; TVCLComboBoxView = class(TVCLCustomView, IComboBoxView) public procedure DropDown; end;
Note that in this model its possible to reuse the same Text procedure in both classes. In the model that only uses classes, every descendant class needs to implement their own text as they can not inherit from a common class.