The Decorator design pattern is a structural design pattern used to dynamically add behavior to an object without altering its structure. In Salesforce Apex, this can be particularly useful when you want to enhance the functionality of an object without changing its existing code, which is a common scenario when dealing with managed packages like Salesforce CPQ (formerly Steelbrick CPQ), where you cannot modify the source code.
A common functionality in Salesforce CPQ is the pricing calculation of QuoteLineItems, where you might need to apply different pricing strategies based on certain criteria. The Decorator pattern allows you to dynamically adjust the pricing calculations without modifying the original QuoteLineItem code.
Here's a simplified example demonstrating how we could use the Decorator pattern in Salesforce Apex to apply different pricing strategies to CPQ QuoteLineItems:
First, let's create an interface that will define the common functionality for our Quote line items:
public interface IQuoteLineItem {
void calculatePrice();
Decimal getPrice();
}
Next, we have a basic implementation of our Quote line item:
public class BasicQuoteLineItem implements IQuoteLineItem {
private Decimal basePrice;
public BasicQuoteLineItem(Decimal price) {
this.basePrice = price;
}
public void calculatePrice() {
// Basic price calculation logic
}
public Decimal getPrice() {
return basePrice;
}
}
Now, we can create our decorators to extend the functionality. Each decorator implements the same interface and takes in an instance of IQuoteLineItem
.
public abstract class QuoteLineItemDecorator implements IQuoteLineItem {
protected IQuoteLineItem decoratedItem;
public QuoteLineItemDecorator(IQuoteLineItem item) {
this.decoratedItem = item;
}
public void calculatePrice() {
decoratedItem.calculatePrice();
}
public Decimal getPrice() {
return decoratedItem.getPrice();
}
}
public class DiscountDecorator extends QuoteLineItemDecorator {
private Decimal discountRate;
public DiscountDecorator(IQuoteLineItem item, Decimal discountRate) {
super(item);
this.discountRate = discountRate;
}
public void calculatePrice() {
super.calculatePrice();
applyDiscount();
}
private void applyDiscount() {
Decimal price = decoratedItem.getPrice();
decoratedItem.getPrice() * (1 - discountRate);
}
}
public class TaxDecorator extends QuoteLineItemDecorator {
private Decimal taxRate;
public TaxDecorator(IQuoteLineItem item, Decimal taxRate) {
super(item);
this.taxRate = taxRate;
}
public void calculatePrice() {
super.calculatePrice();
addTax();
}
private void addTax() {
Decimal price = decoratedItem.getPrice();
decoratedItem.getPrice() * (1 + taxRate);
}
}
Finally, we can use these decorators to dynamically add behavior to our QuoteLineItem
objects:
List<IQuoteLineItem> quoteLineItems = new List<IQuoteLineItem>();
for (QuoteLineItem qli : [SELECT Id, UnitPrice FROM QuoteLineItem WHERE QuoteId = :someQuoteId]) {
IQuoteLineItem item = new BasicQuoteLineItem(qli.UnitPrice);
// Apply discounts only if the item qualifies
if (meetsDiscountCriteria(qli)) {
item = new DiscountDecorator(item, 0.10); // 10% discount
}
// Apply tax to all items
item = new TaxDecorator(item, 0.08); // 8% tax
item.calculatePrice(); // Calculate final price
quoteLineItems.add(item);
}
In the example above, we query all the QuoteLineItem
records outside of the loop and then process them in bulk. However, you would need to add additional logic to handle updates to these records back to the database, ensuring that you collect all the updated items and perform a single DML update outside of the loop.
2nd Example
One common requirement might be to apply additional discounts or fees based on complex business rules that are not covered by standard CPQ functionality. For instance, you might need to apply volume discounts, promotional discounts, or special fees that depend on the combination of products and customer attributes.
Here's an example of how you could use the Decorator pattern to add custom discounting logic to CPQ quote lines in a bulkified way:
Step 1: Define the interface
public interface IQuoteLineCalculator {
void calculate(List<SBQQ__QuoteLine__c> quoteLines);
}
Step 2: Implement the base calculator
public class BaseQuoteLineCalculator implements IQuoteLineCalculator {
public void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
// Here you would apply the base CPQ pricing logic
// For simplicity, let's assume this is a no-op, since CPQ calculates base prices
}
}
Step 3: Implement the decorators
public abstract class QuoteLineCalculatorDecorator implements IQuoteLineCalculator {
protected IQuoteLineCalculator innerCalculator;
public QuoteLineCalculatorDecorator(IQuoteLineCalculator inner) {
this.innerCalculator = inner;
}
public void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
// Delegate to the wrapped calculator
innerCalculator.calculate(quoteLines);
}
}
public class VolumeDiscountDecorator extends QuoteLineCalculatorDecorator {
public VolumeDiscountDecorator(IQuoteLineCalculator inner) {
super(inner);
}
public override void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
// First, let the inner calculator do its work
super.calculate(quoteLines);
// Then apply volume discounts
for (SBQQ__QuoteLine__c line : quoteLines) {
Integer quantity = line.SBQQ__Quantity__c;
// Example volume discount logic
if (quantity > 100) {
line.SBQQ__AdditionalDiscount__c = 0.1; // 10% discount
}
}
}
}
public class PromotionalDiscountDecorator extends QuoteLineCalculatorDecorator {
public PromotionalDiscountDecorator(IQuoteLineCalculator inner) {
super(inner);
}
public override void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
// Apply the inner calculator's logic
super.calculate(quoteLines);
// Then apply promotional discounts
for (SBQQ__QuoteLine__c line : quoteLines) {
// Example promotional discount logic
if (isPromotionalProduct(line.SBQQ__Product__c)) {
line.SBQQ__AdditionalDiscount__c += 0.05; // Additional 5% discount
}
}
}
private Boolean isPromotionalProduct(Id productId) {
// Implement logic to determine if the product is on promotion
return true; // Simplified for example purposes
}
}
Step 4: Use the decorators in your CPQ plugin or custom logic
public with sharing class CustomQuoteCalculator {
public void calculateQuoteLineAdjustments(List<SBQQ__QuoteLine__c> quoteLines) {
IQuoteLineCalculator calculator = new BaseQuoteLineCalculator();
// Wrap the calculator with decorators as needed
calculator = new VolumeDiscountDecorator(calculator);
calculator = new PromotionalDiscountDecorator(calculator);
// Perform calculations
calculator.calculate(quoteLines);
// Update the quote lines in bulk
update quoteLines;
}
}
This example demonstrates how the Decorator pattern can be applied in Salesforce CPQ to compose different pricing behaviors dynamically. Each decorator adds its layer of logic on top of the existing calculations. The BaseQuoteLineCalculator
represents the standard CPQ pricing logic, and each decorator adds additional adjustments like volume discounts or promotional discounts.
The CustomQuoteCalculator
class demonstrates how these decorators can be used in a CPQ plugin or custom logic, ensuring that all calculations are done in bulk to avoid governor limits. The list of SBQQ__QuoteLine__c
records is passed through each decorator, with each decorator applying its specific adjustments.
This pattern offers a clean and maintainable structure for extending CPQ logic, enabling you to add or remove decorators as business requirements evolve without modifying the underlying classes.
0 comments:
Post a Comment