alguidelines/content/docs/patterns/builder-pattern/index.md
2025-05-16 19:21:57 +02:00

6.8 KiB

title tags categories
Builder
AL
Decoupling
Readability
Testability
Extendability
Pattern
Creational

Created by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (Gang of Four), Described by Elton Werkmeister

Abstract

The Builder Pattern is one of 23 remarkable design patterns described in the book "Design Patterns". Its intent is to make the costruction of complex objects in a beautiful way not so complex, while solving various other object creation problems.

With the newly introduced "this" operator in Version 14 of AL, the Builder Pattern finally found its way to BC. Let me show you how it works.

Context

Whenever you have an object with a lot of fields or configuration options that need to be set in a specific or flexible way, the Builder Pattern can help simplify the creation process.
It is especially useful when the object initialization logic would otherwise lead to complex constructors or scattered setup code.

Problem

The Builder Pattern helps to solve the following problems:

Object signatures are often overloaded with parameters...

certain procedure demand a long list of parameters, which can reduce readability and increase the risk of passing values in the wrong order, especially when several parameters share the same data type.

Objects might have parameters that are actually optional...

Not every object requires all fields to be set in every context. Handling optional parameters often leads to overloaded code or conditional initialization scattered across the application.

Object creation frequently involves complex logic...

Instantiating some objects requires multiple setup steps, conditional logic, or validation rules. Without a clear structure, this logic often ends up in large, blocks of code that are hard to test and maintain.

Description

The Builder Pattern is one of the creational design patterns. A so-called builder simplifies the creation of objects by allowing the user to assemble objects step by step.
It exposes several small, focused methods that store data in global variables within the builder.
A key feature is that these methods return the builder itself, enabling method chaining for fluent and readable object construction.

The Pattern

The Builder Pattern is easy to implement and understand.

This pattern is particularly easy to understand and implement because of its clear structure.
Imagine this very simple example of a SMTP Mail setup table, that prove the point of the pattern:

Field Name Type
Server Text[100]
Port Integer
UserID Text[100]
UserPassword SecretText
AuthRequired Boolean
ConnectionType Enum Connection Type
IsDefaultConfig Boolean

Without the builder pattern, a "CreateSmtpSetup" procedure could look something like this:

procedure CreateSmtpSetup(
    Server: Text[100];
    Port: Integer;
    UserID: Text[100];
    UserPassword: SecretText;
    AuthRequired: Boolean;
    UseSSL: Boolean;
    IsDefaultConfig: Boolean
): Record "SMTP Mail Setup"
var
    Setup: Record "SMTP Mail Setup";
begin
    Setup.Init();
    Setup."Server" := Server;
    Setup."Port" := Port;
    Setup."AuthRequired" := AuthRequired;
    Setup."UserID" := UserID;
    if UseSSL then
        Setup."ConnectionType" := Enum::ConnectionType::SSL
    else
        Setup."ConnectionType" := Enum::ConnectionType::Other;
    Setup."UseSSL" := UseSSL
    Setup."IsDefaultConfig" := IsDefaultConfig;
    exit(Setup);
end;

The call to that procedure like this:

procedure XYZ()
var
    SmtpSetup: Record "SMTP Mail Setup";
begin
    SmtpSetup := CreateSmtpSetup(
        'smtp.office365.com',
        587,
        'admin@yourdomain.com',
        'JohnDoe12345',
        true,
        true,
        true
    );
    SmtpSetup.Insert(true);
end;

You probably see that the procedure itself as well as the calling of said procedure is or could become very confusing and tedious.
Let me show you the definition of the corresponding builder.

Codeunit 99999 "SmtpSetupBuilder" {
    var
        SmtpSetupProduct: Record SmtpSetup;

    procedure Init(): Codeunit "SmtpSetupBuilder" begin
        SmtpSetupProduct.Init();
    end;

    procedure Server(Value: Text[100]): Codeunit "SmtpSetupBuilder" begin
        SmtpSetupProduct := Value;
        exit(this);
    end;

    procedure Port(Value: Integer): Codeunit "SmtpSetupBuilder" begin
        SmtpSetupProduct := Value;
        exit(this);
    end;

    procedure UserID(Value: Text[100]): Codeunit "SmtpSetupBuilder" begin
        SmtpSetupProduct := Value;
        exit(this);
    end;

    procedure UserPassword(Value: SecretText): Codeunit "SmtpSetupBuilder" begin
        SmtpSetupProduct := Value;
        exit(this);
    end;

    procedure AuthRequired(Value: Boolean): Codeunit "SmtpSetupBuilder" begin
        SmtpSetupProduct := Value;
        exit(this);
    end;

    procedure ConnectionType(Value: Enum ConnectionType): Codeunit "SmtpSetupBuilder" begin
            SmtpSetupProduct := Value;
            exit(this);
    end;

    procedure IsDefaultConfig(Value: Boolean): Codeunit "SmtpSetupBuilder" begin
        SmtpSetupProduct := Value;
        exit(this);
    end;

    procedure Build(var SmtpSetup: Record SmtpSetup): Codeunit "SmtpSetupBuilder" begin
        SmtpSetup := SmtpSetupProduct;
        exit(this);
    end;
}
procedure XYZ()
var
    SmtpSetupBuilder: Codeunit "SmtpSetupBuilder";
    SmtpSetup: Record "SMTP Mail Setup";
begin
    SmtpSetupBuilder
    SmtpSetup := CreateSmtpSetup(
        'smtp.office365.com',
        587,
        'admin@yourdomain.com',
        'JohnDoe12345',
        true,
        true,
        true
    );
    SmtpSetup.Insert(true);
end;