Piotr Gajek
written byPiotr Gajek
posted on August 14, 2022
Technical Architect and Full-stack Salesforce Developer. He started his adventure with Salesforce in 2017. Clean code lover and thoughtful solutions enthusiast.

Abstract, Virtual, Interface in Apex

Object-oriented programming

Object-oriented programming (OOP) - is a paradigm based on the concept that every piece of data and behavior related to that data can be wrapped into special bundles called objects. An object is a kind of abstraction defined by a programmer, which tries to mimic some real subject.

Object-oriented programming has four pillars:

Abstraction

In every class you are developing, try to replicate real-world objects and their behaviors. Class is only an abstract model with attributes and descriptions of behavior (methods).

Encapsulation

Encapsulation is the ability of an object to hide parts of its state and behaviors from other objects, exposing only a limited interface to the rest of the program.

Encapsulation is provided by Interfaces of most programming languages.

Polymorphism

Polymorphism is the ability of a program to detect the real class of an object and call its implementation even when its real type is unknown in the current context.

Your class can "pretend" that is something else. You can achieve it by implementing interface or extending the virtual/abstract class.

Inheritance

Inheritance is the ability to build new classes on top of existing ones.

You can reuse existing code by extending the existing class and adding extra functionality to your child's class. Use Abstract and Virtual classes.


Interface

  • Define: public interface MyInterface {}.
  • Use: public class MyChildClass implements MyInterface {}.
  • has only method's definitions.
  • a new layer of abstraction.
  • can be treated as a new data type.
  • class can implement many interfaces.

Abstract

  • Define: public abstract class MyAbstractClass {}.
  • Use: public class MyChildClass extends MyAbstractClass {}.
  • child classes inherit all the methods and properties of the extended class.
  • can contain virtual and abstract methods.
  • abstract class is child super-type.
  • cannot be initialized.
  • class can extend only one abstract class.

Virtual

  • Define: public virtual class MyVirtualClass {}.
  • Use: public class MyChildClass extends MyVirtualClass {}.
  • child classes inherit all the methods and properties of the extended class.
  • can contain only virtual methods.
  • virtual class is a child super-type.
  • can be initialized.
  • class can extend only one virtual class.

Virtual method vs Abstract method

  • virtual method has basic logic, that can be overridden by the child class.
  • abstract does not contain logic, and has to be overridden by the child class.

Code

Let's refactor simple code step by step to use interface, abstract and virtual.
I will use Factory Method and Abstract Factory patterns.

0 - Reference

// DataFactory.cls
public class DataFactory {

    public static Account createAccount() {
        Account account = new Account(
            Name = 'My Account',
            Email = 'myAccount@email.com'
        );
        insert account;
        return account;
    }

    public static Contact createContact() {
        Contact contact = new Contact(
            LastName = 'My Contact'
        );
        insert contact;
        return contact;
    }
}
// Usage
Account account = DataFactory.createAccount();
Contact contact = DataFactory.createContact();

1 - Interface

// DataFactory.cls
public interface DataFactory {
    sObject getRecord();
    sObject createRecord();
}
// AccountFactory.cls
public class AccountFactory implements DataFactory {
    public sObject getRecord() {
        return new Account(
            Name = 'My Account'
        );
    }

    public sObject createRecord() {
        Account account = getRecord();
        insert account;
        return acccount;
    }
}
// ContactFactory.cls
public class ContactFactory implements DataFactory {
    public sObject getRecord() {
        return new Contact(
            LastName = 'My Contact'
        );
    }

    public sObject createRecord() {
        Contact contact = getRecord();
        insert contact;
        return contact;
    }
}
// DataCreator.cls
public class DataCreator {
    private final Map<sObjectType, System.Type> OBJECT_TO_FACTORY = new Map<sObjectType, System.Type>{
        Account.sObjectType => AccountFactory.class,
        Contact.sObjectType => ContactFactory.class
    };

    public static Object createRecord(sObjectType objectTypeToCreate) {
        DataFactory factory = (DataFactory) OBJECT_TO_FACTORY.get(objectTypeToCreate).newInstance();
        return factory.createRecord();
    }
}
// Usage
Account account = (Account) DataCreator.createRecord(Account.sObjectType);
Contact contact = (Contact) DataCreator.createRecord(Contact.sObjectType);

2 - Abstract

// DataFactory.cls
public abstract class DataFactory {
    public abstract sObject getRecord();
    public sObject createRecord() {
        sObject record = this.getRecord();
        insert record;
        return record;
    };
}
// AccountFactory.cls
public class AccountFactory extends DataFactory {
    public sObject getRecord() {
        return new Account(
            Name = 'My Account'
        );
    }
}
// ContactFactory.cls
public class ContactFactory extends DataFactory {
    public override sObject getRecord() {
        return new Contact(
            LastName = 'My Contact'
        );
    }
}
// DataCreator.cls
public class DataCreator {
    private final Map<sObjectType, System.Type> OBJECT_TO_FACTORY = new Map<sObjectType, System.Type>{
        Account.sObjectType => AccountFactory.class,
        Contact.sObjectType => ContactFactory.class
    };

    public static Object createRecord(sObjectType objectTypeToCreate) {
        DataFactory factory = (DataFactory) OBJECT_TO_FACTORY.get(objectTypeToCreate).newInstance();
        return factory.createRecord();
    }
}
// Usage
Account account = (Account) DataCreator.createRecord(Account.sObjectType);
Contact contact = (Contact) DataCreator.createRecord(Contact.sObjectType);

3 - Virtual

