Skip to main content

Another desired generics example

Domain framework to support/promote bulkified code -- SObjects parent class contains logic reusable across multiple SObject types, while a domain subclass would be define for each SObject type where SObject-specific logic is needed.  

 

Today:

public virtual inherited sharing class SObjects {

private final List<SObject> records;

private SObjects() {

this.records = new List<SObject>();

}

public SObjects(List<SObject> records) {

this.records = records;

}

// Creates a new SObjects instance.

// Subclasses should override this method and create an appropriate subtype.

protected virtual SObjects newInstance(List<SObject> records) {

return new SObjects(records);

}

public List<SObject> getRecords() {

return records;

}

public SObjects filterFieldChanged(Map<Id,SObject> oldMap, Set<SObjectField> fields) {

List<SObject> filtered = new List<SObject>();

// ... filter logic ...

return newInstance(filtered);

}

public Map<Id,SObject> mapByIdToSObject() {

return new Map<Id,SObject>(records);

}

protected Map<Id,SObjects> mapByRecordTypeIdToSObjects() {

Map<Id,SObjects> result = new Map<Id,SObjects>();

for (SObject rec : records) {

Id key = (Id) rec.get('RecordTypeId');

if (key != null) {

SObjects value = result.get(key);

if (value == null) {

value = newInstance(new List<SObject>());

result.put(key, value);

}

value.records.add(rec);

}

}

return result;

}

}

public inherited sharing class Accounts extends SObjects {

public Accounts(List<Account> records) {

super(records);

}

// must remember to override factory method for use by parent

protected override SObjects newInstance(List<SObject> records) {

return new Accounts(records);

}

// must redefine with new name to cast

public List<Account> asList() {

return getRecords();

}

// must redefine with new name to cast

public Map<Id,Account> mapById() {

return (Map<Id,Account>) mapByIdToSObject();

}

// must redefine with new name to cast

public Map<Id,Accounts> mapByRecordTypeId() {

return (Map<Id,Accounts>) mapByRecordTypeIdToSObjects();

}

}

With generics:

public virtual inherited sharing class SObjects<T,U> {

private final List<T> records;

public SObjects() {

this.records = new List<T>();

}

public SObjects(List<T> records) {

this.records = records;

}

protected virtual U newInstance(List<T> records) {

// can something like this be made possible / more elegant?

U result = U.newInstance();

result.setRecords(records); // TODO add method and make class variable non-final

return result;

}

public List<T> getRecords() {

return records;

}

public Map<Id,T> mapByIdToSObject() {

return new Map<Id,T>(records);

}

// use domain class as map value to encourage continued bulk operations

protected Map<Id,U> mapByRecordTypeIdToSObjects() {

Map<Id,U> result = new Map<Id,U>();

for (T rec : records) {

Id key = (Id) rec.get('RecordTypeId');

if (key != null) {

U value = result.get(key);

if (value == null) {

value = newInstance(new List<T>());

result.put(key, value);

}

value.records.add(rec);

}

}

return result;

}

// return same type to encourage continued bulkified operations

public U filterFieldChanged(Map<Id,T> oldMap, Set<SObjectField> fields) {

List<T> filtered = new List<T>();

// ... filter logic ...

return newInstance(filtered);

}

}

public inherited sharing class Accounts extends SObjects<Account, Accounts> {

public Accounts(List<Account> records) {

super(records);

}

// no need to override newInstance() in each subclass if parent can construct correct type

// getRecords() can be used from parent directly and returns type List<Account>

// mapByIdToSObject() can be used from parent directly and returns type Map<Id,Account>

// mapByRecordTypeIdToSObjects() can be used from parent directly and returns type Map<Id,Accounts>

// filterFieldChanged() can be used from parent directly and returns type Accounts

// ... other bulk methods specific to Account continue on ...

}

Love that Salesforce is tackling this and gathering real-world use cases!

1 comment
  1. Mar 14, 2023, 8:37 PM

    I'd like to think that

    U result = U.newInstance();

    could be as simple as

    U result = new U();

    But perhaps it can't.

     

    In general I think your example needs type constraints that I outlined in another comment. Otherwise how does the system know that this is legal:

    result.setRecords(records);

    With type constraints you'd declare an interface that defined the methods the various U's would implement and the have

    public virtual inherited sharing class SObjects<T,U : IMyInterface> {

    or maybe

    public virtual inherited sharing class SObjects<T,U> where U: IMyInterface {
0/9000