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!
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 {