Question

I've got a BaseService for easy interaction with my server-side API, and I'd like to "subclass" it for specific API resources. What better way than to directly inherit from the BaseService which does all the hard work?

So, I've got it set up, and it works, however I'm not entirely sure I am doing it the right way, as I've read that there are multiple types of inheritance in JS. Here's a link to some code I found that does it different ways: https://github.com/roblevintennis/Testing-and-Debugging-JavaScript/blob/master/code/objects/lib/js_inheritance.js

This is the way I am doing it. I've left function bodies out for brevity.

The BaseService, that my implementations will inherit from.

BaseService.prototype.get = function (resource, data, parameterOverrides) {
    return aPromisedLandOfDragons();
};

BaseService.prototype.post = function (resource, data, parameterOverrides) {
    return aPromisedLandOfDragons();
};

BaseService.prototype.put = function (resource, data, parameterOverrides) {
    return aPromisedLandOfDragons();
};

BaseService.prototype.destroy = function (resource, data, parameterOverrides) {
    return aPromisedLandOfDragons();
};

function BaseService(resource) {
    this.apiEndpoint = "/api/" + resource + "/";
}

Here is an implementation that can directly use the BaseService, passing in the API resource to the constructor.

AccountService.prototype = new BaseService("Accounts");
AccountService.prototype.getAll = function(searchString) {
    // Uses the BaseService method. Lovely!
    return this.get(null, {
        searchString: searchString || ""
    });
};

function AccountService() {

}

So, my question is (and hoping it won't be closed due to too localized): Is this a good way of doing inheritance in JavaScript? Does it have any performance impacts that can be drastically improved?

As an aside: If I wanted to directly override a method from the base class, e.g .get, is it possible to do that, while still being able to call the base class' implementation?

P.S: I know that technically there is no such thing as classes in JavaScript, but bear with me.

Was it helpful?

Solution 2

It seems fine, and to call the overwritten base method from another (e.g. the new) method you can go

AccountService.prototype.get = function(resource, data, parameterOverrides) {
    …
    // this passes all arguments just as received:
    BaseService.prototype.get.apply(this, arguments);
    // or pass some parameters explicitly:
    BaseService.prototype.get.call(this, resource, data);
    …
}

OTHER TIPS

While it's quite possible to do it the way you have written and intend, there are lots of reasons to avoid classical inheritance (see Classical Vs prototypal inheritance), and lots of reasons to favor composition, instead. Let's take a look at what this might be with prototypal inheritance:

var base = {
  get: function (resource, data, parameterOverrides) {
    return aPromisedLandOfDragons();
  },

  post: function (resource, data, parameterOverrides) {
    return aPromisedLandOfDragons();
  },

  put: function (resource, data, parameterOverrides) {
    return aPromisedLandOfDragons();
  },

  destroy: function (resource, data, parameterOverrides) {
    return aPromisedLandOfDragons();
  }
};

// Use a factory to instantiate instances:
function baseService(resource) {
  var instance = Object.create(base);
  instance.apiEndpoint = "/api/" + resource + "/";
  return instance;
}

var withAccount = {
  getAll: function(searchString) {
    // Uses the BaseService method. Lovely!
    return this.get(null, {
        searchString: searchString || ""
    });
  }
};

function accountService(resource) {
  var instance = mixIn({}, base, withAccount);
  instance.apiEndpoint = "/api/" + resource + "/";
  return instance;
}

Doing it this way allows for considerably more flexibility down the road. If you use the Stampit library, it becomes even easier:

var base = stampit().methods({
  get: function (resource, data, parameterOverrides) {
    return aPromisedLandOfDragons();
  },

  post: function (resource, data, parameterOverrides) {
    return aPromisedLandOfDragons();
  },

  put: function (resource, data, parameterOverrides) {
    return aPromisedLandOfDragons();
  },

  destroy: function (resource, data, parameterOverrides) {
    return aPromisedLandOfDragons();
  }
}).enclose(function () {
  this.apiEndpoint = '/api/' + this.resource + '/';
  delete this.resource;
});

var withAccount = stampit().methods({
  getAll: function(searchString) {
    // Uses the BaseService method. Lovely!
    return this.get(null, {
        searchString: searchString || ""
    });
  }
});

var accountService = stampit.compose(base, withAccount);

// Watch it work:

var myAccountService = accountService({resource: 'foo'});
// { apiEndpoint: '/api/foo/' }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top