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:
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
toextend
. 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?