From Xojo Documentation
|You are currently browsing the old Xojo documentation site. It will go offline as of October 2, 2023. Please visit the new Xojo documentation site!|
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.
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 adds 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 that a class implements more than one method with the same name but from different class interfaces. You may have decided that a method implements two interfaces that both have a method that shares the same name and declaration. Normally, there is no way to tell them apart. However, there is a way to specify the class interface to which each method belongs. You can do so using the optional Interfaces field in the method declaration dialog box, available using the “Show Implements” contextual menu of the method.
To use it, suppose you have two class interfaces, Foo and Bar, that both contain a method named “wahoo” that has no parameters. You can have a class called (Baz) that implements both methods separately.
Add the wahoo method to Baz. Then right-click (control+click on MacOS) on the method name and select “Show Implements”. This displays an additional field in the Inspector called “Implements”. Here you can specify the name of the interface and method that this method actually implements: Foo.wahoo.
Now add another method called wahoo and select “Show Implements” for it. Specify “Bar.wahoo”. Now you have told the class exactly which interface methods it implements.
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.
Polymorphism is the ability to have completely different data types (typically classes) behave in a uniform manner. This can be done using class interfaces.
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:
Add another new class (Dog) and select Animal as its interface. The Code Editor adds the Speak method where you can put this code:
To test this, create a button on a window. In the Action event of the button add code to create an array of Animals:
For Each a As Animal In animals
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 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:
Aggregates Test, Awesome
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:
Function Current() As Variant
Function MoveNext() As Boolean
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:
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:
If foo IsA Writeable Then
// Do reading and writing
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:
Aggregates Readable, Writeable
Sub DoSomethingAwesome(foo As ReadableAndWriteable)
Now there will be a compile error if the item doesn’t implement both interfaces. That's a much better solution to the problem.