Open In App

Embracing SOLID Principles in Apex: A Guide for Salesforce

Last Updated : 30 Dec, 2024
Summarize
Comments
Improve
Suggest changes
Share
Like Article
Like
Report

Salesforce development often demands robust, scalable, and maintainable solutions to meet the evolving needs of businesses. Applying SOLID principles in Apex can help developers design code that is easier to manage, extend, and adapt over time. These principles are a set of design guidelines aimed at improving object-oriented programming practices.
This article explores how to apply the SOLID principles in Apex, providing practical examples tailored for intermediate to advanced developers..

What Are SOLID Principles?

SOLID is an acronym for five design principles:

Solid-Principles-in-APEX
Solid Principles in APEX

These principles, when followed, make the codebase more modular, testable, and easier to maintain.

1. Single Responsibility Principle (SRP)

A class should have only one reason to change. In Apex, this means ensuring each class performs a single, well-defined task.

Example: Before Applying SRP:

apex
public class CustomerHandler {
    public Boolean validateCustomerData(Account customer) {
        // validation logic
    }

    public void saveCustomerToDatabase(Account customer) {
        // database interaction logic
    }
}

Here, the CustomerHandler class is handling two responsibilities: validation and database interaction.

After Applying SRP:

apex
public class CustomerValidator {
    public Boolean validateCustomerData(Account customer) {
        // validation logic
    }
}

public class CustomerDatabaseHandler {
    public void saveCustomerToDatabase(Account customer) {
        // database interaction logic
    }
}

Benefit: Each class has a single responsibility, making it easier to test and modify independently.

2. Open/Closed Principle (OCP)

A class should be open for extension but closed for modification. This ensures existing code isn’t altered when adding new functionality. In Apex, we achieve this using interfaces or abstract classes.

Example: Before Applying OCP:

public class ProductPricer {
public Decimal calculatePrice(Product product) {
// pricing logic
}
}

Adding new pricing strategies would require modifying this class.

After Applying OCP:

apex
public interface PriceCalculator {
    Decimal calculatePrice(Product product);
}

public class StandardPriceCalculator implements PriceCalculator {
    public Decimal calculatePrice(Product product) {
        // standard pricing logic
    }
}

public class DiscountedPriceCalculator implements PriceCalculator {
    public Decimal calculatePrice(Product product) {
        // discounted pricing logic
    }
}

Benefit: New pricing strategies can be added by creating new implementations of PriceCalculator without altering existing code.

3. Liskov Substitution Principle (LSP)

Subtypes must be replaceable for their parent types without altering the correctness of the program. This ensures that subclasses correctly extend and behave like their parent classes.

Example: Before Applying LSP:

apex
public class PatientRecord {
    public String generateReport() {
        // common logic for generating a patient report
    }
}

public class PediatricPatientRecord extends PatientRecord {
    public String generateReport() {
        // pediatric-specific logic
    }
}

This violates LSP because PediatricPatientRecord could unintentionally alter the behavior expected from PatientRecord.

After Applying LSP:

apex
public abstract class PatientRecord {
    public abstract String generateReport();
}

public class PediatricPatientRecord extends PatientRecord {
    public String generateReport() {
        // pediatric-specific logic
    }
}

Benefit: Subclasses can now extend the parent class behavior without altering it, ensuring consistency.

4. Interface Segregation Principle (ISP)

A class should not be forced to implement methods it does not use. In Apex, breaking large interfaces into smaller, more specific ones achieves this.

Example: Before Applying ISP:

public interface CustomerInteractionLogger {
void logInfo(String message);
void logError(String message);
void logDebug(String message);
}

Classes implementing this interface may need to define methods they don’t use.

After Applying ISP:

XML
public interface InfoLogger {
    void logInfo(String message);
}

public interface ErrorLogger {
    void logError(String message);
}

public interface DebugLogger {
    void logDebug(String message);
}

Benefit: Classes now implement only the interfaces they need, reducing unnecessary dependencies.

5. Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules; both should depend on abstractions. In Apex, we achieve this by using interfaces and dependency injection.

Example: Before Applying DIP:

apex
public class DataService {
    private ExternalApiService apiService;

    public DataService() {
        this.apiService = new ExternalApiService();
    }

    public List<Account> getAccounts() {
        return apiService.retrieveAccounts();
    }
}

Here, DataService directly depends on ExternalApiService.

After Applying DIP:

apex
public interface DataRetrievalService {
    List<Account> retrieveAccounts();
}

public class ExternalApiService implements DataRetrievalService {
    public List<Account> retrieveAccounts() {
        // implementation for retrieving accounts
    }
}

public class DataService {
    private DataRetrievalService dataRetrievalService;

    public DataService(DataRetrievalService dataRetrievalService) {
        this.dataRetrievalService = dataRetrievalService;
    }

    public List<Account> getAccounts() {
        return dataRetrievalService.retrieveAccounts();
    }
}

Benefit: The DataService class depends on the abstraction DataRetrievalService, making it flexible and testable.

Advantages of Applying SOLID Principles in Apex

  • Improved Maintainability: Classes with focused responsibilities are easier to understand and maintain.
  • Enhanced Scalability: New features can be added without altering existing code.
  • Better Testability: Modular design enables easier unit testing.
  • Reduced Coupling: Dependency management ensures that changes in one part of the system don’t break others.

Conclusion

The SOLID principles are invaluable for Salesforce developers looking to design maintainable, scalable, and flexible applications in Apex. By adhering to these principles, you can create a codebase that is easier to manage, test, and extend, ensuring long-term success for your projects. Start by applying these principles incrementally, refactoring existing code where necessary, and enjoy the benefits of clean and robust software design.


Similar Reads