Cannot comprehend module-based inheritance and composition

96 views
Skip to first unread message

Aaron Martone

unread,
Mar 1, 2015, 11:15:09 PM3/1/15
to nod...@googlegroups.com
Sorry in advance, I've been at this problem for 6 days now and have failed at every single turn on what I need to do. I simply cannot afford to waste more time, so I'm asking for help.

My directory structure looks like this:

/root
____/controllers
________server.ctrl.js
____/libs
________/classes
____________Server.js
____________ServerController.js
server.app.js

I am trying to create a "Server" class based on /libs/classes/Server.js. When instantiated, it takes 1 param, the constant ENV (environment), which is stored in its THIS scope. 
One of the properties of the "Server" object is called 'ctrl' and that should be set to an instance of the 'ServerController' class at /libs/classes/ServerController.js

The 'ServerController' needs to inherit via the prototype chain, the properties on the 'Server' so that it can gain access to the 'THIS' scope's 'ENV' when it references 'this.ENV'.
I have tried everything I can think of, many times 20x over, and cannot figure out how this is done. Hopefully I have explained my situation well enough. I'm able to do inheritance without modules, but something about Node's module exporting is throwing me off my understanding.

FYI. The ServerController 'class' requires the /controllers/server.ctrl module which exports an object of functions that the ServerController class returns as direct functions off itself. Since ServerController gets stored in the Server object's 'ctrl' property, I was hoping I could call it via Server.ctrl.functionName();


==================================================================
FROM HERE BELOW ARE THINGS I'VE TRIED, NOT NECESSARY TO THE QUESTION
==================================================================



in server.app.js:
var Server = require('./libs/classes/Server'); // the 'Server' class.
var ServerController = require('./libs/classes/ServerController'); // the 'ServerController' class.

var ctrl = new ServerController();
var server = new Server(ENV, ctrl);

in Server.js:
function Server(ENV, ctrl) { this.ENV = ENV; this.ctrl = ctrl; }
Server.prototype.test = function() { console.log('Server-level test'); }
module.exports = Server;

in ServerController.js:
var Server = require('./Server');
function ServerController() { }
ServerController.prototype = Object.create(Server.prototype);
ServerController.prototype.test = function() { console.log('Controller-level test, shadows Server-level'); }
module.exports = ServerController;

Then I try to run things like:
server.test(); // Expecting 'Server-level test'
server.ctrl.test(); // Expecting 'Controller-level test, shadows Server-level');

But I'm running into tons of errors.

Peter Rust

unread,
Mar 3, 2015, 11:45:22 AM3/3/15
to nod...@googlegroups.com
Aaron,

When I dropped the above into 3 text files and ran them (with Node 0.10), I got an error about ENV being undefined, so I changed it to "process.ENV" and then got the expected output. I put the two calls to "test()" at the end of server.app.js:

var Server = require('./Server'); // the 'Server' class.
var ServerController = require('./ServerController'); // the 'ServerController' class.

var ctrl = new ServerController();
var server = new Server(process.ENV, ctrl);

server.test(); // Expecting 'Server-level test'
server.ctrl.test(); // Expecting 'Controller-level test, shadows Server-level');

