Why use an interface in Apex?
Lets say you have some classes defined that provide the same method, but aren't otherwise related. E.g (based off a StackOverflow answer)
interface Flyable {
void Fly();
}
public class Bird extends Reptile implements Flyable {
public void Fly() {
//Flap wings
}
}
public class Plane extends Machine implements Flyable {
public void Fly() {
// throttle, elevators, rudder, ailerons, flaps, ...
}
}
A Bird
and a Plane
aren't related by a common base class, so that can't inherit the implementation of flight. Nor should they, as their implementations to flight are fairly different. But they can both fly.
The interface allows you to perform a common operation on otherwise unrelated objects. E.g.
List<Flyable> flyingThings = GetBirdInstancesAndPlaneInstancesMixed();
for(Flyable item in flyingThings) {
item.Fly();
}
This is exactly where it makes your code cleaner. How would you otherwise make this list of unrelated things fly? You would have a List<Object>
where you need to figure out the type you are dealing with and try and call the applicable method. Getting the runtime-type of an Object in Apex is a pain and there is no switch statement to jump through all the possible options.
Better to have a common interface and let any class that implements it deal with how it gets done.
Thinking about the benefit of Comparable being an interface rather than an abstract base class is similar. You can't have multiple inheritance in Apex, so if it was a base class rather than interface, anything that wanted to be Comparable would ultimately need to extend from it. That's not really what you want, as now all sorts of otherwise unrelated objects are interchangeable at the base level. With Comparable as an interface you can still call compareTo() on anything that implements it to sort it without needing to know anything else about how that object.
Reasons to use an interface:
- Behavior Contract - A common method is needed on otherwise unrelated objects. The implementation of that method could be significantly different. As such, there is little benefit in inheriting the implementation (behavior).
- Coupling - If code is only dependent on the interface then it is easier to change the implementation, as there no/fewer references to a specific class. E.g. I can change the way a plane flys without any risk of making birds fall out of the sky.
Interfaces abstract an algorithm from data. They provide a "contract" between an object and method that uses that object. Implementing an interface guarantees that a given object has a specific type of behavior. In the standard library, we see this in the Schedulable, Queueable, Batchable, Comparable, Iterable, and Iterator interfaces, to name a few.
For example, implementing the Schedulable interface guarantees that System.schedule knows how to run the code inside of the class, while implementing Comparable allows List.sort to know how to sort a collection of that object. This allows a given method to know how to interact with objects that may well be implemented after the original code was written. Without interfaces, you'd have to write a new copy of a given algorithm for every new data type.
Without Comparable, List.sort would have to have a different implementation of the code for every data type, and would also require developers to write their own sort algorithm every time they created a new class that they wanted to be able to sort. Most of the asynchronous features of Salesforce (Queuable, Schedulable, and Batchable) would not be possible in a strongly typed language like Apex Code without interfaces or reflection. Since we don't have proper reflection in Apex Code, interfaces are absolutely required in order to implement these features within the confines of the language.
Interfaces are not needed in languages like Ruby or JavaScript, because these languages can inspect objects dynamically to determine if they support a certain method. However, strongly typed languages like C++, Java, and Apex Code need a way for the compiler to guarantee that a given object supports a given method, otherwise it could not reasonably guarantee that the code would run without runtime exceptions because of simple typos.
In summary, interfaces allow us to plug in user-defined types into algorithms, which reduces the amount of code that has to be maintained. We only need one copy of an algorithm to support many diverse types of objects without the developer needing to be concerned about the underlying data.
As a very specific example, I once designed a configuration page that enabled use to toggle triggers and integration callouts (external) in a standardized format. While all integration callouts were based on a single abstract parent class, and all trigger classes were based on a single virtual parent class (to allow copy-paste definitions for each object in the system that needed a trigger), they needed a common definition to provide on-screen help in a way that didn't rely on modifying the Visualforce's source every time a new class was added. Using an interface (call it IHelpText), we were able to add functionality that was common to two entirely different types of classes without having to provide an abstract parent class to both the callout classes and trigger classes. The code also allowed those classes to be upgraded to provide help "in the future", so it didn't have to be implemented at the time the new class was created.
Using a set of interfaces, I built a modular batch process. This allows me to perform simple tasks in production without deploying new code, without exporting, transforming, and importing data, reduce the number of batch classes I needed to have in the system. For example, I would have two classes that look like this:
public class GenerateRecordsFromQuery implements Batch.Generator {
String query;
public GenerateRecordsFromQuery(String query) {
this.query = query;
}
public Object generate() {
return Database.getQueryLocator(query);
}
}
public class UpdateRecordsAction implements Batch.Action {
public void perform(Object[] records) {
update (SOBject[])records;
}
}
This allows me to write code (in an Execute Anonymous window) in production that looks like this:
Database.executeBatch(
new Batch(
new GenerateRecordsFromQuery('select id from lead where isconverted = false'),
new UpdateRecordsAction()));
By building single modules, I can use them in any combination I desire. I can give them whatever constructor I want and use them in any reasonable combination.
Finally, one important thing to consider is that interfaces can be mixed and matched. You can't do that with abstract or virtual classes, because each child can only inherit from one parent. However, one class can implement any number of interfaces, allowing a single class to have multiple behaviors. This makes it easy for classes that have nothing in common except for a single feature can share that feature without a common parent.
This question is quite broad, but I have written or worked with few interfaces which really improve my experience with Apex
. The big win from all of them is that they allow other programs to extend a library with minimal effort.
Your interface
provides you with guaranteed functionality, in terms of input/output pairings. This contract allows you to take a generic object and use it in a well defined way.
The provider architecture I implemented in my SObjectFactory library is a simple example that will let you see both sides of how you can benefit from an interface.
The interface can be as simple as:
public interface IFieldProvider { Object getValue(); }
A basic implementation example (others here):
public class UniqueNumberProvider implements IFieldProvider
{
Integer counter;
public UniqueNumberProvider() { this(0); }
public UniqueNumberProvider(Integer start) { this.counter = start; }
public Object getValue() { return counter++; }
}
How it's used behind the scenes (simplified):
public static SObject build(SObjectType sObjectType, Map<SObjectField, Object> fields)
{
SObject record = sObjectType.newSObject();
for (SObjectField field : fields.keySet())
{
Object value = fields.get(field);
if (value instanceOf IFieldProvider)
value = ((IFieldProvider)value).getValue();
record.put(field, value);
}
return record;
}
What makes this architecture really useful is if you want to write your own provider. This pattern made it easy for me to write as many providers as I could think of and plug them in as needed in my tests.
You can see a similar advantage in the Selector library.