Interfaces

A class interface is a construct that you can use to tie together classes that do not share a super class but have something in common in your application. Class interfaces are used to specify what an object does without specifying how it does it.

In order to understand class interfaces better, it's helpful to think of a class as consisting of two components, the (public) interface to the class and the implementation. The interface consists of the class's Public method calls and the implementation is the code that implements the methods. The interface says what the class does and the implementation says how it does it.

In the object hierarchy, a subclass inherits both the interface and the implementation from its super class. That is, it gets both the method calls and the specific implementation of the method. Class interfaces enable you to separate the two constructs. If two or more classes need to do the same thing but do it in different ways, you use an interface instead of a super class.

A class interface operates as a “spec” that contains a list of methods that custom classes in your project use. It does not actually contain any code for the methods themselves. The methods in the class interface are placeholders for methods that are actually contained in each custom class that “implements” the class interface. Also, a custom class can implement more than one class interface.

The term “implement” means that the class has methods of the same names and declarations that are found in the class interface. The class interface specifies the methods and their declarations but not the code.

Several class interfaces are built-in to the framework. You can implement any of these in your classes or add and implement your own. For example, the Iterator and Iterable interfaces specify methods that you can use to provide a way to iterate through the contents of a class by using the For Each...Next command.

When you specify that a class implements a class interface, the class must implement all the methods in the class interface and the method declarations must match. However, the classes are free to implement the methods in different ways. For example, a method that changes the font in a Label would be implemented in a different way than a method that changes the font in a Canvas control that displays text via calls to the Graphics class. The process involves three basic steps:

  • Creating the class interface and its method definitions

  • Creating the classes that implement the class interface

  • Adding the classes to your project and calling the class interface methods in your program. Typically, that means writing generic code that tests whether a class implements a class interface and executing class interface methods where appropriate.

To add a new class interface, click the Insert button on the toolbar and choose Class Interface or select Class Interface from the Insert menu. This adds a new class interface to the Navigator with the default name (Interface1 for the first class interface). Use the Inspector to change the name of the class interface.

You can only add methods to class interfaces. To add a method to a class interface, use the Add button on the Code Editor toolbar, Insert > Method from the menu, the contextual menu or the keyboard shortcut (Option Command M on MacOS or Ctrl Shift M on Windows and Linux). You cannot specify any code in the class interface, so the Code Editor is disabled. Use the Inspector to specify method names and parameters.

Implementing the Interface methods in a class

To implement the methods of the class interfaces, you need to assign the class interface to a class. To do so, select the class in the Navigator and in the Inspector select the Choose button next to the Interfaces label. You can also use the “Implement Interface” option in the contextual menu of the method in the Navigator.

When you click Choose, the Choose Interfaces dialog displays showing the names of all the class interfaces available to you. Some of the interfaces are ones that are built-in and others are ones you created yourself. Select one or more interfaces to assign to the class.

../../_images/interfaces_class_interface_dialog.png

You can also select the “Include #pragma error” in the source of each method” to force a compiler error to be generated with the message “Don't forget to implement this method!". Remove the pragma after you have added code to implement the interface method.

Press OK to have the Code Editor automatically add the methods of the interface to your class with a comment telling you the interface to which it belongs (along with the optional pragma).

If you modify or change the class interface after you have already applied it to a class, you will need to manually update the class.

If you attempt to compile a project that contains a class with an interface, but the class does not implement all the interface methods, you get a compile error: “This class is missing one or more methods of an interface it implements.”

Any method (public, protected or private) can be used to implement a method.

Modifying and deleting interfaces

If you change your mind and want to delete or replace an interface, you do so the same way you added the interface. When the Interface dialog appears, uncheck the interfaces you no longer want.

Any methods that were added to the class while the interface was implemented are not modified or deleted when you remove the interface that they belonged to. That is, if you add an interface via the Implement Interfaces dialog, the methods that are specified by that interface remain as part of the class even if you delete the interface itself. You must take care of any “clean up” activities.

Specifying the interface being implemented

In rare cases you may find you need a class to implements multiple interfaces each with methods with the same signature (same name and parameters or no parameters at all). This will cause a compiler error because the compiler doesn't know for which interface the method has been implemented. Fortunately, there is a solution using the optional Implements field in the method declaration pane of the Inspector, available using the Show Implements contextual menu of the method.

In this case, after selecting both interfaces, your class will have the duplicate method but with two signatures. First, rename one of the signatures so there is no longer duplication. Next, right-click (Control click on MacOS) on one of the signatures and choose Show Implements from the contextual menu. The Implements field will then appear in the Inspector. In this field, using the standard dot syntax, you can indicate the interface and method name that this signature is implementing.

