使用JavaScript Function.prototype进行代码重构的一些例子 JavaScriptjsSAPSAP云平台SAP Cloud Platform
Example1 – how to enhance a big / complex function with your own logic
Suppose there is already a big function with huge logic, and you have the task to enhance it by appending your own logic to its end.
var bigFunction = function() { // big logic console.log("big logic"); }
Of course if the big function is owned by you, you could directly append the enhancement, like below:
var bigFunction = function() { // big logic console.log("big logic"); // append our own enhancement directly console.log("our own enhancement"); }
Another approach, if you would not like to touch the existing one, is to use a temporary variable to store the old bigFunction:
var _old = bigFunction; bigFunction = function() { if ( _old ) { _old(); } console.log("our own enhancement"); } bigFunction(); // After it is executed, console will print out
A Better Solution
We add a new function in Function.prototype:
Function.prototype.after = function( func ){ var _self = this; return function() { var ret = _self.apply( this, arguments ); if ( ret === false ) { return false; } func.apply( this, arguments); return ret; } }
This after function returns a new function, which will first call the original function, and then call the subsequent function passed in via variable “func” if the original function execution is successfully ( ret != false ).
Then just construct a new function using after function:
bigFunction = bigFunction.after( function() { console.log("our own logic"); });
Now when bigFunction() is executed, we get the same printout. With this approach, the temporary variable is avoided.
Example 2 – Write performance measurement code without polluting your productive code
Suppose in your application you have to create mass div node. You need to measure the creation performance. The most straightforward way is to get timestamp before and after creation. In order to get timestamp you cannot avoid to pollute your productive code with two “new Date()” ( or any other way for time measurement in JavaScript ) as below.
var append_doms = function() { var d = new Date(); // dirty code - nothing to do with application logic!!! for( var i = 0; i < 100000; i++) { var div = document.createElement( "div"); document.body.appendChild(div); } // dirty code - nothing to do with application logic!!! console.log(" time consumed: " + ( new Date() - d)); };
A Better Solution
Using the idea of the first example, we create another method before in Function.prototype, which has similar design as Function.prototype.after:
Function.prototype.before = function( func) { var _self = this; return function() { if ( func.apply( this, arguments ) === false ) { return false; } return _self.apply( this.arguments); } }
With this approach, our productive code is clean – not any time measurement code there.
var append_doms = function() { for( var i = 0; i < 100000; i++) { var div = document.createElement( "div"); document.body.appendChild(div); } };
And we wrap the original function with before and after function we defined in Function.prototype:
var log_time = function( func, log_name) { return func = ( function() { var d; return func.before( function(){ d = new Date(); }).after( function(){ console.log( log_name + ( new Date() - d)); }); })(); };
Now we get a new function log_time which is dedicatedly used for performance measurement. This new function actually consists of three parts:
(1) an anonymous function with body “d = new Date();”, chained by Function.prototype.before.
(2) the original append_doms
(3) an anonymous function with body “console.log( log_name + ( new Date() – d)); “, chained by Function.prototype.after.
We can elegantly call it via one line of code below to achieve the performance measurement.
log_time(append_doms, "consumed time: ")();
AOP in Java
Update on 2016-07-29: In Java it could be done elegantly via Spring framework. Please find more detail from this blog: [An example to explain why we need AOP – Aspect Oriented Programming](https://blogs.sap.com/?p=145576) .
Example 3 – Replace lots of IF-ELSE with Design Pattern “Chain of Responsibility”
Suppose I am responsible for developing a file upload function and the upload could be finished by various approach if each feature is supported by client side. The priority is listed below:
// Priority: ActiveX > HTML5 > Flash > Form(default)
If means for example, if ActiveX is supported by client’s browser, it should be used. The default is upload via form, if all previous tries have failed.
function isActiveXSupported(){ //... return false; } function isHTML5Supported(){ //... return true; } function isFlashSupported(){ //... return false; }
The codes above just simulate the situation that HTML5 upload should be used, since its preceding attempt, isActiveXSupported, returns false. In order to get the proper upload service, we have to code lots of tedious IF-ELSE evaluation:
var uploadAPI; if ( isActiveXSupported()) { // lots of initialization work uploadAPI = { "name": "ActiveX"}; } else if( isHTML5Supported()) { // lots of initialization work uploadAPI = { "name": "HTML5"}; } else if( isFlashSupported()) { // lots of initialization work uploadAPI = { "name": "Flash"}; } else { // lots of initialization work uploadAPI = { "name": "Form"}; } console.log(uploadAPI); // HTML5 service is got
A Better Solution
We do some minor change on Function.prototype.after:
Function.prototype.after = function( func ){ var _self = this; return function() { var ret = _self.apply( this, arguments ); if ( ret ) { return ret; } return func.apply( this, arguments); } }
Now if the execution of original function is successfully ( returns true ), we terminate the function chain, that is, we don’t pass the responsibility chain to its subsequent function passed via func.
With this approach, there is no more IF-ELSE evaluation. In fact, now we spread the evaluation into the dedicated initialization function of each API:
var getActiveX = function() { try { // lots of initialization work return { "name": "ActiveX"}; } catch (e) { // user broswer does not support ActiveX return null; } } var getHTML5 = function() { try { // lots of initialization work return { "name": "HTML5"}; } catch (e) { // user broswer does not support HTML5 return null; } } var getFlash = function() { try { // lots of initialization work return { "name": "Flash"}; } catch (e) { // user broswer does not support Flash return null; } } var getForm = function() { return { "name": "Form"}; } ```
Now in order to get appropriate API, we just use single line:
> var uploadAPI = getActiveX.after(getHTML5).after(getFlash).after(getForm)();
This design idea is actually the so called “Chain of Responsibility”. Simply speaking, the function in the beginning of chain ( in my example, it is getActiveX ) will analyze whether it is able to resolve the task. If yes, the whole statement returns, task is over. Otherwise, it simply delegate the task to the next node in the chain.
# Example 4 – eliminate lots of IF-ELSE in validity check via Strategy Design Pattern
For example, before we assemble the request payload to send OData request via OData API, we must perform various validity check on the user input. If you have a page with lots of UI elements, usually it will lead to lots of IF-ELSEIF-ELSEIF validity check code spread in your application.
```javascript
var send = function() { var value = input.value; if( value.length === '' ) { return false; } else if( value.length > MAX_LENGTH) { return false; } ... // LOTS OF other rules to be checked else { // all check passes, now ready to call OData API } }
A Better Solution
Instead of directly coding all those validity checks in the application, we can first put the rules in a JavaScript object (so called “Strategy” in Design Pattern ):
var valid_rules = { not_empty: function( value ) { return value.length !== ''; }, max_length: function( value ) { return value.length <= MAX_LENGTH ; } }
With strategy ready, now we can simply write the validity check function in a generic way. It doesn’t know any detail about validity rules, but simply scan rules configured in strategy object one by one. If any check fails, the whole function will return false – check not pass.
var valid_check = function() { for( var i in valid_rules ) { if ( vali_rules[i].apply( this, arguments) === false ) { return false; } } }
Finally the send function could be reduced as:
var send = function( value ) { if ( valid_check( valu