SOLID Principles in Salesforce Apex – The Smart Way to Write Maintainable Code
Writing clean, maintainable, and scalable Apex code becomes much easier when you apply the SOLID principles. These principles form the foundation of good object-oriented programming and help Salesforce developers avoid rigid, tightly coupled code.
Here’s a simple walkthrough of each principle with clear, animal-themed examples that mirror real-world scenarios.
1. Single Responsibility Principle (SRP)
A class should have only one reason to change. It should handle one job only.
Bad Example:
public class LionHandler {
public Boolean validateLion(Lion lion) {
// validation logic
}
public void saveLion(Lion lion) {
// DML logic
}
}
This class performs both validation and database operations. If validation rules or database logic change, the same class must be edited, increasing risk.
Good Example:
public class LionValidator {
public Boolean validateLion(Lion lion) {
// validation logic
}
}
public class LionDatabaseHandler {
public void saveLion(Lion lion) {
// DML logic
}
}
Each class now has a single purpose. Code becomes easier to maintain and modify safely.
2. Open/Closed Principle (OCP)
Classes should be open for extension but closed for modification.
Bad Example:
public class ElephantSound {
public String makeSound(String type) {
if (type == 'African') {
return 'Trumpet loudly';
} else if (type == 'Asian') {
return 'Soft trumpet';
}
return null;
}
}
Every time a new elephant type is added, this class must be modified. That makes it error-prone.
Good Example:
public abstract class Elephant {
public abstract String makeSound();
}
public class AfricanElephant extends Elephant {
public override String makeSound() {
return 'Trumpet loudly';
}
}
public class AsianElephant extends Elephant {
public override String makeSound() {
return 'Soft trumpet';
}
}
Now, adding a new elephant type means simply creating a new subclass. The existing code remains untouched.
3. Liskov Substitution Principle (LSP)
Subclasses should be substitutable for their parent class without changing behavior.
Bad Example:
public class Bird {
public void fly() {
System.debug('Flying');
}
}
public class Penguin extends Bird {
public override void fly() {
throw new IllegalArgumentException('Penguins cannot fly');
}
}
If code expects a Bird to fly, replacing it with a Penguin breaks functionality.
Recommended by LinkedIn
Good Example:
public abstract class Bird {
public abstract void move();
}
public class Sparrow extends Bird {
public override void move() {
System.debug('Flying');
}
}
public class Penguin extends Bird {
public override void move() {
System.debug('Swimming');
}
}
Now both subclasses behave correctly according to their nature. Substitution works safely.
4. Interface Segregation Principle (ISP)
A class should not be forced to implement methods it does not use.
Bad Example:
public interface Animal {
void eat();
void fly();
}
public class Elephant implements Animal {
public void eat() {
System.debug('Eating');
}
public void fly() {
// Not applicable
}
}
The Elephant class is forced to implement a method that makes no sense for it.
Good Example:
public interface Eater {
void eat();
}
public interface Flyer {
void fly();
}
public class Elephant implements Eater {
public void eat() {
System.debug('Eating');
}
}
public class Sparrow implements Eater, Flyer {
public void eat() {
System.debug('Eating');
}
public void fly() {
System.debug('Flying');
}
}
Each class now implements only the interfaces relevant to it. The design becomes clear and focused.
5. Dependency Inversion Principle (DIP)
High-level modules should depend on abstractions, not concrete implementations.
Bad Example:
public class ZooDatabase {
public void save(Lion lion) {
System.debug('Saving to database');
}
}
public class ZooService {
private ZooDatabase db = new ZooDatabase();
public void addLion(Lion lion) {
db.save(lion);
}
}
The service is tightly coupled with a specific database class. Replacing or testing it becomes difficult.
Good Example:
public interface AnimalStorage {
void save(Lion lion);
}
public class ZooDatabase implements AnimalStorage {
public void save(Lion lion) {
System.debug('Saving to database');
}
}
public class MockStorage implements AnimalStorage {
public void save(Lion lion) {
System.debug('Mock save for testing');
}
}
public class ZooService {
private AnimalStorage storage;
public ZooService(AnimalStorage storage) {
this.storage = storage;
}
public void addLion(Lion lion) {
storage.save(lion);
}
}
Now the service depends only on an abstraction, allowing easy replacement or testing without changing the core logic.
Final Thoughts
The SOLID principles are not abstract theory; they are practical habits that make your Apex code cleaner and more reliable.
They help you:
By consistently applying these principles, your Salesforce projects become more professional, scalable, and easier to manage as they grow.
Try to provide the examples using Salesforce context.