Personally I would use "util.inherits(ServerController, Server);" instead of manually setting the prototype, but under the hood it does the same thing, the only difference is that it wires up a ".super_" property for you and configures the constructor (https://github.com/joyent/node/blob/master/lib/util.js#L632).

Another thing you might be running into (if your code is slightly different from what you posted) is cyclical module references (http://nodejs.org/api/modules.html#modules_cycles). The best way to deal with that is to do "module.exports = ..." immediately after declaring the constructor function and before doing anything else, including any require()s (https://github.com/AmpersandJS/ampersand-state/issues/78#issuecomment-56844756).

-- peter

Bryan Donovan

unread,
Mar 3, 2015, 11:45:23 AM3/3/15
to nod...@googlegroups.com
Your example works for me.

Floby

unread,
Mar 3, 2015, 11:45:41 AM3/3/15
to nod...@googlegroups.com
Hello,

Inheritance in javascript does not work with classes as you've already discovered. It works with prototypes and constructors.

You may find the util.inherits [1] method useful as it mimics classical simple inheritance.

your code would then look something like that :

ServerController.js:
function ServerController() {
  Server.call(this); // this would seem odd not to call the parent constructor
}
util.inherits(ServerController, Server);
ServerController.prototype.test = function () { 
  // override
}


zladuric

unread,
Mar 3, 2015, 11:46:07 AM3/3/15
to nod...@googlegroups.com
Your words and code differ a bit, but this is not a problem. But the most beautiful thing, you don't attempt to do anywhere what you say you want.

You say you want ServerController to inherit from Server and to do it, uyou require it in the module. Then later on you instantiate the server and attach env. That way it will never work. What you do in server instantiation (adding that ENV) is not yet available at the time you start the controller module.

The modules are executed at require time. But your actual objects are instantiated later from those prototypes (which do not yet have ENV).

Why don't you simply 'init' both with ENV there? The name suggests you won't be altering it. Or if you do want to share access to the same ENV the way Java does, it's simpler to just have some getter and setter on the server so that ctrl can reach for it, or the other way around. Or make a better use of class/object/inheritance pattern so that you can use something like super().ENV from controller. But it's not how it's usually done in node.

To get more details, you should share more of the requirements - why do you want to share these? Why do you need to separate them completely from one another, but have them share the ENV? Do you need that ENV to also be writable? By both server and ctrl?

Aaron Martone

unread,
Mar 3, 2015, 2:26:51 PM3/3/15
to nod...@googlegroups.com
I did not want to use the util module because that prevents me from comprehending the concept rather than understanding what is being done. I want to learn the proper wording and coding rather than have something else do it for me first.

My apologies if I do not use the right words and come across as confusing. One of the prime reasons why so many websites have failed to teach me these concepts is actually the language barrier of them referencing concepts by their names and being under the assumption that I am intimately familiar with the concept. So please correct me where I am wrong.

Following on what you've said zladuric:

I wanted to create an object that handled server-related functionality. And in hoping to keep with a "separation of concerns" mindset, felt an instance of a Server() object would need to have a Controller associated to it in order to perform the commands. In my mind, I felt the ServerController() should be within the Server() object (like on its this.ctrl property) so that when I instantiate the Server() into variable 'server', I could call things like:

var server = new Server();
server.ctrl.doAction();

And I was thinking because the ServerController() was inside the Server() object, if I passed in or init'd the Server() object with the environment variable, the ServerController could (through a properly setup prototype, gain access to its parent object, and resolve any 'this.env' reference.

I fully agree that it is best to conceptually determine how to 2 are related to one another; but I'm relatively new to knowing what patterns are out there (there has been a lot of stink about many JS-related resources teaching outdated methodologies, and I just want to make sure I'm not learning or invoking those types of practices).

Ultimately, I'd like to understand the why of the implementation. At first, I had thought "If ServerController is inside Server, this is an act of Composition, no? Like saying Car() is composed of an Engine(), the Server() is composed of a ServerController(). And then I was hoping the composed object would be able to look at its parent environment for "shared" common information between them so that I didn't have to init it or redefine it at the lower levels.

Peter Rust

unread,
Mar 3, 2015, 3:09:00 PM3/3/15
to nod...@googlegroups.com
Aaron,

As an aside: you seem to have a strong drive for conceptual purity and solid object-oriented design patterns and correctness. While these are not completely dismissed in the Node community, I think you will find them much less emphasized than in the Java and C# communities. Design patterns and conceptual purity are followed only when they lead to simpler code, fewer layers, etc right now (rather than anticipated in the future) -- and they are avoided when they lead to a proliferation of classes, layers and complex inheritance hierarchies. This is the sort of thing you see frequently in Java codebases, but it is refreshingly absent from the Node core and libuv libraries. There's not a ton of inheritance or deep hierarchies or heavily layered code going on in either, though these concepts are used sparingly when they are helpful for pragmatic reasons.

In this (as in other things), Node has it roots in unix. For more of a philosophical discussion of the merits of this approach, check out Isaac's article Going Fast, Frankenstein, and Refactoring and the Unix Philosophy chapter of The Art of Unix Programming.

That said, the Node community is quite diverse, so you won't see these priorities reflected in all the modules in npm or shared by everyone on this list.

-- peter 
Cornerstone Systems NW

Zlatko Đurić

unread,
Mar 3, 2015, 3:09:35 PM3/3/15
to nod...@googlegroups.com
Ok, so there are a few things I would advise.

1. KISS. Simple functions or several of them per module, without complex inheritance chains.
2. Closures (the term describing the sharing scope that you are referring to).

Keep it simple. Yes, separation of concerns is great, but what is your ultimate goal? To separate concerns or have a server with functionalities?

You can simply attach 'methods' (not a fullly correct term, but let's roll for a bit) to server.

So your server can have a few lines at the top:
var initialize = require('./initialize');
var attachClients = require('./attach-clients');
var functionalityA = require('./functionality-a');
var functionalityB = require('./functionality-b');
...
etc.

Then attach them to your server.

Server.prototype.initialize = initialize;
Server.prototype.attachClients = attachClients;
...

So now your server has all the methods you need. You can even namespace it into server.ctrl.method if you like it that way, but that's just a detail.

The real thing is that those things can be simple functions that do what you want.

That's a typical node.js approach - make a lot of small modules that each does one thing and one thing well. Each can export maybe just one function or another can export a "class" (though I'd move away from that concept ASAP), or glue together a few of them to achieve something third.


Closures - that's also your friend. Given this:
function Server() {

  // "close" this var inside the Server function
  var myName = 'Server thing.';
  function anAction() {
    // this "inner" function has access to the Server closure, or all variables reachable within server - even after instantiation.
    console.log('My name is', myName); // picks up the var from outer function
  }
  this.anAction = anAction; // if you really want to expose it.
  return this;
};

Now you have an outer thing (Server "class"), and the inner thing (the action). when you instantiate the server (var server = new Server();), the Server function is ran and gone. But later when you call the 'anAction()' of it, it will still have access to the last known value of that myName var. And if some other action, function, "method" let's say, changes that variable, your anAction will have access to that updated info.

And this works on module level, too.
- You have an internal variable or set of them in a module.
- You export some functions and objects.
- Each of those exported things still has access to those internal variables.
- As you call them in the future, they can even modify the module.

And further on, require() is caching stuff, so if you require the same file sometime later from some other module, you will get that current, modified state of things. 
That's how you would share info between your "Server" and "ServerController".

My advice is to learn a lot about closures in JS, and
    
  






--
Job board: http://jobs.nodejs.org/
New group rules: https://gist.github.com/othiym23/9886289#file-moderation-policy-md
Old group rules: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
---
You received this message because you are subscribed to a topic in the Google Groups "nodejs" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/nodejs/cyVYXFGfAr0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to nodejs+un...@googlegroups.com.
To post to this group, send email to nod...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/nodejs/9ba3e6c4-c8d7-4efa-af09-7b05be637fe7%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Zlatko

Aaron Martone

unread,
Mar 4, 2015, 12:03:46 PM3/4/15
to nod...@googlegroups.com
@Peter:
Thank you for the clarification. I sometimes get very caught up on "best practices" and the nitty gritty of the semantics in order to help myself follow a routine when learning new tech; it's like focusing a laser. In one way, it's great, because once I have that mindset, I'm adhered to it; but in other ways it can be bad, in that if I expect a certain methodology and that is not seeing enough of the scope of the "big picture", then I lose a lot of the implied meaning from being too focused.  I will attempt to be more pragmatic and not to be as strict; there is value in simple coding practices that I should not overlook.

@zladuric:
1. KISS. Got it. I'll try not to overthink and look for the best, pragmatic and simple approach rather than the most convoluted. I don't want to build the octopus where one arm reaches to another to another to another. Check.
2. Use of closures. That makes sense in retrospect. I can have functions return functions which encapsulate their scope's state when they are defined and returned in order for them to have a defined scope of reference.Got it.

I like your structured approach. Bring in the function through modularity and the attach via creation of references. Kind of a lateral inclusion rather than multiple nesting.

I actually tried your approach, and it worked on the first shot. Closures were something that originally took me a moment to understand, but once I did, I realized how powerful they were; like miniature enclosed environments. I really appreciate you giving a second pair of eyes on the problem and offering this different approach; it has definitely gotten me out of this "code block". I need to sometimes step back and take a look at approaching the problem from another direction.

Much appreciated.
Reply all
Reply to author
Forward
0 new messages