Enums gave AL an extensible list type; interfaces gave it a way to vary behaviour by that list without a case statement. Together they are the pattern behind price calculation, document sending and most modern base-application design. A worked example from an enum value to a working implementation another app can extend.
The patterns post named the strategy pattern; this is the full worked version, end to end, because it is the one most worth having in muscle memory.
A courier shipment has a carrier. Each carrier has its own API, label format and tracking URL. The naive code is a case Shipment.Carrier of in three procedures, and every new carrier edits all three.
interface "CON ICarrier"
{
procedure Send(var Shipment: Record "CON Courier Shipment");
procedure GetLabel(Shipment: Record "CON Courier Shipment"; var TempBlob: Codeunit "Temp Blob");
procedure TrackingUrl(Shipment: Record "CON Courier Shipment"): Text;
}
enum 50120 "CON Carrier" implements "CON ICarrier"
{
Extensible = true;
DefaultImplementation = "CON ICarrier" = "CON Carrier Not Set";
value(0; " ") { Caption = ' '; }
value(1; RoyalMail) { Caption = 'Royal Mail'; Implementation = "CON ICarrier" = "CON Carrier Royal Mail"; }
value(2; DPD) { Caption = 'DPD'; Implementation = "CON ICarrier" = "CON Carrier DPD"; }
}
Each value names the codeunit that implements the interface for it. DefaultImplementation covers values that do not specify one (and values other apps add without an implementation).
codeunit 50121 "CON Carrier Royal Mail" implements "CON ICarrier"
{
procedure Send(var Shipment: Record "CON Courier Shipment")
begin
// Royal Mail-specific API call
end;
procedure GetLabel(Shipment: Record "CON Courier Shipment"; var TempBlob: Codeunit "Temp Blob")
begin
end;
procedure TrackingUrl(Shipment: Record "CON Courier Shipment"): Text
begin
exit(StrSubstNo(RoyalMailTrackUrlTok, Shipment."Tracking No."));
end;
}
The compiler enforces that every procedure in the interface is implemented. Nothing in the implementation knows about the others.
procedure Send(var Shipment: Record "CON Courier Shipment")
var
Carrier: Interface "CON ICarrier";
begin
Shipment.TestField(Carrier);
Carrier := Shipment.Carrier; // enum to interface: the platform picks the codeunit
Carrier.Send(Shipment);
end;
That is the whole dispatch. No case statement, anywhere.
enumextension 60100 "FAB Carrier Ext" extends "CON Carrier"
{
value(60100; Evri) { Caption = 'Evri'; Implementation = "CON ICarrier" = "FAB Carrier Evri"; }
}
A different app, a different publisher, no change to your code. Your pages show the new value because the field is the enum; your dispatch calls the new codeunit because the value names it.
local."CON ICarrier v2") and have the enum implement both for a release.Once you have done this once, you will see it everywhere in the base application: "Price Calculation", "Document Sending Profile", the e-document framework, the AI agents. It is how Microsoft makes a behaviour extensible, and it is how you should.