The observer pattern

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.

Note

The observer pattern is a behavioral design pattern in which an object (a subject or publisher) maintains a list of all its dependents (an observer or subscriber) and notifies them whenever there is any change in its state.

Following are the thumb rules to implement the observer pattern:

  • The Subject class maintains a list of all its dependent observers with additional methods, such as subscribe, unsubscribe, notify, and so on
  • The Observer class mostly extends an observable class or interface containing the update or notify method called by the Subject class

Let'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 observer pattern

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.

Tip

Apex has a limit of 1,000 e-mails in 24 hours. So, we will be using workflow e-mails to bypass this limit.

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:

  • Users will navigate to the Music Library, and if they see that music is not public, then they will click on the Subscribe button, which internally calls the subscribe() method from MusicSubject.
  • If the admin updates the 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.
  • The 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:

The observer pattern

Point to consider

The observer pattern addresses how you can decouple senders and receivers, but with different trade-offs. Observer defines a decoupled interface that allows multiple receivers to be configured at runtime.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset