JavaScript: Change The Behavior of A Class Method At Runtime

Useful Strategy To Perform Different Operations Without Repeated If Else Or Switch Statements

Recently I came across a problem where I needed to perform two different sets of operations throughout the application for two types of users. The trouble was:

  1. I needed to turn on or off an operation based on certain condition, which would only be known once the user was logged in, not at the beginning.
  1. I did not want to use if else repeatedly to check if the user was the right type for the operation.

The application was React, but the problem is equally valid for Vue, Angular or any other JavaScript application including Node.

Let’s discuss the solution that worked for me.

A Singleton Class Wrapper

As we need to maintain a state (although not persistently), we need a class. And since this should be common across the whole application with one instance, a singleton class is what we require.

Luckily in JavaScript, whatever you export or module.exports is shared across all files that import or require it. So if you export a new instance of a class, consider it the singleton instance.

// analytics.js
class Analytics {
  constructor(){
  }
} 

export default new Analytics();
// file a.js
import analytics from "./analytics.js";
// file b.js
import analytics from "./analytics.js";

A Method To Switch The Operation On And Off

In the singleton class add a method whose job is to decide which operation needs to be carried out. Say that we need to log an analytics event if a user is not from China. The method should decide that. We call it determineTrackingPermission. Also, let’s change our class to old-school way of class declaration with function, the reason of which you’ll see soon.

const Analytics = function() {
};

Analytics.prototype.determineTrackingPermission = function(user) {
  if (user.country !== "CN"){
    // keep tracking
  }
  else {
    // don't track
  }
}

export default new Analytics();

The Method And Its Logic

Add the main method of interest, which in our is track. Add two helper methods for our use case, call them trackUser, and doNotTrackUser. Assign one or another to track inside determineTrackingPermission, based on the condition. Assign it once initially for the default behavior.

The complete file will look like as below:

import someAnalyticsSdk from "some-analytics-sdk";

const Analytics = function() {
};

const trackUser = function(event, data){
  someAnalyticsSdk.track(event, data);
};

const doNotTrackUser = function(){
  // do nothing!
};

Analytics.prototype.track = doNotTrackUser;

Analytics.prototype.determineTrackingPermission = function(user) {
  if (user.country !== "CN"){
    Analytics.prototype.track = trackUser;
  }
  else {
    Analytics.prototype.track = doNotTrackUser;
  }
}

export default new Analytics();

How To Use It

1. Determine And Set The Permission At Run Time

As soon as you are in the position to determine if the user can be tracked, say on login success, call the determineTrackingPermission method with the user object.

import analytics from "./analytics.js"; // the singleton instance
// ...
// ...
// ...

// set immediately on login
analytics.determineTrackingPermission(user);

2. Call The Main Method

Across as many locations as you need, you simply import and call the the main track method without ever worrying about if the user is supposed to be tracked or not.

import analytics from "./analytics.js"; // the singleton instance
// ...
// ...
// ...

// This call will track the event only if the user doesn't belong to China
analytics.track("Page Visit", {
  url: window.location.href
});

And that’s it!


What Are The Benefits Of This Approach?

  1. The ability to switch strategies at run time.

  2. Not more than one set of if else, compared with always checking the permission in the method or (worse) at the point of the operation.

  3. Since it’s a wrapper, we just need to make changes in one file for any logic or service provider change in the future.

What Design Patterns Are Used Here?

  1. Singleton

  2. I believe the second one can be called Strategy Pattern because we’re changing the strategies at runtime.

Here are some more examples of design patterns in React JS.

See also