// DataFactory.cls
public abstract class DataFactory {
    public abstract sObject getRecord();
    public sObject createRecord() {
        sObject record = this.getRecord();
        insert record;
        return record;
    };
}
// AccountFactory.cls
public class AccountFactory extends DataFactory {
    public sObject getRecord() {
        return new Account(
            Name = 'My Account'
        );
    }
}
// ContactFactory.cls
public class ContactFactory extends DataFactory {
    public override sObject getRecord() {
        return new Contact(
            LastName = 'My Contact'
        );
    }
}
// DataCreator.cls
public virtual class BasicDataCreator {
    public virtual Map<sObjectType, System.Type> getObjectToFactory() {
        return new Map<sObjectType, System.Type>{
            Account.sObjectType => AccountFactory.class,
            Contact.sObjectType => ContactFactory.class
        };
    }

    public static Object createRecord(sObjectType objectTypeToCreate) {
        DataFactory factory = (DataFactory) this.getObjectToFactory().get(objectTypeToCreate).newInstance();
        return factory.createRecord();
    }
}
// AccountFactory.cls
public class AccountVariationAFactory extends DataFactory {
    public sObject getRecord() {
        return new Account(
            Name = 'My Variation A Account'
        );
    }
}
// ContactFactory.cls
public class ContactVariationAFactory extends DataFactory {
    public override sObject getRecord() {
        return new Contact(
            LastName = 'My Variation AContact'
        );
    }
}
// DataCreator.cls
public class VariationADataCreator extends BasicDataCreator {
    public override Map<sObjectType, System.Type> getObjectToFactory() {
        return new Map<sObjectType, System.Type>{
            Account.sObjectType => AccountVariationAFactory.class,
            Contact.sObjectType => ContactVariationAFactory.class
        };
    }
}
// Usage
BasicDataCreator basicDataCreator = new BasicDataCreator();

Account account = (Account) basicDataCreator.createRecord(Account.sObjectType);
Contact contact = (Contact) basicDataCreator.createRecord(Contact.sObjectType);

VariationADataCreator variationADataCreator = new VariationADataCreator();

Account accountVariationA = (Account) variationADataCreator.createRecord(Account.sObjectType);
Contact contactVariationA = (Contact) variationADataCreator.createRecord(Contact.sObjectType);

Why to use?

  • single responsibility principle - many small classes that do only one thing.
  • open-close principle - classes should be open for extensions but closed for modification.
  • dependency inversion principle - depend on abstractions. Do not depend on concrete classes.
  • DRY - don't repeat yourself - common logic can be stored in the base class.
  • code is easier to read - there is no one big class.
  • most of the design patterns use an interface, abstract or virtual - to start using design patterns you need to understand object-oriented programming.
  • program to interface, not implementations.
  • encapsulate what varies - keep in child classes what varies.

Comparison

Below you can find a table, which contains a comparison between Interface, Abstract, and Virtual in Apex.

Feature Interface Abstract Virtual
Keyword interface
e.g public interface MyInterface {}
abstract
e.g public abstract class MyAbstractClass {}
virtual
e.g public virtual class MyVirtualClass {}
How to use? By implements
e.g public class MyChildClass implements MyInterface {}
By extends
e.g public class MyChildClass extends MyAbstractClass {}
By extends
e.g public class MyChildClass extends MyVirtualClass {}
Type of Methods Only method signature
e.g String getFullName(String firstName, String lastName);
Can contains abstract, virtual and implements own methods.
e.g abstract String getFullName(String firstName, String lastName);
public virtual String getFullName(String firstName, String lastName);
Can contains only virtual and implements own methods
e.g public virtual String getFullName(String firstName, String lastName);
Need to Implement Parent Methods ✅ All interface's methods need to be implemented ✅ Only methods signed as abstract ❌ Cannot force child class to implement parent methods
Override Parent Methods ❌ Interface doesn't contain logic to override ✅ Methods signed as abstract and virtual ✅ Methods signed as virtual
Has basic logic? ❌ Interface contains only the method's signature ✅ Methods signed as virtual and inner methods can contain basic logic ✅ Methods signed as virtual and inner methods can contain basic logic
Can be initialized directly? new MyInterface(); new MyAbstractClass(); new MyVirtualClass();
Pros ✅ New layer of abstraction.
✅ Interface can be treated as a new data type.
✅ Logic behind the interface can be changed without changes in related classes.
✅ We program to interfaces, not implementations. Code doesn't have reference to specific classes.
✅ Child class is forced to implement interface's method (Guarantee specific logic).
✅ Child class can implement many interfaces.
✅ Abstract class can store common logic (avoid redundancy)
✅ Child class can be forced to implement some (signed as abstract) methods.
✅ Child class can override some (signed as virtual) methods.
✅ Basic logic can be provided by abstract class inner methods.
✅ Easy to update common code for all child classes. Just update code in abstract class.
✅ Virtual class contains basic logic, accessible by child classes.
✅ Virtual class can be initialized direct.
✅ Child class can override some (signed as virtual) methods.
✅ Easy to update common code for all child classes. Just update code in a virtual class.
✅ Basic logic can be provided by virtual class inner methods.
Cons ❌ Hard to add or change the interface's methods, because all classes that implement the interface need to be changed as well.
❌ Apex class needs to implement all interface methods even if it's not relevant.
❌ Cannot be initialized directly.
❌ Apex class needs to implement all abstract methods even if it's not relevant.
❌ Child class can be only extended by one parent class.
❌ Cannot force child class to implement parent method (abstract cannot be used)
❌ Child class can be only extended by one parent class.
Goal To create a new layer of abstraction. To provide a kind of partial class with common logic. To provide a kind of full class with base logic.

If you have some questions feel free to ask in the comment section below. 🙂

Was it helpful? Check out our other great posts here.


Resources

Buy Me A Coffee