The Command design pattern encapsulates a request as an object, thereby allowing for parameterization of clients with queues, requests, and operations. It also allows for the support of undoable operations. In Salesforce Apex, you can use the Command pattern to encapsulate all the details of an operation in a single object.
A common functionality in Salesforce CPQ where the Command pattern might be useful is when performing different actions on a Quote object, such as applying discounts, recalculating prices, or generating documents. These operations can be encapsulated as command objects, making them easy to manage and extend.
Here's an example of how you might implement the Command pattern in Salesforce CPQ:
// The Command interface declares a method for executing a command.
public interface ICommand {
void execute();
}
// Concrete Commands implement various kinds of requests.
public class ApplyDiscountCommand implements ICommand {
private Quote quote;
private Decimal discountRate;
public ApplyDiscountCommand(Quote quote, Decimal discountRate) {
this.quote = quote;
this.discountRate = discountRate;
}
public void execute() {
// Logic to apply discount to the quote
quote.TotalPrice *= (1 - discountRate);
update quote;
}
}
public class RecalculatePricesCommand implements ICommand {
private Quote quote;
public RecalculatePricesCommand(Quote quote) {
this.quote = quote;
}
public void execute() {
// Logic to recalculate prices for the quote
// This might involve complex logic and multiple steps
}
}
public class GenerateDocumentCommand implements ICommand {
private Quote quote;
public GenerateDocumentCommand(Quote quote) {
this.quote = quote;
}
public void execute() {
// Logic to generate a document for the quote
}
}
// The Invoker class is associated with one or several commands. It sends a request to the command.
public class CommandInvoker {
private ICommand command;
public void setCommand(ICommand command) {
this.command = command;
}
public void executeCommand() {
command.execute();
}
}
// Usage
CommandInvoker invoker = new CommandInvoker();
Quote currentQuote = [SELECT Id, TotalPrice FROM Quote WHERE Id = :someQuoteId];
// Applying a discount
ICommand applyDiscount = new ApplyDiscountCommand(currentQuote, 0.1); // 10% discount
invoker.setCommand(applyDiscount);
invoker.executeCommand();
// Recalculating prices
ICommand recalculatePrices = new RecalculatePricesCommand(currentQuote);
invoker.setCommand(recalculatePrices);
invoker.executeCommand();
// Generating a document
ICommand generateDocument = new GenerateDocumentCommand(currentQuote);
invoker.setCommand(generateDocument);
invoker.executeCommand();
In this example, we have an ICommand
interface with an execute
method, which must be implemented by all concrete command classes. We implement three concrete commands (ApplyDiscountCommand
, RecalculatePricesCommand
, and GenerateDocumentCommand
) that encapsulate the request details and the operations to be performed on the Quote
object.
The CommandInvoker
class is used to execute these commands. It allows you to set the command to be executed and then calls the execute
method on the command object.
By applying the Command pattern, you can add new commands easily without changing the existing code, which adheres to the Open/Closed Principle. It also allows you to queue, log, or rollback operations if necessary.
2nd Example
Let's say we have different post-price calculation operations that need to be applied to quote lines after the main pricing logic of CPQ has been run. We can define a command interface and create multiple command classes, each encapsulating the logic for a specific operation.
Step 1: Define the Command interface
public interface IQuoteLineCommand {
void execute(List<SBQQ__QuoteLine__c> quoteLines);
}
Step 2: Implement specific Commands
public class ApplyBulkDiscountCommand implements IQuoteLineCommand {
private Decimal discountRate;
public ApplyBulkDiscountCommand(Decimal discountRate) {
this.discountRate = discountRate;
}
public void execute(List<SBQQ__QuoteLine__c> quoteLines) {
for (SBQQ__QuoteLine__c line : quoteLines) {
line.SBQQ__NetPrice__c *= (1 - discountRate);
}
}
}
public class ApplyTaxCommand implements IQuoteLineCommand {
private Decimal taxRate;
public ApplyTaxCommand(Decimal taxRate) {
this.taxRate = taxRate;
}
public void execute(List<SBQQ__QuoteLine__c> quoteLines) {
for (SBQQ__QuoteLine__c line : quoteLines) {
line.SBQQ__NetPrice__c *= (1 + taxRate);
}
}
}
Step 3: Use the Commands in your CPQ logic
public class QuoteLinePriceProcessor {
private List<IQuoteLineCommand> commands = new List<IQuoteLineCommand>();
public void addCommand(IQuoteLineCommand command) {
commands.add(command);
}
public void processQuoteLines(List<SBQQ__QuoteLine__c> quoteLines) {
for (IQuoteLineCommand command : commands) {
command.execute(quoteLines);
}
}
}
public with sharing class QuoteLineCalculator {
public void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
QuoteLinePriceProcessor processor = new QuoteLinePriceProcessor();
// Define the commands to be executed
processor.addCommand(new ApplyBulkDiscountCommand(0.05)); // 5% bulk discount
processor.addCommand(new ApplyTaxCommand(0.07)); // 7% tax
// Process all quote lines with the defined commands
processor.processQuoteLines(quoteLines);
// Bulk update the quote lines with changes
update quoteLines;
}
}
The Command pattern is used here to encapsulate post-price calculation operations into objects (ApplyBulkDiscountCommand
and ApplyTaxCommand
). Each command has an execute
method that applies a specific operation to a list of SBQQ__QuoteLine__c
records.
The QuoteLinePriceProcessor
class acts as an invoker that keeps a list of commands and can execute them in order. This allows for adding, removing, or reordering commands without changing the core logic in the QuoteLineCalculator
class.
The QuoteLineCalculator
class demonstrates how the commands can be utilized in a bulkified manner to process quote lines. It adds the necessary commands to the processor and then executes them, performing all operations in bulk to prevent hitting governor limits.
The Command pattern provides flexibility and extensibility to the pricing logic in CPQ. You can easily add new commands for additional operations, or modify existing ones without affecting other parts of the system.
This pattern is helpful for organizing complex sets of operations, and when you need an undo feature, you can extend the command interface to include an undo
method, allowing you to reverse the operation if necessary.
0 comments:
Post a Comment