Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
Salesforce Developer
hard

How do you design Apex classes for extensibility — letting other code customize behavior without modifying yours?

Extensibility = others can plug in new behaviour without you re-releasing your code. Patterns:

1. Interface + Implementation registry

`apex public interface IDiscountCalculator { Decimal calculate(Opportunity opp); }

public class DiscountFactory { public static IDiscountCalculator getCalculator(String discountType) { Discount_Calculator_Mapping__mdt mapping = Discount_Calculator_Mapping__mdt.getInstance(discountType); return (IDiscountCalculator) Type.forName(mapping.Apex_Class__c).newInstance(); } } `

Custom Metadata maps a discount type to an Apex class name. Adding a new discount type means adding a metadata record + an Apex class implementing IDiscountCalculator — no edits to the factory.

2. Strategy pattern — multiple implementations of the same interface, chosen at runtime.

apex public class DiscountService { private IDiscountCalculator calculator; public DiscountService(IDiscountCalculator calculator) { this.calculator = calculator; } public Decimal applyDiscount(Opportunity opp) { return calculator.calculate(opp); } }

The caller injects the right strategy.

3. Hooks / callbacks — let extending code register handlers to run at specific points.

`apex public class OrderProcessor { private static List<IOrderHook> hooks = new List<IOrderHook>(); public static void registerHook(IOrderHook hook) { hooks.add(hook); }

public void processOrder(Order o) { for (IOrderHook hook : hooks) hook.beforeProcess(o); // core processing for (IOrderHook hook : hooks) hook.afterProcess(o); } } `

4. Plugin pattern — load implementations dynamically via Custom Metadata listing class names.

apex public class PluginManager { public static List<IPlugin> loadPlugins(String pluginType) { List<IPlugin> plugins = new List<IPlugin>(); for (Plugin__mdt pl : [SELECT Apex_Class__c FROM Plugin__mdt WHERE Type__c = :pluginType AND Active__c = true ORDER BY Order__c]) { plugins.add((IPlugin) Type.forName(pl.Apex_Class__c).newInstance()); } return plugins; } }

5. Configurable behaviour via Custom Metadata — for simpler cases, expose configuration knobs rather than full extensibility.

apex Order_Config__mdt cfg = Order_Config__mdt.getInstance('default'); if (cfg.AutoApprove_Discounts__c) { ... }

6. `global virtual` classes — within a managed package, declare extensibility points as global virtual. Subscribers can subclass and override.

apex global virtual class StandardDiscountCalculator implements IDiscountCalculator { global virtual Decimal calculate(Opportunity opp) { return ...; } } // Subscriber: class CustomDiscountCalculator extends StandardDiscountCalculator { override calculate(...) }

Trade-offs:

  • More extensibility = more complexity. Don't over-engineer for hypothetical needs.
  • Versioning — interface changes break extending code. Once published, interfaces should be stable.
  • Testing — extension points need tests proving registered handlers actually run.
  • Performance — dynamic dispatch is slower than direct calls.

When to use which:

  • Configurable behaviour -> Custom Metadata flags.
  • Multiple implementations chosen at runtime -> Interface + Factory.
  • Pluggable extensions added by other modules -> Plugin pattern.
  • ISV package allowing customer customisation -> global virtual + interfaces.

A well-designed extensible Apex framework is what enables managed packages to remain useful across customer-specific customisations — without the customer having to fork the package.

Why this answer works

Architect-level. Naming the patterns, the trade-off awareness, and the ISV use case signal advanced design experience.

Follow-ups to expect

Related dictionary terms