For example, if you had two interfaces (Foo and Bar) that both include a Test method, in the Implements field, you would indicate Foo.Test for one method and Bar.Test for the other.

Creating a new class interface from an existing class

If an existing class in your project contains methods that you want to extract as a class interface, you can generate the new class interface directly from the Navigator. To do so, select “Extract Interface” from the contextual menu of the method in the Navigator.

The new class interface is added to the project and the current class is made an implementor of the new interface.

../../_images/interfaces_extract_interface.png

Polymorphism

Polymorphism is the ability to have completely different data types (typically classes) behave in a uniform manner. This can be done using class interfaces.

Animal example

Here is how to implement the Animal, Dog and Cat example using class interfaces, like you previously saw using inheritance with overridden methods and inheritance with events.

First, create a new class interface (called Animal). Add to it a Speak method that returns String.

Now create a new class (Cat) and select Animal as its interface. The Code Editor automatically adds the Speak method for you. Add this code to it:

Return "Meow!"

Add another new class (Dog) and select Animal as its interface. The Code Editor adds the Speak method where you can put this code:

Return "Woof!"

To test this, create a button on a window. In the Pressed event of the button add code to create an array of Animals:

Var animals() As Animal
animals.AddRow(New Cat)
animals.AddRow(New Dog)

For Each a As Animal In animals
  MessageBox(a.Speak)
Next

When you run this code, you will see “Meow!” and then “Woof!”.

Because Cat and Dog both implement Animal, they are allowed to be assigned to a variable (the animals array) with a type of Animal. And when you call the Speak method of Animal (in the loop), because of polymorphism, your code calls the Speak method of actual type (Dog or Cat) that was added to the array.

Interface aggregation

Interface aggregation is the ability to group several interfaces together into a “super interface.” This is an advanced feature that you may not need to use often. Consider these interfaces:

Interface Test
  Sub Foo()
End Interface

Interface Awesome
  Sub Bar()
End Interface

Interface Sweet
  Aggregates Test, Awesome
  Sub Blah()
End Interface

The Sweet interface is an aggregate of the Test and Awesome interfaces.

In this example, there are three interfaces that contain a single method each. If a class were to implement the Test interface, then it would be required to implement just the method Foo. However, if the class were to implement the Sweet interface, then it would have to implement Foo, Bar and Blah. That's because Sweet aggregates both the Test and Awesome interfaces.

Basically, when a class implements an interface, it must implement all of the methods from the interface, as well as methods from any aggregated interfaces. When a class implements an interface, then calling IsA on the class will return True for that interface as well as any aggregated interfaces. From the example above, a class implementing Sweet, then IsA Sweet, IsA Test and IsA Awesome are all True (since it essentially implements all three of the interfaces).

This allows you the ability to combine interfaces in creative ways. Although interfaces don 't necessarily relate to one another, there are times when you need something that satisfies the IsA command for multiple different interfaces. Or, when you want to merge functionality together for interfaces. For instance, say that you want to have an item which can be iterated over. A common interface might be:

Interface Iterator
  Function Current() As Variant
  Function MoveNext() As Boolean
  Sub Reset()
End Interface

This allows you to iterate over the object in a generic fashion. However, it could very well be that creating this iterator will cause circular references, or some sort of memory management issues. In that case, you might want to use a disposable interface, like this:

Interface Disposable
  Sub Dispose()
End Interface

In the example, you could have the Iterator interface aggregate the Disposable interface. This would guarantee that anything which can be iterated over can also be disposed of afterwards.

You might be wondering “so why not just put the Dispose method right onto the Iterator interface?” That's a perfectly legitimate way to accomplish the same goal — however, Iterator and Disposable aren 't the same conceptually. So it doesn't make sense to force the two interfaces together in such a fashion. It doesn't hurt, but it just doesn't help either. Next is an example that would hurt.

Say you have a method that is going to generically take a parameter which represents something that can read and write to or from a source. In this case, you want something that's both Readable and Writeable. You could just declare the method like this:

Sub DoSomethingAwesome(foo As Readable)
  If foo IsA Writeable Then
    ' Do reading and writing
  End If
End Sub

This code will compile and will work, but it's also not safe. The user could pass in something which is Readable, but not Writeable and it will compile. Instead, the user could easily do this instead:

Interface ReadableAndWriteable
  Aggregates Readable, Writeable
End Interface

Sub DoSomethingAwesome(foo As ReadableAndWriteable)
End Sub

Now there will be a compile error if the item doesn't implement both interfaces. That's a much better solution to the problem.