This question is an improvement I did based on recommendations from these other questions:
Simple object-oriented calculator
Simple object-oriented calculator - follow-up
To those looking this question first 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.
And I guess I'm finally coming to grips with it. Please take a look.
function ViewBase() {
Object.defineProperty(this, "parse", {
value: function() {
for(prop in this)
this[_.camelCase(prop)] = $('#' + this[prop]);
},
writable: true,
enumerable: false
});
};
// View class mirroring html components
function ViewCalc () {
this.IPT_X = 'x';
this.IPT_Y = 'y';
this.IPT_RES = 'res';
this.BTN_SUM = 'sum';
this.BTN_SUBTRACT = 'subt';
this.BTN_MULTIPLY = 'mult';
this.BTN_DIVISION = 'div';
this.BTN_CLEAN = 'clean';
this.BTN_RAND = 'rand';
this.parse();
};
ViewCalc.prototype = new ViewBase();
function Operands() {
// connect view to the base business class
this.view = new ViewCalc();
//public
this.x = 0;
this.y = 0;
//public
this.showOperands = function() {
//use of a private property (IPT_yyX) and a public property (this.x)
this.view.iptX.val(this.x);
this.view.iptY.val(this.y);
};
this.clean = function() {
this.x = 0;
this.y = 0;
// call to a local public method
this.showOperands();
};
this.updateOperands = function(x, y) {
// use of a public property
this.x = x;
this.y = y;
};
this.clean();
};
function Randomizer() {
// private
function getRandomNumber() {
return Math.round(Math.random() * 1000);
};
this.updateOperands = function(x, y) {
// call to superior class's method
Randomizer.prototype.updateOperands.call(this, x, y);
// call to method of superior object
this.showOperands();
};
this.populateRandomNumbers = function() {
// call to public local method (this.updateOperands())
// and to a local private method (getRandomNumber()))
this.updateOperands(getRandomNumber(), getRandomNumber());
};
// init
this.populateRandomNumbers();
};
Randomizer.prototype = new Operands();
function Operations() {
//public
this.sum = function() {
// call to 2 local private methods
showRes(doSum());
};
this.subtract = function() {
showRes(doSubtraction());
};
this.multiply = function() {
showRes(doMultiplication());
};
this.division = function() {
showRes(doDivision());
};
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);
};
// init
this.view.btnSum.on('click', function() { self.sum() });
this.view.btnSubtract.on('click', function() { self.subtract() });
this.view.btnMultiply.on('click', function() { self.multiply() });
this.view.btnDivision.on('click', function() { self.division() });
this.view.btnClean.on('click', function() { self.clean() });
this.view.btnRand.on('click', function() { self.populateRandomNumbers() });
};
Operations.prototype = new Randomizer();
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>
What I did this time:
From @tkellehe's and @SirPython's answers:
- I'm realizing that using this self variable is causing more harm than does good. So I reverted to this, new and prototype usage.
From @tkellehe's answer:
- I remove all
_var
s from parameters, all private vars will use it from now on. Just there aren't any at the example at the moment. - I used
Object.define
in theView
classes so I don't need to useObject.keys
anymore. - Because I reverted to this, new and prototype usage, override function doesn't work/it's not needed anymore. So I had to use
Class.prototype.method.call
instead and I don't need a base class anymore or a salvage to not using new. At least at this point. - I know loading within context is important and I'll use it. I just don't think it's needed for this example. But that library loading function was really cool.
What still bothers me:
- I don't like using
Class.prototype.method.call
. It's too long and too clumsy. Is there a better solution for calling the superior method? Can you make override function work again in this format? - In the
ViewCall
andViewBase
classes, it would be better if I could putthis.parse()
in theViewBase
class in such a manner that it get called in the momentViewClass
was instatiated. But I couldn't find a way to do it. Does anyone have an idea on how I could do this?
Edit
I improved the code again and created another question (the final one):
1 Answer 1
Current Design
The implementation you have here uses less code than before and has readability for those who are accustomed to JavaScript. The simplest way to add readability, say for you blog, would be to add helper functions. But, here is where you must be careful, because you may add too many to the point where you create your own syntax. You can start to see that from what I made below pertaining to the superioritize
function where it presents its own mini API for interfacing with the chain of overridden methods.
My Attempt
I tried to pull everything together and have some OOP looking concepts present.
Inherit
The first is inherit_to_from
:
function inherit_to_from(child, parent) {
child.prototype = new parent();
return child;
}
I know what you are thinking... He is a genius! (JK)
The point of this function was merely to make it easier for when doing inheritance that it looks somewhat straight forward. Also, made the child
first to look "closer" to what _C++ does. When I use it I place it before the declaration of the child
that way it looks "closer" to C++.
function Parent() {};
inherit_to_from(Child, Parent);
function Child() {};
Overriding
For the overriding
, I left the Base
class such that every class used will have the same basic functions.
// To make things easier added this function.
function is_function(obj) { return obj instanceof Function };
function Base() {};
// Inheritable property 'override' used to handle overriding methods.
Base.prototype.override = function(method, body) {
// Made it a little bullet proof.
if(is_function(this[method]) && is_function(body)) {
var overridden = this[method];
body.superior = overridden;
this[method] = body;
}
return this;
};
// Inheritable property 'def_prop' used to add some OOP.
// This way instances can go " instance.def_prop(...) "
// rather than " Object.defineProperty(instance, ...) "
Base.prototype.def_prop = function(property, descriptor) {
Object.defineProperty(this, property, descriptor);
return this;
};
Now the tricky part was when you override the method, the context of the superior
method becomes the method that had overridden it.
var base = new Base();
base.foo = function() { return this };
base.override("foo", base.foo);
console.log(base.foo.superior() === base); // => false
console.log(base.foo.superior() === base.foo); // => true
So, I created this strange fix which probably is not the best take but it keeps somewhat of the original design.
// A helper function for determining if an object is SuperiorChainer (Not bullet proof...)
function is_SuperiorChainer(obj) { return is_function(obj) && _SuperiorChainer.prototype === obj.prototype };
// Idea would be to make this private...
function _SuperiorChainer(context, propertyObject) {
var chain = function() {
return propertyObject.apply(context, arguments);
};
// Used to help with verification to whether it is a _SuperiorChainer.
chain.prototype = _SuperiorChainer.prototype;
Object.defineProperty(chain, "context", { value: context });
// Gets the real object... Not the best name...
Object.defineProperty(chain, "real", { value: propertyObject });
// Creates the links to the chain.
Object.defineProperty(chain, "superior",
propertyObject.superior === undefined ?
{ value: undefined } :
{
get: function() {
return _SuperiorChainer(context, propertyObject.superior);
}
}
);
return chain;
}
// The public function that starts the chain. (What an amazing name... *sarcasm*)
function superioritize(context, propertyName) {
if(is_function(context[propertyName]))
return _SuperiorChainer(context, context[propertyName]);
}
The use is a little strange, but it works...
var base = new Base();
base.foo = function() { return this };
base.override("foo", base.foo);
console.log(superioritize(base, "foo").superior() === base); // => true
console.log(superioritize(base, "foo").superior() === base.foo); // => false
What you can do is edit the override
function connected to Base
to make it create the chain similar to what superioritize
does.
EDIT
So, after revisiting this... I realized I could have just used bind
to handle this problem... So, the new override
function would look like this:
// Inheritable property 'override' used to handle overriding methods.
Base.prototype.override = function(method, body) {
// Made it a little bullet proof.
if(is_function(this[method]) && is_function(body)) {
var overridden = this[method];
body.superior = overridden.bind(this);
this[method] = body;
}
return this;
};
And it looks like it did before!!!
var base = new Base();
base.foo = function() { return this };
base.override("foo", base.foo);
console.log(base.foo.superior() === base); // => true
console.log(base.foo.superior() === base.foo); // => false
ViewBase
For this I created a unique function that will automatically define the camel case property and create the JQuery object.
inherit_to_from(ViewBase, Base);
function ViewBase() {};
ViewBase.prototype.def_view_prop = function(property, id) {
this[property] = id;
this[_.camelCase(property)] = $("#" + id);
return this;
};
ViewCalc
Did not really have to do anything for this except use the def_view_prop
function. I did not attach the properties to the prototype
. So, you cannot inherit from ViewCalc
and have those properties be unique. Essentially, all objects of the inherited class would have the same values as to when the inheritance was made (an instance of ViewCalc
was placed into the prototype
so anything going down the prototype
chain would get the same values).
inherit_to_from(ViewCalc, ViewBase);
function ViewCalc() {
this.def_view_prop("IPT_X", 'x');
this.def_view_prop("IPT_Y", 'y');
this.def_view_prop("IPT_RES", 'res');
this.def_view_prop("BTN_SUM", 'sum');
this.def_view_prop("BTN_SUBTRACT", 'subt');
this.def_view_prop("BTN_MULTIPLY", 'mult');
this.def_view_prop("BTN_DIVISION", 'div');
this.def_view_prop("BTN_CLEAN", 'clean');
this.def_view_prop("BTN_RAND", 'rand');
};
NOTE: I probably should have added another function of some sort that would update the JQuery objects in case they do not get loaded correctly or the HTML was to be dynamically created later (or something weird like that). But, instead what I did was just place the script at the end of the HTML file to make sure all of the HTML elements had been created before the JQuery objects attempted to be created.
Operands & Randomizer
For Operands
I added two smart getters and setters x
and y
. These allow you to directly set x
and y
which will automatically set the input
. Also, this allows you to always get the most recent values in the input
s.
inherit_to_from(Operands, Base);
function Operands() {};
// Because this is in the prototype, all share the same ViewCalc.
Operands.prototype.def_prop("view", { value: new ViewCalc() });
Operands.prototype.def_prop("x", {
get: function() { return +this.view.iptX.val() },
set: function(v) { this.view.iptX.val(v) },
enumerable: true
});
Operands.prototype.def_prop("y", {
get: function() { return +this.view.iptY.val() },
set: function(v) { this.view.iptY.val(v) },
enumerable: true
});
Operands.prototype.clean = function() {
this.x = 0;
this.y = 0;
return this;
};
inherit_to_from(Randomizer, Operands);
function Randomizer() {};
Randomizer.prototype.def_prop("randomize", { value: function() {
this.x = Math.round(Math.random() * 1000);
this.y = Math.round(Math.random() * 1000);
return this;
}});
Operations
For Operations
I merely copied and pasted what you had then made some minor changes.
inherit_to_from(Operations, Randomizer);
function Operations() {
var self = this;
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);
};
self.sum = function() {
_showRes(_doSum());
};
self.subtract = function() {
_showRes(_doSubtraction());
};
self.multiply = function() {
_showRes(_doMultiplication());
};
self.division = function() {
_showRes(_doDivision());
};
self.override("clean", function() {
superioritize(self, "clean").superior();
self.view.iptRes.val("");
});
self.view.btnSum.on ('click', self.sum);
self.view.btnSubtract.on('click', self.subtract);
self.view.btnMultiply.on('click', self.multiply);
self.view.btnDivision.on('click', self.division);
self.view.btnClean.on ('click', self.clean);
self.view.btnRand.on ('click', function() { self.randomize() });
};
Code Inaction
function is_function(obj) { return obj instanceof Function };
function inherit_to_from(child, parent) {
child.prototype = new parent();
return child;
};
function _is_SuperiorChainer(obj) { return is_function(obj) && _SuperiorChainer.prototype === obj.prototype }
function _SuperiorChainer(context, propertyObject) {
var chain = function() {
return propertyObject.apply(context, arguments);
};
chain.prototype = _SuperiorChainer.prototype;
Object.defineProperty(chain, "context", { value: context });
Object.defineProperty(chain, "real", { value: propertyObject });
Object.defineProperty(chain, "superior",
propertyObject.superior === undefined ?
{ value: undefined } :
{
get: function() {
return _SuperiorChainer(context, propertyObject.superior);
}
}
);
return chain;
}
function superioritize(context, propertyName) {
if(is_function(context[propertyName]))
return _SuperiorChainer(context, context[propertyName]);
}
function Base() {}
Base.prototype.override = function(method, body) {
if(is_function(this[method]) && is_function(body)) {
var overridden = this[method];
body.superior = overridden;
this[method] = body;
}
return this;
};
Base.prototype.def_prop = function(property, descriptor) {
Object.defineProperty(this, property, descriptor);
return this;
};
inherit_to_from(ViewBase, Base);
function ViewBase() {};
ViewBase.prototype.def_view_prop = function(property, id) {
this[property] = id;
this[_.camelCase(property)] = $("#" + id);
return this;
};
inherit_to_from(ViewCalc, ViewBase);
function ViewCalc() {
this.def_view_prop("IPT_X", 'x');
this.def_view_prop("IPT_Y", 'y');
this.def_view_prop("IPT_RES", 'res');
this.def_view_prop("BTN_SUM", 'sum');
this.def_view_prop("BTN_SUBTRACT", 'subt');
this.def_view_prop("BTN_MULTIPLY", 'mult');
this.def_view_prop("BTN_DIVISION", 'div');
this.def_view_prop("BTN_CLEAN", 'clean');
this.def_view_prop("BTN_RAND", 'rand');
};
inherit_to_from(Operands, Base);
function Operands() {};
// Because this is in the prototype all have the same ViewCalc.
Operands.prototype.def_prop("view", { value: new ViewCalc() });
Operands.prototype.def_prop("x", {
get: function() { return +this.view.iptX.val() },
set: function(v) { this.view.iptX.val(v) },
enumerable: true
});
Operands.prototype.def_prop("y", {
get: function() { return +this.view.iptY.val() },
set: function(v) { this.view.iptY.val(v) },
enumerable: true
});
Operands.prototype.clean = function() {
this.x = 0;
this.y = 0;
return this;
};
inherit_to_from(Randomizer, Operands);
function Randomizer() {};
Randomizer.prototype.def_prop("randomize", { value: function() {
this.x = Math.round(Math.random() * 1000);
this.y = Math.round(Math.random() * 1000);
return this;
}});
inherit_to_from(Operations, Randomizer);
function Operations() {
var self = this;
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);
};
self.sum = function() {
_showRes(_doSum());
};
self.subtract = function() {
_showRes(_doSubtraction());
};
self.multiply = function() {
_showRes(_doMultiplication());
};
self.division = function() {
_showRes(_doDivision());
};
self.override("clean", function() {
superioritize(self, "clean").superior();
self.view.iptRes.val("");
});
self.view.btnSum.on ('click', self.sum);
self.view.btnSubtract.on('click', self.subtract);
self.view.btnMultiply.on('click', self.multiply);
self.view.btnDivision.on('click', self.division);
self.view.btnClean.on ('click', self.clean);
self.view.btnRand.on ('click', function() { self.randomize() });
};
var o = new Operations();
<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>
<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'>
</body>
<!-- <script src = "test.js"></script> -->
-
\$\begingroup\$ again, thanks for caring so much. I'm traveling today. but tomorrow I'll take a really close look at it. :) \$\endgroup\$Nelson Teixeira– Nelson Teixeira2015年12月30日 21:52:10 +00:00Commented Dec 30, 2015 at 21:52
-
\$\begingroup\$ I've picked up your advices and created a new question. I commented there. codereview.stackexchange.com/questions/115645/… \$\endgroup\$Nelson Teixeira– Nelson Teixeira2016年01月02日 18:19:19 +00:00Commented Jan 2, 2016 at 18:19
Explore related questions
See similar questions with these tags.