Apex Interface Patterns

What Was Actually Done

Patterns

Pattern 1: Selector Interface

When to use: You need reusable, testable SOQL queries that can be swapped or mocked.

Interface:

public interface IContactSelector {
    List<Contact> selectByAccountIds(Set<Id> accountIds);
}

Implementation:

public with sharing class ContactSelector implements IContactSelector {
    public List<Contact> selectByAccountIds(Set<Id> accountIds) {
        if (accountIds.isEmpty()) {
            return new List<Contact>();
        }
        return [
            SELECT Id, FirstName, LastName, AccountId
            FROM Contact
            WHERE AccountId IN :accountIds
        ];
    }
}

Test Double:

@IsTest
private class ContactSelectorStub implements IContactSelector {
    private List<Contact> contacts;

    ContactSelectorStub(List<Contact> contacts) {
        this.contacts = contacts.deepClone(true, true, true);
    }

    public List<Contact> selectByAccountIds(Set<Id> accountIds) {
        return contacts;
    }
}

Pattern 2: Service Interface for External Integrations

When to use: You call external services and need to swap callout vs. mock implementations.

Interface:

public interface INotificationService {
    void sendNotification(String recipient, String message);
}

Production Implementation:

public with sharing class HttpNotificationService implements INotificationService {
    public void sendNotification(String recipient, String message) {
        // Uses Named Credentials and HttpCalloutMock-safe design
        // See integration-examples for callout patterns
    }
}

Test Implementation:

@IsTest
private class InMemoryNotificationService implements INotificationService {
    public List<String> recipients = new List<String>();
    public List<String> messages = new List<String>();

    public void sendNotification(String recipient, String message) {
        recipients.add(recipient);
        messages.add(message);
    }
}

Pattern 3: Factory for Interface Implementations

Use a small factory to choose the concrete implementation, which you can override in tests.

public with sharing class ServiceFactory {
    @TestVisible
    private static INotificationService notificationServiceOverride;

    public static INotificationService getNotificationService() {
        if (notificationServiceOverride != null) {
            return notificationServiceOverride;
        }
        return new HttpNotificationService();
    }
}

In tests:

@IsTest
private class NotificationServiceTests {
    @IsTest
    static void testNotificationsAreSent() {
        InMemoryNotificationService mock = new InMemoryNotificationService();
        ServiceFactory.notificationServiceOverride = mock;

        // Exercise code that calls ServiceFactory.getNotificationService()

        System.assertEquals(1, mock.recipients.size());
    }
}

To Validate