Developers / Admins

Why Object-Oriented Programming Is Vital Knowledge for Salesforce Developers

By Luke Freeland

In my previous Object-Oriented Design Principles Article, some Object-Oriented design principles in Apex were covered. However, why is Object-Oriented Programming in Apex vital knowledge for Salesforce Developers? There are many reasons but the primary ones, for me, are 1) it allows you to create better, reusable solutions, and 2) it provides more capabilities than only using imperative Apex.

Let’s look at this more closely via dynamic programming and the Template Pattern.

Dynamic Dependency Injection

Dynamic Dependency Injection is when a class is dynamically instantiated at runtime based on a configuration that tells Apex which class to instantiate. This is possible because of the Type class and its forName and newInstance functions provided by Salesforce.

This is primarily used by Salesforce ISVs who create AppExchange applications as it allows them to make their applications customizable – they can change the code at runtime for each customer to suit their needs.

Note: Many developers in the Salesforce ecosystem will use the term “Dependency Injection” for what I refer to as Dynamic Dependency Injection. The latter is preferred as it’s more qualified and less ambiguous.

Pricing Example

Suppose that we work for an ISV and our product has standard pricing logic to price a product but each customer may have their own pricing logic. Let’s start with an interface named ProductPricer:

global interface ProductPricer {
    Decimal price(Id productId);
}

It’s defined as global so that it can be accessed from within our package and in subscriber orgs, also known as our customer orgs. It has one function defined name price. It takes in the id of the product to price and it returns that product’s price as a Decimal.

Next, let’s create the StandardProductPricer that’s bundled with the product:

global with sharing class StandardProductPricer implements ProductPricer {
    global Decimal price(Id productId) {
        // Standard pricing here...
        return null;
    }
}

The StandardProductPricer implements the ProductPricer interface and its price function. It is global so that it can be used within the product and in subscriber orgs. The actual pricing logic is omitted for brevity but suppose it actually does the pricing logic.

Next, let’s create a ProductPricerFactory class that is responsible for creating the appropriate ProductPricer based on the configuration:

global with sharing class ProductPricerFactory {
    global static ProductPricer create() {
        Product_Pricer_Settings__c pricerSettings = Product_Pricer_Settings__c.getOrgDefaults();

        Type pricerType = Type.forName(pricerSettings.Product_Pricer_Class__c);

        return (ProductPricer) pricerType.newInstance();
    }
}

The ProductPricerFactory has one function named create. The create function uses a custom setting named Product_Pricer_Settings__c to determine the product pricer to instantiate dynamically. After getting the settings record, it uses the Type.forName function to reference the specified pricer’s Type and then it uses the newInstance function to instantiate it and then return it as the casted interface. This allows the caller to have a ProductPricer but not have to know which particular one it is using!

The Apex code in the package and in the subscriber orgs can simply use the factory like this:

ProductPricer pricer = ProductPricerFactory.create();

Decimal price = pricer.price('productIdHere');

Now, if a particular customer needs custom pricing logic, a custom pricing class can be created and the Product_Pricer_Settings__c custom setting can be updated to specify the new one. No code should have to change!

Product Pricer Settings Custom Setting Definition

Here’s the definition for the Product Pricer Settings custom setting. This can easily be configured using a custom metadata type, a custom label, a custom object, or somewhere else.

Template Pattern

The template pattern is a design pattern where a class provides the common logic, but then it can be extended to provide additional functionality. One way to implement the Template pattern is through a virtual class where subclasses can override the virtual function(s) to provide their additional or custom functionality.

To illustrate this, suppose that we have various automation that is needed when a user becomes disabled depending on their position. For a regular employee, disabling their user is the standard. For a manager, it’s that and other automation. For an HR employee, it’s even another automation.

Let’s start with the common way of implementing this in the UserDisabler class:

public with sharing class UserDisabler {
    public void disable(User userToDisable) {
        userToDisable.IsActive = false;

        if (userToDisable.UserRole.Name == 'Manager') {
            // Extensive Disabling Logic
        }
        else if (userToDisable.Profile.Name == 'Human Resources') {
            // Other Extensive Disabling Logic
        }
        // Other positions to disable here
    }
}

If this logic is only for a few positions, this code would be acceptable to me, but it contains many positions, which are omitted for brevity. Let’s refactor this code to use the Template pattern so that the UserDisabler class is a virtual class providing the common logic for disabling a user regardless of their position:

public with sharing virtual class UserDisabler {
    public void disable(User userToDisable) {
        userToDisable.IsActive = false;

        customDisable(userToDisable);
    }

    protected virtual void customDisable(User userToDisable) { }
}

The UserDisabler class is now a virtual class whose disable function sets the IsActive field to false. It invokes the customDisable virtual function so that a subclass can override it to provide any custom disabling logic it needs. A subclass is created for each position requiring custom disabling logic.

For example, the ManagerUserDisabler class:

public with sharing class ManagerUserDisabler extends UserDisabler {
    protected override void customDisable(User userToDisable) {
        // Extensive Manager Disabling Logic
    }
}

To determine which “UserDisabler” class to use, a factory class can be used which returns a UserDisabler like this:

public with sharing class UserDisablerFactory {
    public static UserDisabler create(User userToDisable) {
        if (userToDisable.UserRole.Name == 'Manager') {
            return new ManagerUserDisabler();
        }
        // Instantiate other user disabler subclasses based on position here

        return new UserDisabler();
    }
}

This factory class has the create function. The create function takes in the user to disable and based on that determines which “UserDisabler” class to instantiate and use. It tries instantiating the position-specific one first but if it’s a regular employee, then the “UserDisabler” is used.

This pattern provides multiple benefits:

  • If additional common logic is needed, it’s added to the UserDisabler aka the template class and all the other subclasses automatically inherit that functionality.
  • The code is more modular so that each class can be worked on independently and tested independently.
  • It is more maintainable since there are more classes but each has less code in it.

Summary

Hopefully, this article has provided valuable insights into Dynamic Dependency Injection within the Salesforce ecosystem. If you’re interested in more insights, download your copy of the SF Ben Developer Survey today.

Want to learn Object-Oriented Programming? Check out my Object-Oriented Programming in Apex Course.

The Author

Luke Freeland

Luke is a 15x certified Salesforce Architect and full-stack Developer who provides Salesforce technical consulting services out of his company, Metillium, Inc.

Leave a Reply