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> -->