This is a very common design pattern and can be seen being implemented in many areas of software applications, such as (Model View Controller (MVC), event handling, and almost all GUI toolkits. If we take an example of MVC, then "View" informs all its dependents such as the controller whenever there is any change in the UI.
This design pattern is also referred to as the publisher-subscriber pattern frequently. It works on the principle of don't call us - we'll call you. The publisher, in this case, notifies all its subscribers whenever required.
Following are the thumb rules to implement the observer pattern:
Subject
class maintains a list of all its dependent observers with additional methods, such as subscribe
, unsubscribe
, notify
, and so onObserver
class mostly extends an observable
class or interface containing the update
or notify
method called by the Subject
classLet's see this simple design pattern in action. We will consider the same example of the music library portal, as we discussed earlier in the visitor pattern. Customers search for songs that are not yet released to the public and are in the promo phase. So, developers want to allow customers to subscribe for e-mail notifications regarding a given song and to receive an e-mail when that song is released.
The following image shows the high-level object structure needed to maintain a list of all customers who want to receive e-mail notifications:
The Music Library
object contains information about all the songs added on the portal that are available for purchase. The is Public
checkbox indicates whether the music is released or not on the portal. In our example, we want to send e-mail notifications to all the customers maintained in the Music Notification
object whenever songs are made public. The Music Notification
object contains the e-mail address of a customer and the Subscribe
checkbox indicates whether the end user wants to receive a notification.
We have to assume that the workflow rule is written on the Music Notification
object. It says that whenever the Send Email
field is changed and the value is true
, then an e-mail notification is sent to all the subscribed customers.
Let's start with the Subject
class, which will have the necessary methods to subscribe
, unsubscribe
, and notify
:
public interface ISubject {
void subscribe(Music_Notification__c observer);
void unSubscribe(Music_Notification__c observer);
void notifyObservers();
}
The following interface needs to be implemented by all the observers, which should be notified by a subject:
public interface IObserver { void notify(); }
The following class is a concrete implementation of observer
, which gets a list of newly released songs and notifies all the customers who have subscribed to receive song notifications. In order to send an e-mail notification from the workflow rule, the Send_Email__c
field is set to true
:
public class MusicObserver implements IObserver{ LIst<String> musicId ; // notify for passed music public MusicObserver(LIst<String> mId){ musicId = mId; } public void notify(){ List<Music_Notification__c> lstMusicNotify = [SELECT ID FROM Music_Notification__c Where Music_Library__c IN: musicId AND Subscribe__c = true] ; if(!lstMusicNotify.isEmpty()) { for(Music_Notification__c mn : lstMusicNotify) { mn.Send_Email__c = true ; } //Workflow rule is written to send email if "Send_Email__c" is true update lstMusicNotify ; } } }
The following class provides the subscribe
and unsubscribe
methods to allow observer
instances to subscribe or unsubscribe from notifications. It also provides the notifyObservers
method, which invokes the notify
method of all the observers:
public class MusicSubject implements ISubject{ LIst<String> musicId ; public MusicSubject(LIst<String> mId){ musicId = mId ; } public void subscribe(Music_Notification__c observer) { observer.Subscribe__c = true; insert observer; } /** * Assuming parameter observer will only have email addres and music Id * */ public void unSubscribe(Music_Notification__c observer) { List<Music_Notification__c> lstExistingSubsriber = [SELECT ID FROM Music_Notification__c WHERE Email_Address__c = :observer.Email_Address__c AND Music_Library__c =: observer.Music_Library__c] ; if(!lstExistingSubsriber.isEmpty()) { for(Music_Notification__c m : lstExistingSubsriber){ m.Subscribe__c = false; } update lstExistingSubsriber ; } } public void notifyObservers() { IObserver obs = new MusicObserver(musicId); obs.notify(); } }
Whenever any song is made public
, there will be a change in the database. Therefore, we can use triggers on the music record to inform all the subscribers:
trigger ObserverPatternStart on Music_Library__c (after update) { List<String> lstQualifiedSubjectIds = new List<String>(); for(Integer i =0 ; i<Trigger.New.size() ; i++) { //If existing music is made public if(Trigger.new[i].is_Public__c && Trigger.new[i].is_Public__c != Trigger.old[i].is_Public__c){ lstQualifiedSubjectIds.add(Trigger.new[i].id) ; } } if(!lstQualifiedSubjectIds.isEmpty()) { MusicSubject sub = new MusicSubject(lstQualifiedSubjectIds); sub.notifyObservers(); } }
Let's summarize the preceding example:
public
, then they will click on the Subscribe button, which internally calls the subscribe()
method from MusicSubject
.is Public
flag to true
on a record of Music Library
, then the trigger will execute to call the notifyObservers()
method from the MusicSubject
class.notifyObservers()
method will call the notify()
method of the MusicObserver
observer class. The notify()
method will get all the related records from the database and update the field to trigger the workflow rule for an e-mail notification.The following anonymous code shows the observer pattern in action:
//user navigates to album which is not yet public List<MUSIC_LIBRARY__c> lstSongs = [SELECT ID FROM MUSIC_LIBRARY__c WHERE Album__c = 'Chuck Berry Is on Top']; List<Music_Notification__c> lstSubscriberlist = new List<Music_Notification__c>(); for(MUSIC_LIBRARY__c song : lstSongs) { Music_Notification__c notify = new Music_Notification__c(Music_Library__c = song.Id, Email_Address__c = '[email protected]', Subscribe__c = true); } //user subscribes to album insert lstSubscriberlist; for(MUSIC_LIBRARY__c song : lstSongs) { //make album public song.is_Public__c = true; } //changes made to database update lstSongs;
The output is as follows:
trigger will execute notifyObservers() of MusicSubject notify() of MusicObserver will be invoked to mark sendEmail field in notification record changing field "Send Email" will invoke Workflow rule to send email
The following class diagram shows the overall structure of a solution using the observer pattern: