3

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.

Jeff
  • 12,085
  • 12
  • 82
  • 152

2 Answers2

6

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/' }
Community
  • 1
  • 1
Eric Elliott
  • 4,711
  • 1
  • 25
  • 33
  • I'm basically trying to mimic inheritance from C#, so knowing about the super class is usually desireable for me. I like this approach though, but I don't like the fact that you'd pretty much need a library to work with inheritance in JS. Thanks for your answer though. :) – Jeff Sep 10 '13 at 08:56
  • 2
    If you want to mimic C# inheritance, you're on the right track, though it might not behave exactly as you expect (it's still prototypes, after all). Just be aware that you're missing out on the extra flexibility, expressiveness, and power that you get for free when you favor composition and delegation over class inheritance. BTW, super is a code smell: http://en.wikipedia.org/wiki/Call_super – Eric Elliott Sep 10 '13 at 09:31
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);
    …
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
OneOfOne
  • 95,033
  • 20
  • 184
  • 185
  • So I can't use anything like `this.prototype.get` to call the base implementation? – Jeff Sep 05 '13 at 18:08
  • short answer no, long answer you can go `AccountService.prototype.$super = BaseService.prototype;` and use `this.$super.get.call(this)`, notice that you will still have to use `call` or `apply` to call the base function. – OneOfOne Sep 05 '13 at 18:11
  • Why would I still have to use `call` though? Doesen't the account service instance get passed in as `this` with standard invocation? – Jeff Sep 05 '13 at 18:16
  • prototype is "special", you're can't use it like that inside an instance. – OneOfOne Sep 05 '13 at 18:18