Strategy Pattern using Apex
Introduction
In this article, we will discuss what a strategy pattern is and how we can use it to implement scalable customization in Salesforce.
The strategy pattern is one of the behavioral patterns in object-oriented programming. This design pattern is useful when a program needs to choose a specific algorithm from a family of algorithms based on a context or instruction.
For example, you might have a requirement where a certain validation needs to run based on the state of a record. In this case, the appropriate algorithm can only be determined at runtime. The strategy pattern is ideal for this scenario.
In theory, you could achieve the same thing with the Callable interface in Apex. With Callable, you can pass the context or action and handle the validation based on the passed action parameter. But over time, the types of validation grow, and so does your callable class. Each time, you would find yourself adding new conditions and perhaps new handler classes to implement the new validation. This will eventually create technical debt, which will make your application difficult to maintain and scale.
Strategy Pattern and Programming Design Principles
Before we dive into how to use the strategy pattern, it's worth noting some programming design principles it satisfies.
First is the single responsibility principle. By applying the strategy pattern, you break down your algorithm into separate modules, each with one responsibility. This makes it easy to identify what each class does and makes testing simpler.
The second principle is open/closed. Whenever a new algorithm is needed, you just add another subclass without changing the parent class. This adheres to the open/closed principle, reducing the chance of breaking the application when adding new features.
The third principle is dependency inversion. This means we depend on interfaces and abstractions rather than concrete classes. In the strategy pattern, we always refer to the interface when performing the algorithm. This makes bug fixing, testing, and even code versioning much easier.
Lastly, by combining these principles, we create decoupled code that is more maintainable, scalable, reusable, and testable.
How to do it in Apex?
Scenario:
For demonstration purposes, here is a simple requirement where the strategy pattern would be applicable:
Whenever a quote is changed to "ordered," order items are created based on the quote line items. Each order line item creation should result in the creation of an Asset. Additionally, some products require validation before creating an asset, or a record must be created on top of an Asset record, or even a notification must be sent after the Asset creation.
Design
Based on the Strategy Pattern UML Diagram, we can design our Apex classes as below:
Code
Interface
public interface IProductFulfillment {
public void createAsset(OrderItem oi);
}
Implementation
public class ProductA implements IProductFulfillment {
public void createAsset(OrderItem oi) {
if (oi.Website__c == null) {
Website__c website = createWebsite();
Asset asset = buildAsset();
// set other fields with details based on OrderItem
asset.Website__c = website.Id;
insert asset;
}
}
private void createWebsite(){
// create website here
}
}
public class ProductB implements IProductFulfillment {
public void createAsset(OrderItem oi) {
Asset asset = buildAsset();
// set other fields with details based on OrderItem
insert asset;
sendEmail();
}
private void sendEmail() {
// send email here
}
}
Context
In the code below, we are using Custom Metadata Types to store the mapping between the product names and fulfillment handlers.
public class ProductFufillmentContext {
public static final Map<String, IProductFulfillment> productHandlerMap;
static {
productHandlerMap = new Map<String, IProductFulfillment>();
for (Product_Fulfillment_Handlers__mdt handlerMdt : Product_Fulfillment_Handlers__mdt.getAll().values()) {
productHandlerMap.put(handlerMdt.Product_Name__c,
(IProductFulfillment) Type.forName(handlerMdt.Handler_Name__c).newInstance()
);
}
}
private IProductFulfillment productHandler;
public ProductFufillmentContext(String productName) {
productHandler = productHandlerMap.get(productName);
}
public void createAsset(OrderItem oi){
productHandler.createAsset(oi);
}
}
To execute
OrderItem oi = // get order item;
ProductFufillmentContext ctx = new ProductFufillmentContext(oi.Product2.Name);
ctx.createAsset();
As you may have noticed, the DML operations were executed in the implementation class. But what about bulkification?
Instead of executing the DML operations in the implementation class, we can modify it to return a list of SObjects and then execute the DML on that list. There's also an interesting design pattern called Unit of Work that can help simplify transaction management and DML bulkification.
In summary:
This article explores the strategy pattern in Salesforce, a design pattern that helps select specific algorithms from a family of algorithms at runtime. It discusses its relevance to programming principles like single responsibility, open/closed, and dependency inversion, contributing to more maintainable and scalable code. Illustrated with a scenario of creating assets from order items, the article provides a detailed example of implementing the strategy pattern using Apex, highlighting the use of interfaces, custom metadata types, and potential for DML bulkification.
Subscribe to my newsletter
Read articles from JESCY QUERIMIT directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by