3
\$\begingroup\$

This question is an improvement I did based on recommendations from these other questions:

Simple object-oriented calculator

Simple object-oriented calculator - follow-up

Simple object-oriented calculator - part 3

To those looking at this question for the first time, I'm trying to find a correct OOP model I can use in JS. I initially found a very different solution from what's normally done, and I received lots of help from some of the good guys here :) The links above tell the whole story.

Well I guess we're coming to the final steps. :) Here's what I did from @tkellehe advices:

JSFiddle

function extend(child, parent) {
    child.prototype = new parent();
    return child;
};

function Base() {

	this.override = function(method, body) {
		function is_function(obj) { return obj instanceof Function };
			if(is_function(this[method]) && is_function(body)) {
				var overridden = this[method];
				body.superior  = overridden.bind(this);
				this[method]   = body;
			}
			return this;
	};

	this.def = function(property, descriptor) {
		descriptor.enumerable = true;
		Object.defineProperty(this, property, descriptor);
		return this;
	};

	this.defHidden = function(property, descriptor) {
		descriptor.enumerable = false;
		Object.defineProperty(this, property, descriptor);
		return this;
	};
}

// Mandatory for derived classes. Sets correct prototype chain, so 
// objects instances can find public methods.
extend(ViewBase, Base);
function ViewBase() {

	// overriding a superior method maintaing the older version
	this.override('def', function(property, id) {
		this[property]              = id;
		this[_.camelCase(property)] = $("#" + id);
	    return this;
	});
};

// View class mirroring html components
extend(ViewCalc, ViewBase);
function ViewCalc () {

	//call to property of superior object
	this.def("IPT_X", 'x');
	this.def("IPT_Y",'y');
	this.def("IPT_RES",'res');
	this.def("BTN_SUM", 'sum');
	this.def("BTN_SUBTRACT", 'subt');
	this.def("BTN_MULTIPLY", 'mult');
	this.def("BTN_DIVISION", 'div');
	this.def("BTN_CLEAN", 'clean');
	this.def("BTN_RAND", 'rand');

};

extend(Operands, Base)
function Operands() {
	// connect view to the base business class
	this.view = new ViewCalc();

	// public method definition using descriptors
	this.def("x", { 
		get: function() { return +this.view.iptX.val() },
		set: function(v) { this.view.iptX.val(v) }
	});

	this.def("y", { 
		get: function() { return +this.view.iptY.val() },
		set: function(v) { this.view.iptY.val(v) }
	});    
   
	// normal public method
	this.clean = function() {
		this.x = this.y = 0;
	}

	// init 
	this.clean();

};

extend(Randomizer, Operands);
function Randomizer() {

	// private method
	function _getRandomNumber() {
		return Math.round(Math.random() * 1000);
	};

	this.populateRandomNumbers = function() {
		// call to local private method (getRandomNumber()))
		// and to public properties of superior class
		this.x = _getRandomNumber()
		this.y = _getRandomNumber();
	};

	// init
	this.populateRandomNumbers();
	
};

extend(Operations, Randomizer)
function Operations() {

	// save 'this' so when method are called from a different 
	// context the correct 'this' is called. It's necessary in
	// this object because of the call from 'onclick' events.
	// In the other constructors it's not necessary because this 
	// doesn't occur.
	var self = this;

	// private
	function _doSum() {
		return self.x + self.y;
	};

	function _doSubtraction() {
		return self.x - self.y;
	};

	function _doMultiplication() {
		return self.x * self.y;
	};
 
	function _doDivision() {
		return self.x / self.y;
	};

	function _showRes(val) {
		self.view.iptRes.val(val);
	};

	//public
	self.sum = function() {
		// call to 2 local private methods
		_showRes(_doSum());
	};

	self.subtract = function() {
		_showRes(_doSubtraction());
	};

	self.multiply = function() {
		_showRes(_doMultiplication());
	};

	self.division = function() {
		_showRes(_doDivision());
	}; 

	// overriding a superior method maintaing the older version
	self.override('clean', function() {
		// calling superior class's version of the method
		self.clean.superior();
		//call to property of superior object
		self.view.iptRes.val("");
	});

	// init
	self.view.btnSum.on('click', function() { self.sum() });
	self.view.btnSubtract.on('click', function() { self.subtract() });
	self.view.btnMultiply.on('click', function() { self.multiply() });
	self.view.btnDivision.on('click', function() { self.division() });   
	self.view.btnClean.on('click', function() { self.clean() });
	self.view.btnRand.on('click', function() { self.populateRandomNumbers() });

};

var o = new Operations();
<html>
<body>
X: <input id='x'>
<br>
Y: <input id='y'>
<br>
Res: <input id='res'>
<br>
<input id='sum' type='button' value='+'>
<input id='subt' type='button' value='-'>
<input id='mult' type='button' value='*'>
<input id='div' type='button' value='/'>
<input id='clean' type='button' value='C'>
<input id='rand' type='button' value='Rand'>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>

