Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I split a class definition across multiple files in node.js?

My class definition for Foo that has grown to the point that I'd like to split it across multiple files. For example, I'd like something like:

// file foo.js
'use strict';
function Foo() { };
Foo.prototype.constructor = Foo;
Foo.prototype.methodA = function() { console.log('methodA'); };
module.exports = Foo;

// file foo-aux.js
'use strict';
Foo.prototype.methodB = function() {
  console.log('methodB');
  this.methodA();
};

// file main.js
'use strict';
const Foo = require('./foo');
var foo = new Foo();
foo.methodB();

What's the right combination of module.export and require() to make the above code work?

like image 320
fearless_fool Avatar asked Jun 30 '16 13:06

fearless_fool


2 Answers

updated answer

(Full props to McMath's detailed answer -- it scales better than the original answer below and offers the opportunity for better code reuse. And it nudged me into updating this answer. But if you don't need the full-on mixin route espoused there, here's a clean and simple approach.)

Modifying a technique suggested by Shaun Xu, you can pass the primary class definition as an argument to require which will be received in the split out file(s).

It's good practice to create a subdirectory with index.js to hold the class definition and the sub-files -- this makes it clear that the sub-files are part of the Foo class:

main.js
foo/
  index.js
  foo-a.js
  foo-b.js

with the following:

// foo/index.js
'use strict';
function Foo() {};
Foo.prototype.constructor = Foo;
require('./foo-a')(Foo);
require('./foo-b')(Foo);
module.exports = Foo;

// foo/foo-a.js
'use strict';
module.exports = function(Foo) { 
  Foo.prototype.methodA = function() {
    console.log('methodA');
  };
  // more methods as desired...
};

// foo/foo-b.js
'use strict';
module.exports = function(Foo) { 
  Foo.prototype.methodB = function() {
    console.log('methodB');
    this.methodA();
  };
  // more methods as desired...
};

and to call it:

// main.js
'use strict';
const Foo = require('./foo/');
var foo = new Foo();
foo.methodB();

original answer

// file foo.js
'use strict';
function Foo() { };
Foo.prototype.constructor = Foo;
Foo.prototype.methodA = function() { console.log('methodA'); };
require('./foo-aux')(Foo);  // <== add this line
module.exports = Foo;

// file foo-aux.js
'use strict';
module.exports = function(Foo) {  // <== wrap function definitions 
  Foo.prototype.methodB = function() {
    console.log('methodB');
    this.methodA();
  };
};

// file main.js
'use strict';
const Foo = require('./foo');
var foo = new Foo();
foo.methodB();

// test
$ node foo.js
methodB
methodA
like image 165
fearless_fool Avatar answered Nov 09 '22 14:11

fearless_fool


There are two solutions that I would consider, depending on whether I wanted to define one method per file or to group multiple related methods in a single file.

One method per file

Start with a directory structure like this:

foo/
  foo.a.js
  foo.b.js
  index.js
main.js

One of the methods for Foo might look like this:

// foo/foo.a.js

module.exports = function() {
  console.log('Method A');
};

The other method can be defined in a similar way. Foo itself can be defined like this:

// foo/index.js

function Foo() { }

Foo.prototype.methodA = require('./foo.a');
Foo.prototype.methodB = require('./foo.b');

module.exports = Foo;

Now we can use Foo like this:

// main.js

var Foo = require('./foo');

var foo = new Foo();

foo.methodA(); // 'Method A'
foo.methodB(); // 'Method B'

One advantage of this solution over your own is that all the methods for Foo are declared in one place, i.e., in foo/index.js, but are defined elsewhere. It's immediately clear looking at one file what methods Foo has, without all the clutter of their implementations.

Multiple methods per file

In this case, I would be inclined to use the mixin pattern. Here is the directory structure:

/foo
  bar.js
  baz.js
  index.js
/utils
  extend.js
  mixin.js
main.js

Start with a function that extends one object with another, including getters/setters and maintaining the same property descriptor.

// utils/extend.js

module.exports = function extend(target, source) {
  var names = Object.getOwnPropertyNames(source);
  var len = names.length;

  for (var i = 0; i < len; i++) {
    var name = names[i];
    var descriptor = Object.getOwnPropertyDescriptor(source, name);

    Object.defineProperty(target, name, descriptor);
  }
};

A mixin just does this to the prototypes of two objects:

// utils/mixin.js

var extend = require('./extend');

module.exports = function mixin(target, source) {
  extend(target.prototype, source.prototype);
};

Now we can define the Bar base class like this:

// foo/bar.js

function Bar(a, b) {
  this.a = a;
  this.b = b;
}

Bar.prototype.methodA = function() {
  console.log(this.a);
};

Bar.prototype.methodB = function() {
  console.log(this.b);
};

module.exports = Bar;

Baz can be defined similarly. Then Foo, which extends both, can be defined like this:

// foo/index.js

var Bar = require('./bar');
var Baz = require('./baz');
var mixin = require('../utils/mixin');

function Foo(a, b, c, d) {
  Bar.call(this, a, b);
  Baz.call(this, c, d);
}

mixin(Foo, Bar);
mixin(Foo, Baz);

module.exports = Foo;

And we can use it like this:

// main.js

var Foo = require('./foo');

var foo = new Foo('one', 'two', 'three', 'four');

foo.methodA(); // 'one'
foo.methodB(); // 'two'
foo.methodC(); // 'three'
foo.methodD(); // 'four'

One advantage of this method is that we could potentially Bar or Baz by themselves or to extend other classes. Moreover, the fact that each has its own constructor lets us declare their dependencies in the files in which they are defined, rather than, say, having to remember to assign a this.a property in the Foo constructor.

like image 7
McMath Avatar answered Nov 09 '22 14:11

McMath