- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
Design patterns are a way of "templatizing" solutions to recurring problems. Before we talk about things in technical terms, I'd like to tell you a little story.
Once upon a time... Mom got Johnny up for school. She carefully looked over his things. Lunch? Check. Homework? Check. Cell phone? Check. As she put him on the bus, she called over his shoulder, "Don't forget to call me at lunch to tell me if you make it to the finals in the spelling bee. That way I can arrange to pick you up after your practice."
This little "parable" illustrates what happens in our code when we instantiate an object dependency. The items in Johnny's backpack represent constructor parameters, things our object will need to get its work done. But that cell phone represents a special tool. Just like Mom needed Johnny to tell her about a particular turn of events so that she could respond appropriately, our classes often have similar behavioral inter-dependencies. Specifically, it is common for one object to need to know if something changes inside another object. In fact, there is often a list of objects that need to know about such changes, so that they can perform some corresponding operation.
In design pattern lingo, we call this situation the "Observer" pattern. The ever-so-interesting-to-other-objects object is called the "subject". The dependent objects that react to signals from the subject are called observers. Since there can be many observers of a single subject, the relationship is one to many.
Take a look at these two example classes that implement a variant of the Observer pattern:
var u_Subject = Class.create();
u_Subject.prototype = {
initialize: function() {
},
observers: [],
register: function(observer){
this.observers.push(observer);
},
doWork: function(){
if(Math.floor(Math.random()*10) % 2 == 0){
this._notify('Even');
}else{
this._notify('Odd');
}
},
_notify: function(msg){
for (var i=0; i< this.observers.length; i++) {
this.observers[i].getMessage(msg);
}
},
type: 'u_Subject'
}
var u_Observer = Class.create();
u_Observer.prototype = {
initialize: function(subject, name) {
subject.register(this);
this.name = name;
},
getMessage: function(message){
gs.print('Observer ' + this.name + ' printing message from subject:' + message );
},
name:'',
type: 'u_Observer'
}
Now from a background script we could do this:
var sub = new u_Subject();
var obj1 = new u_Observer(sub, 'Observer1');
var obj2 = new u_Observer(sub, 'Observer2');
var obj3 = new u_Observer(sub, 'Observer3');
sub.doWork();
sub.doWork();
Output for a sample run:
Notice that something that is happening in a single subject object is sending the same message to (and triggering a print call in) multiple observers. That's pretty cool if you think about it.
In case you're thinking that there is no place for the Observer design pattern in practical day-to-day ServiceNow scripting, let me propose a scenario and see if you have ever bumped into anything similar:
You have a Workflow running on a catalog item. That Workflow executes a "Run Script" activity which creates a new Change Request. Then the Workflow moves to a "Wait for Condition" activity. The idea is for the catalog item to hang around and wait until the Change moves to a particular state, and then flow to the next activity. You implement the Workflow, but are sorely disappointed to find that the "Wait for Condition" activity never detects the state change on the Change, though you're sure that your code is written correctly. What gives? While updates to the RITM would trigger the "Wait for Condition" activity to be re-evaluated, updates on a completely different task (the Change) do not. The RITM Workflow is stranded!
So, what's the solution? We can implement a variation of the Observer pattern on the Task table with just a couple of quick modifications.
1. Add a GlideList field to the Task table.
2. Add an Advanced Before (Insert/Update) Business Rule called run flows on observers:
// It is possible that a WF on a different task is sitting at a wait for condition Activity.
// If an activity is registered as this task's observer we will 'nudge' that task's WF,
// causing it to recheck its Wait For activity's condition.
//Condition: !current.u_observers.nil()
(function(){
var wf = new Workflow();
var ri = new GlideRecord("task");
ri.addQuery('sys_id','IN',current.u_observers);
ri.query();
while(ri.next()){
wf.runFlows(ri, 'update');
}
})();
* Thanks to sabell2012 for the improvement on this code!
Now when the Change (the subject) is updated, all of observers will get a signal to run their flows, as if that Task had been updated.
This is really powerful! With one addition to the Task table, any Task can easily be informed (have its Workflow triggered) due to an update to any other Task. Though I created this for the use-case described above, I now feel that the applications are wide-ranging, and that it will be a default configuration for all of the instances I work on moving forward.
If you want to be a better developer, you owe it to yourself to spend some energy learning about design patterns. I really enjoy the Head First Design Patterns (subset) book, but there are tons of great resources available online as well. Please feel free to add links to your favorite DP resources below in the comments.
We'll find out who the real hard-core geeks are among us if they say that they found the GOF book pleasant bedside reading.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.