Some comments:

  • I was glad you edited and changed the override method to use bind. I wasn't confortable with that _SuperiorChainer thing. I found it too complicated and very strange to use. With bind, it got much better.
  • Instead of creating two methods: one for normal classes, and one for view classes (dev_prop and def_view_prop), I overrode it and reduced it's name. I think it got clear and pratical enough.
  • I loved the idea of using def_view_proc instead of parsing the class. Much better.
  • I loved the way of defining a property using descriptors with smaller syntax. It reminded me the old Dephi times. I found Delphi properties definition very useful. I pushed descriptor.enumerable to the function tough, and created a defHidden method just in case.
  • I've put function is_function(obj) { return obj instanceof Function }; inside of the overwritten function. It's only used there and as you recommended to me, we should avoid global context functions. If it's needed anywhere else I'll remove it from there and create an utility class or something.
  • I changed the name from inherit_from_to to extend. Small and equally explicit.
  • I loved the much cleaner code, removing showOperators method and stuff. I'll definitely will use method descriptions more.

So what do you think? Any final comments?

\$\endgroup\$
  • 1
    \$\begingroup\$ I like your naming conventions way better than mine:)lol \$\endgroup\$ – tkellehe Jan 3 '16 at 4:49
  • \$\begingroup\$ take a look at my blog again. I updated the post to use this oop model instead. And also converted to Require.js. And again, thanks for caring. \$\endgroup\$ – Nelson Teixeira Jan 3 '16 at 5:30
  • 1
    \$\begingroup\$ I'm thinking here is that an obvious improvement would be parse the view class to generate variables bound to the components in order to directly update the components once changed. I won't do that because I'm olny trying to reach a good oop model. And after that I'll be using AngularJs that already has think kind of functionality. Mas the idea stays for someone else that could use this. \$\endgroup\$ – Nelson Teixeira Jan 3 '16 at 14:38
2
\$\begingroup\$

Style

The style is very simple now and does not require a lot of extra functions to make it look OOP like:) Also, uses less code overall to get the same result as your first question:) and like I said in the comments, way better choice of names than what I had!

Also, I like how you have the def and defHidden to make it easier. Especially since enumerable is the main one that is used when defining a property.

Another good thing is overriding the def for the ViewBase was a good idea:)


Efficiency

Only make private functions if they benefit the instance not all instances.

function Randomizer() {

    // private method
    function _getRandomNumber() {
        return Math.round(Math.random() * 1000);
    };

Because you will end up creating a new _getRandomNumber for every instance created. You would be better off creating a static like function:

Randomizer.getRandomNumber = function() {
    return Math.round(Math.random() * 1000);
};

Or even give an instance the function such that they could override it later:

Randomizer.prototype.getRandomNumber = function() {
    return Math.round(Math.random() * 1000);
};

Or you could take is_function out and have a helper object with the getRandomNumber and the is_function in it.

var helper = {
    getRandomNumber: function() {
        return Math.round(Math.random() * 1000);
    },
    is_function: function(obj) {
        return obj instanceof Function
    }
};

While we are talking aboutis_function. It shouldn't be a global function but where you placed it is not very good, because every call will create a new one.

this.override = function(method, body) {
        function is_function(obj) { return obj instanceof Function };
        if(is_function(this[method]) && is_function(body)) {
        var overridden = this[method];
        body.superior  = overridden.bind(this);
        this[method]   = body;
    }
    return this;
};

You would be better off just inserting the instanceof Function directly into the if statement

this.override = function(method, body) {
        if(this[method] instanceof Function && body instanceof Function) {
        var overridden = this[method];
        body.superior  = overridden.bind(this);
        this[method]   = body;
    }
    return this;
};

Or attaching is_function to some object like stated above or even to Base.

Base.is_function = function(obj) { return obj instanceof Function };

Also, I believe Lodash has something called _.isFunction that does the same thing. I just created that small helper function because I already had it in my test code:)


Thanks! Haven't had this much fun answering a question before:) Lots of fun concepts have been thrown around!

\$\endgroup\$
  • 1
    \$\begingroup\$ Thank you for being so helpful. :) Your considerations are useful as always. I think this question already served it's purpose of finding a good way of doing oop. Now it's a question of refining it with pratical experience. Midway through the process I was thinking in creating a small page and give a name to this thing we created. I have a VPS and can easily create a page for it. But in the end it's just the small Base class and extend helper that do the work and I don't think it's big enough for that. But it sure was fun. Unless you have some ideas on how to enhace it a bit ;) \$\endgroup\$ – Nelson Teixeira Jan 3 '16 at 19:04
  • \$\begingroup\$ @NelsonTeixeira. Sorry for the late response, just started back to work. I created this github project if you want to keep the fun going. It is based off of some older code. I tried to clean it up some, but still needs a lot more comments, functionality, and better naming... Also, I have an idea on how to create private members within a CLASS and already have been working on it:D That is really cool that you have a VPS! \$\endgroup\$ – tkellehe Jan 7 '16 at 5:20
  • \$\begingroup\$ I guess at the moment I can't. But I'll keep watching it to see how far it will go :) \$\endgroup\$ – Nelson Teixeira Jan 18 '16 at 21:32
  • \$\begingroup\$ tkellehe please see the issue I opened in the Github project you created about this. \$\endgroup\$ – Nelson Teixeira Sep 27 '16 at 15:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.