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
andabstract
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.