I have a similar approach to Adrian Larson's with a few minor differences.
There are also a few variations that I would like to try some time, but haven't had the reason or the motivation (I'm pretty lazy okay!) to do so.
A while back I answered a bounty question on a similar topic, basically the Qualify portion of this question. In doing so, I have used the following answer from it throughout many of my recent projects with great results.
So my code bases always have the following:
public with sharing class Filters
{
public interface ITriggerComparisonFilter
{
Set<sObject> FilterResults(List<sObject> newsObjectList, Map<Id, sObject> oldMapForsObjects);
}
public interface IFilterable { }
public with sharing class AllNewRecords implements Filters.IFilterable, Filters.ITriggerComparisonFilter
{
public Set<sObject> FilterResults(List<sObject> newsObjectList, Map<Id, sObject> oldMapForsObjects)
{
return new Set<sObject>(newsObjectList);
}
}
public with sharing class AllOldRecords implements Filters.IFilterable, Filters.ITriggerComparisonFilter
{
public Set<sObject> FilterResults(List<sObject> newsObjectList, Map<Id, sObject> oldMapForsObjects)
{
return new Set<sObject>(oldMapForsObjects.values());
}
}
}
As you can see in the class, I nested two interfaces. A marker interface called IFilterable and a filtering interface for Triggers called ITriggerComparisonFilter. I can have more for different variations of Filter interfaces if need be and place them in here as well.
Here I also implement the generic and simple implementations of the ITriggerComparisonFilter interface. These I use when I am not really interested in filtering, but I still want to remain consistent in my code.
From here, I start implementing a concrete implementation of my interfaces.
So, say I have an sObject called YourMama__c
and you want to implement a filter.
I'd do something like this:
public with sharing class YourMamaFilters
{
public with sharing class YourMamaSoFatJokeNeededOnChangeFilter implements Filters.IFilterable, Filters.ITriggerComparisonFilter
{
public Set<sObject> FilterResults(List<sObject> newsObjectList, Map<Id, sObject> oldMapForsObjects)
{
List<YourMama__c> updatedYourMamaRecords = (List<YourMama__c>)newsObjectList;
Map<Id, YourMama__c> oldYourMamaMap = (Map<Id, YourMama__c>)oldMapForsObjects;
Set<sObject> filteredYourMamaRecords = new Set<sObject>();
for(YourMama__c yourMamasRecord : updatedYourMamaRecords)
if(NeedsAYourMamaJoke(yourMamasRecord, (YourMama__c)oldYourMamaMap.get(newClient.Id)))
filteredYourMamaRecords.add(newYourMamaRecord);
return filteredYourMamaRecords;
}
private Boolean NeedsAYourMamaJoke(YourMama__c newYourMamasRecord, YourMama__c oldYourMamasRecord)
{
return oldYourMamasRecord.NeedsJoke == false && newYourMamasRecord.NeedsJoke == true;
}
}
public with sharing class YourMamaSoFatJokeNeededOnCreationFilter implements Filters.IFilterable, Filters.ITriggerComparisonFilter
{
public Set<sObject> FilterResults(List<sObject> newsObjectList, Map<Id, sObject> oldMapForsObjects)
{
Set<sObject> filteredYourMamaRecords = new Set<sObject>();
for(YourMama__c newYourMamasRecord : (List<YourMama__c>)newsObjectList)
if(NeedsAYourMamaJoke(newYourMamasRecord))
filteredYourMamaRecords.add(newYourMamasRecord);
return filteredYourMamaRecords;
}
private Boolean NeedsAYourMamaJoke(YourMama__c newYourMamasRecord)
{
return newYourMamasRecord.NeedsJoke == true;
}
}
}
Now, I have a more object oriented way of implementing my qualifying logic.
So in a trigger, you may see different ways you can use this.
if(Trigger.isBefore && Trigger.isInsert)
{
Set<sObject> yourMamaRecordsNeedingJokes =
new Set<sObject>((new YourMamaFilters.YourMamaSoFatJokeNeededOnCreationFilter().FilterResults(Trigger.New, Trigger.OldMap)));
YourMamaTriggerHandler.SendYourMamaJoke(yourMamaRecordsNeedingJokes);
}
Where your handler delegates to some service:
public with sharing class YourMamaTriggerHandler
{
public void SendYourMamaJoke(List<YourMama__c> yourMamaRecordsNeedingJokes)
{
(new YourMamaService()).SendYourMamaSoFatJokeRandom((List<YourMama__c>)new List<sObject>(yourMamaRecordsNeedingJokes));
}
}
public with sharing class YourMamaService
{
public void SendYourMamaSoFatJokeRandom(List<YourMama__c> yourMamaRecordsNeedingJokes)
{
List<Messaging.SingleEmailMessage> youMamaJokeEmails = new List<Messaging.SingleEmailMessage>();
Interger randomHelper = 0;
for(YourMama__c singleYourMamaRecord : yourMamaRecordsNeedingJokes)
{
Messaging.SingleEmailMessage newYourMamaEmail = new Messaging.SingleEmailMessage();
newYourMamaEmail.setSubject('Your mama\'s so fat...');
if(Math.Mod(randomHelper, 0) == 0)
newYourMamaEmail.setPlainTextBody('Your mama\'s so fat, her weight causes an arithmetic overflow on a Double value');
/*
... Other variations here.
Yes, this isn't scalable by the way.
*/
randomHelper++;
newYourMamaEmail.setToAddresses(new List<String> { singleYourMamaRecord.Email__c });
youMamaJokeEmails.add(newYourMamaEmail);
}
Messaging.sendEmail(youMamaJokeEmails);
}
}
Now this is just one way to do it. A very simple way. I have been doing this recently because the projects I have been working on are rather small scale.
There are other things I'd like to try this though:
- You could make a map that maps Filters to Service implementations and loop through the action as they occur
- You can create a utility so you can hold onto cached/static variable results
- You have the option of implementing static variable in the filter to stop recursion as opposed to handling it in the trigger handler (which I prefer)
- Embed filters within each other to further filter your results or make new combinations
Basically as soon as you decouple the filtering and execution logic, it makes implementing features so much easier. Explore what feels right to you and your team. Look at other frameworks like FinancialForce's or simple trigger frameworks like Trigger Pattern for Tidy, Streamlined, Bulkified Triggers. I can't forget the grandfather of all these, Dan Appleman's triiger design pattern in his Advanced Apex Programming book (Some may disagree, but it also helped with Adam Purkiss' version at Plursight which in my opinion is a little better).
That's the beauty of programming, you don't have stick with a single design, or any at all. Gain inspiration from other languages and software design patterns - experiment. That's what makes the job sort of fun, right ;) .