Why SOLID matters - even for JavaScript

Why the



SOLID
principles matter - even for JavaScript




               Martin Lippert,VMware
       mlippert@vmware.com, @martinlippert
Why should I care?
Its easy and
smooth at the
   beginning...
Sooner or later...
But why?
Change happens

„Nothing endures but change“
                        Heraclitus
Dependencies matter
„Taking care of dependencies is one of the most
  critical success factors of healthy software.“
Utilities
        structured                                               object-oriented
       programming                                                programming
(„Go To Statement Considered Harmful“, 1968)
                                                                     (Simula 1967)


                                       functional
                                     programming                       modular
                                     (Lambda calculus 1930ies)
                                                                     programming
                                                                           (Modula-2 1978)
                 APIs
                                               component-oriented
                   plugins                         software
                    (Eclipse 2001)
Back to Reality
„structured“
programming
then we got
object-orientation
now everybody is
 programming in
    JavaScript
Design Principles matter
Information Hiding      Don‘t Repeat Yourself

Interface Segregation   Separation Of Concerns

 Liskov Substitution        Simple Design

Single Responsibility      Avoid Inheritance

   Speaking Code             Open Close

Dependency Inversion        Tell, Don‘t Ask

             Acyclic Dependencies
Single Responsibility
Open Close
Liskov Substitution
Interface Segregation
Dependency Inversion
Single Responsibility
             Principle




   „A class should have one,
and only one, reason to change.“
function Product(id, description) {
    this.getId = function() {
        return id;
    };
    this.getDescription = function() {
        return description;
    };
}                                                    (function() {
                                                         function addToCart() {
function Cart(eventAggregator) {                             var productId = $(this).attr('id');
    var items = [];                                          var product = $.grep(products, function(x) {
                                                                 return x.getId() == productId;
    this.addItem = function(item) {                          })[0];
        items.push(item);                                    cart.addItem(product);
    };
}                                                                 var newItem = $('<li></li>')
                                                                      .html(product.getDescription())
var products = [                                                      .attr('id-cart', product.getId())
    new Product(1, "Star Wars Lego Ship"),                            .appendTo("#cart");
    new Product(2, "Barbie Doll"),                          }
    new Product(3, "Remote Control Airplane")],
    cart = new Cart();                                   products.forEach(function(product) {
                                                             var newItem = $('<li></li>')
                                                                 .html(product.getDescription())
                                                                 .attr('id', product.getId())
                                                                 .dblclick(addToCart)
                                                                 .appendTo("#products");
                                                         });
                                                     })();



                                         http://freshbrewedcode.com/derekgreer/2011/12/08/solid-javascript-single-responsibility-principle/
function Product(id, description) {
                    add the item to the cart
    this.getId = function() {

    };
        return id;
                    when an item is clicked
    this.getDescription = function() {
        return description;
    };
}                                                    (function() {
                                                         function addToCart() {
function Cart(eventAggregator) {                             var productId = $(this).attr('id');
    updating the DOM to display
    var items = [];                                          var product = $.grep(products, function(x) {
                                                                 return x.getId() == productId;
          the newly added item
     this.addItem = function(item) {
         items.push(item);
                                                             })[0];
                                                             cart.addItem(product);
    };
}                                                                 var newItem = $('<li></li>')
                                                                      .html(product.getDescription())
var products = [                                                      .attr('id-cart', product.getId())
    new Product(1, "Star Wars Lego Ship"),                            .appendTo("#cart");
    new Product(2, "Barbie Doll"),                          }
    new Product(3, "Remote Control Airplane")],
    cart = new Cart();                                   products.forEach(function(product) {
         populate the initial list of                        var newItem = $('<li></li>')

          products on the page                                   .html(product.getDescription())
                                                                 .attr('id', product.getId())
                                                                 .dblclick(addToCart)
                                                                 .appendTo("#products");
                                                         });
                                                     })();



                                         http://freshbrewedcode.com/derekgreer/2011/12/08/solid-javascript-single-responsibility-principle/
MVC pattern instead
function Cart() {
    var items = [];

    this.addItem = function(item) {
        items.push(item);
        eventAggregator.publish("itemAdded", item);
    };
}

var cartView = (function() {
    eventAggregator.subscribe("itemAdded", function(eventArgs) {
        var newItem = $('<li></li>')
            .html(eventArgs.getDescription())
            .attr('id-cart', eventArgs.getId())
            .appendTo("#cart");
    });
})();

var cartController = (function(cart) {
    eventAggregator.subscribe("productSelected", function(eventArgs) {
        cart.addItem(eventArgs.product);
    });
})(new Cart());




                         http://freshbrewedcode.com/derekgreer/2011/12/08/solid-javascript-single-responsibility-principle/
function Product(id, description) {
    this.getId = function() {
        return id;
    };
    this.getDescription = function() {
        return description;
    };
}

var productView = (function() {
    function onProductSelected() {
        var productId = $(this).attr('id');
        var product = $.grep(products, function(x) {
            return x.getId() == productId;
        })[0];
        eventAggregator.publish("productSelected", {
            product: product
        });
    }

    products.forEach(function(product) {
        var newItem = $('<li></li>')
            .html(product.getDescription())
            .attr('id', product.getId())
            .dblclick(onProductSelected)
            .appendTo("#products");
    });
})();




                  http://freshbrewedcode.com/derekgreer/2011/12/08/solid-javascript-single-responsibility-principle/
Open Close
                  Principle




     „You should be able to extend
a classes behavior, without modifying it.“
var AnswerType = {                 var view = (function() {
    Choice: 0,                         function renderQuestion(target, question) {
    Input: 1                               ... // create DOM nodes
};
                                           if (question.answerType === AnswerType.Choice) {
function question(label,                       var input = document.createElement('select');
       answerType,                             var len = question.choices.length;
       choices) {                              for (var i = 0; i < len; i++) {
    return {                                       var option = document.createElement('option');
         label: label,                             option.text = question.choices[i];
         answerType: answerType,                   option.value = question.choices[i];
         choices: choices                          input.appendChild(option);
    };                                         }
}                                          }
                                           else if (question.answerType === AnswerType.Input) {
                                               var input = document.createElement('input');
                                               input.type = 'text';
                                           }

                                           answer.appendChild(input);
                                           questionWrapper.appendChild(questionLabel);
                                           questionWrapper.appendChild(answer);
                                           target.appendChild(questionWrapper);
                                       }

                                       return {
                                           render: function(target, questions) {
                                                for (var i = 0; i < questions.length; i++) {
                                                    renderQuestion(target, questions[i]);
                                                };
                                           }
                                       };
                                   })();

                                            http://freshbrewedcode.com/derekgreer/2011/12/19/solid-javascript-the-openclosed-principle/
var AnswerType = {                 var view = (function() {
    Choice: 0,                         function renderQuestion(target, question) {
    Input: 1                               ... // create DOM nodes
};
                                            if (question.answerType === AnswerType.Choice) {
function question(label,                        var input = document.createElement('select');
             change here
       answerType,                              var len = question.choices.length;
                                                for (var i = 0; i < len; i++) {
       choices) {
    return {                                        var option = document.createElement('option');
         label: label,                              option.text = question.choices[i];
         answerType: answerType,                    option.value = question.choices[i];
         choices: choices                           input.appendChild(option);
    };                                          }
}                                           }
                                            else if (question.answerType === AnswerType.Input) {
                                                var input = document.createElement('input');
      change here                               input.type = 'text';
                                            }

                                            answer.appendChild(input);
                                            questionWrapper.appendChild(questionLabel);
                                            questionWrapper.appendChild(answer);
                       adding new answer types or
                                }
                                            target.appendChild(questionWrapper);

                     changing the set{ of answer types
                                return
                            requires for function(target,questions.length; i++) {
                                         changes 0; i < questions) {
                                    render:
                                            (var i =
                                                           renderQuestion(target, questions[i]);
                                                    };
                                            }
                                       };
                                   })();

                                                http://freshbrewedcode.com/derekgreer/2011/12/19/solid-javascript-the-openclosed-principle/
factory method + strategy pattern instead
    function questionCreator(spec, my) {
        var that = {};

        my = my || {};
        my.label = spec.label;

        my.renderInput = function() {
            throw "not implemented";
        };

        that.render = function(target) {
            var questionWrapper = document.createElement('div');
            questionWrapper.className = 'question';

             var questionLabel = document.createElement('div');
             questionLabel.className = 'question-label';
             var label = document.createTextNode(spec.label);
             questionLabel.appendChild(label);

             var answer = my.renderInput();

             questionWrapper.appendChild(questionLabel);
             questionWrapper.appendChild(answer);
             return questionWrapper;
        };

        return that;
    }

                          http://freshbrewedcode.com/derekgreer/2011/12/19/solid-javascript-the-openclosed-principle/
function choiceQuestionCreator(spec) {

    var my = {},
        that = questionCreator(spec, my);

    my.renderInput = function() {
        var input = document.createElement('select');
        var len = spec.choices.length;
        for (var i = 0; i < len; i++) {
            var option = document.createElement('option');
            option.text = spec.choices[i];
            option.value = spec.choices[i];
            input.appendChild(option);
        }

         return input;
    };
                                    function inputQuestionCreator(spec) {
    return that;
}                                         var my = {},
                                              that = questionCreator(spec, my);

                                          my.renderInput = function() {
                                              var input = document.createElement('input');
                                              input.type = 'text';
                                              return input;
                                          };

                                          return that;
                                    }



                                         http://freshbrewedcode.com/derekgreer/2011/12/19/solid-javascript-the-openclosed-principle/
var view = {
    render: function(target, questions) {
        for (var i = 0; i < questions.length; i++) {
             target.appendChild(questions[i].render());
        }
    }
};

var questions = [
    choiceQuestionCreator({
    label: 'Have you used tobacco products within the last 30 days?',
    choices: ['Yes', 'No']
}),
    inputQuestionCreator({
    label: 'What medications are you currently using?'
})
    ];
                                               var AnswerType = {
                                                   Choice: 0,
                                                   Input: 1
                                               };

                                                      function question(label,
                                                             answerType,
                                                             choices) {
                                                          return {
                                                               label: label,
                                                               answerType: answerType,
                                                               choices: choices
                                                          };
                                                      }
                                  http://freshbrewedcode.com/derekgreer/2011/12/19/solid-javascript-the-openclosed-principle/
Liskov Substitution
                Principle




„Derived classes must be substitutable
       for their base classes.“
function Vehicle(my) {
    my = my || {};
    my.speed = 0;
    my.running = false;

    this.speed = function() {                                function FastVehicle(my) {
        return my.speed;                                         my = my || {};
    };
    this.start = function() {                                       var that = new Vehicle(my);
        my.running = true;                                          that.accelerate = function() {
    };                                                                  my.speed += 3;
    this.stop = function() {                                        };
        my.running = false;                                         return that;
    };                                                       }
    this.accelerate = function() {
        my.speed++;
    };
    this.decelerate = function() {
        my.speed--;
    };
    this.state = function() {
        if (!my.running) {
            return "parked";
        }
        else if (my.running && my.speed) {
            return "moving";
        }
        else if (my.running) {
            return "idle";
        }
    };
}


                                  http://freshbrewedcode.com/derekgreer/2011/12/31/solid-javascript-the-liskov-substitution-principle/
function Vehicle(my) {
    my = my || {};
    my.speed = 0;
    my.running = false;

    this.speed = function() {                                function FastVehicle(my) {
        return my.speed;                                         my = my || {};
    };
    this.start = function() {                                       var that = new Vehicle(my);
        my.running = true;                                          that.accelerate = function() {
    };                                                                  my.speed += 3;
    this.stop = function() {                                        };
        my.running = false;                                         return that;
    };                                                       }
    this.accelerate = function() {
        my.speed++;
    };
    this.decelerate = function() {
        my.speed--;
    };

                            changes behavior, therefore
    this.state = function() {
        if (!my.running) {

        }
            return "parked";
                              breaks existing clients
        else if (my.running && my.speed) {
            return "moving";
        }
        else if (my.running) {
            return "idle";
        }
    };
}


                                  http://freshbrewedcode.com/derekgreer/2011/12/31/solid-javascript-the-liskov-substitution-principle/
Solutions?

    Design by Contract
(avoid inheritance principle)
  test-driven development


       but remember:
     its about behavior
       not inheritance
Interface Segregation
           Principle




„Make fine grained interfaces
  that are client specific.“
var exampleBinder = {};
exampleBinder.modelObserver = (function() {
    /* private variables */
    return {
        observe: function(model) {
             /* code */
             return newModel;
        },
        onChange: function(callback) {
             /* code */
        }
    }
})();

exampleBinder.viewAdaptor = (function() {
    /* private variables */
    return {
        bind: function(model) {
             /* code */
        }
    }
})();

exampleBinder.bind = function(model) {
    /* private variables */
    exampleBinder.modelObserver.onChange(/* callback */);
    var om = exampleBinder.modelObserver.observe(model);
    exampleBinder.viewAdaptor.bind(om);
    return om;
};



                     http://freshbrewedcode.com/derekgreer/2012/01/08/solid-javascript-the-interface-segregation-principle/
var exampleBinder = {};
exampleBinder.modelObserver = (function() {
    /* private variables */
    return {
        observe: function(model) {
             /* code */
             return newModel;
        },
        onChange: function(callback) {
             /* code */
        }
    }
})();


                                      implicit interface defined,
exampleBinder.viewAdaptor = (function() {
    /* private variables */
    return {
        bind: function(model) {
                                      callbacks are often similar
             /* code */
        }
    }
})();

exampleBinder.bind = function(model) {
    /* private variables */
    exampleBinder.modelObserver.onChange(/* callback */);
    var om = exampleBinder.modelObserver.observe(model);
    exampleBinder.viewAdaptor.bind(om);
    return om;
};



                     http://freshbrewedcode.com/derekgreer/2012/01/08/solid-javascript-the-interface-segregation-principle/
Dependency Inversion
         Principle




„Depend on abstractions,
  not on concretions.“
$.fn.trackMap = function(options) {
    ...

     var mapOptions = {
         center: new google.maps.LatLng(options.latitude,options.longitude),
         zoom: 12,
         mapTypeId: google.maps.MapTypeId.ROADMAP
     },
         map = new google.maps.Map(this[0], mapOptions),
         pos = new google.maps.LatLng(options.latitude,options.longitude);

     var marker = new google.maps.Marker({
         position: pos,
         title: options.title,
         icon: options.icon
     });

     marker.setMap(map);

     options.feed.update(function(latitude, longitude) {
         marker.setMap(null);
         var newLatLng = new google.maps.LatLng(latitude, longitude);
         marker.position = newLatLng;
         marker.setMap(map);
         map.setCenter(newLatLng);
     });
                                                      $("#map_canvas").trackMap({
                                                          latitude: 35.044640193770725,
     return this;
                                                          longitude: -89.98193264007568,
};
                                                          icon: 'http://bit.ly/zjnGDe',
                                                          title: 'Tracking Number: 12345',
                                                          feed: updater
                                                      });

                                      http://freshbrewedcode.com/derekgreer/2012/01/22/solid-javascript-the-dependency-inversion-principle/
$.fn.trackMap = function(options) {
    ...

     var mapOptions = {                                            depends on the Google
         center: new google.maps.LatLng(options.latitude,options.longitude),
         zoom: 12,
         mapTypeId: google.maps.MapTypeId.ROADMAP
                                                                         Maps API
     },
         map = new google.maps.Map(this[0], mapOptions),
         pos = new google.maps.LatLng(options.latitude,options.longitude);

     var marker = new google.maps.Marker({
         position: pos,
         title: options.title,
         icon: options.icon
     });                                                       depends on the feed API
     marker.setMap(map);

     options.feed.update(function(latitude, longitude) {
         marker.setMap(null);
         var newLatLng = new google.maps.LatLng(latitude, longitude);
         marker.position = newLatLng;
         marker.setMap(map);
         map.setCenter(newLatLng);
     });
                                                      $("#map_canvas").trackMap({
                                                          latitude: 35.044640193770725,
     return this;
                                                          longitude: -89.98193264007568,
};
                                                          icon: 'http://bit.ly/zjnGDe',
                                                          title: 'Tracking Number: 12345',
                                                          feed: updater
                                                      });

                                      http://freshbrewedcode.com/derekgreer/2012/01/22/solid-javascript-the-dependency-inversion-principle/
introduce implicit interface
$.fn.trackMap = function(options) {
    var defaults = {
        /* defaults */
    };

     options = $.extend({}, defaults, options);

     options.provider.showMap(
         this[0],
         options.latitude,
         options.longitude,
         options.icon,
         options.title);

     options.feed.update(function(latitude, longitude) {
         options.provider.updateMap(latitude, longitude);
     });

     return this;
};

$("#map_canvas").trackMap({
    latitude: 35.044640193770725,
    longitude: -89.98193264007568,
    icon: 'http://bit.ly/zjnGDe',
    title: 'Tracking Number: 12345',
    feed: updater,
    provider: trackMap.googleMapsProvider
});
                http://freshbrewedcode.com/derekgreer/2012/01/22/solid-javascript-the-dependency-inversion-principle/
trackMap.googleMapsProvider = (function() {
    var marker, map;

    return {
        showMap: function(element, latitude, longitude, icon, title) {
             var mapOptions = {
                 center: new google.maps.LatLng(latitude, longitude),
                 zoom: 12,
                 mapTypeId: google.maps.MapTypeId.ROADMAP
             },
                 pos = new google.maps.LatLng(latitude, longitude);

             map = new google.maps.Map(element, mapOptions);

             marker = new google.maps.Marker({
                 position: pos,
                 title: title,
                 icon: icon
             });

             marker.setMap(map);
         },
         updateMap: function(latitude, longitude) {
             marker.setMap(null);
             var newLatLng = new google.maps.LatLng(latitude,longitude);
             marker.position = newLatLng;
             marker.setMap(map);
             map.setCenter(newLatLng);
         }
    };
})();



                        http://freshbrewedcode.com/derekgreer/2012/01/22/solid-javascript-the-dependency-inversion-principle/
„Design principles are essential
 for healthy code. Think about
      them all the time.“
Q&A
and thank you for your attention




        Martin Lippert,VMware
mlippert@vmware.com, @martinlippert
1 of 40

More Related Content

What's hot(20)

AngularJS FrameworkAngularJS Framework
AngularJS Framework
Barcamp Saigon977 views
Handling action bar in AndroidHandling action bar in Android
Handling action bar in Android
indiangarg1.2K views
What's New in AndroidWhat's New in Android
What's New in Android
Robert Cooper1.5K views
Android App Development - 05 Action barAndroid App Development - 05 Action bar
Android App Development - 05 Action bar
Diego Grancini825 views
Practical AngularJSPractical AngularJS
Practical AngularJS
Wei Ru1.2K views
Angular Mini-ChallengesAngular Mini-Challenges
Angular Mini-Challenges
Jose Mendez1K views
AngularJs Crash CourseAngularJs Crash Course
AngularJs Crash Course
Keith Bloomfield9.8K views
Angular In DepthAngular In Depth
Angular In Depth
Mindfire Solutions654 views
Action barAction bar
Action bar
Mu Chun Wang1.1K views
Patterns in PHPPatterns in PHP
Patterns in PHP
Diego Lewin686 views
Android 3Android 3
Android 3
Robert Cooper615 views

Viewers also liked(10)

Similar to Why SOLID matters - even for JavaScript(20)

Android Design PatternsAndroid Design Patterns
Android Design Patterns
Godfrey Nolan4K views
Mpg Dec07 Gian Lorenzetto Mpg Dec07 Gian Lorenzetto
Mpg Dec07 Gian Lorenzetto
melbournepatterns299 views
Beautiful java scriptBeautiful java script
Beautiful java script
Ürgo Ringo1.1K views
Web Components EverywhereWeb Components Everywhere
Web Components Everywhere
Ilia Idakiev1.3K views
The Principle of Hybrid App.The Principle of Hybrid App.
The Principle of Hybrid App.
musart Park2.6K views
20180721 code defragment20180721 code defragment
20180721 code defragment
Chiwon Song674 views
Clean JavascriptClean Javascript
Clean Javascript
Ryunosuke SATO16.8K views
Writing Maintainable JavaScriptWriting Maintainable JavaScript
Writing Maintainable JavaScript
Andrew Dupont3.1K views
Your Entity, Your CodeYour Entity, Your Code
Your Entity, Your Code
DrupalDay884 views
Your Entity, Your CodeYour Entity, Your Code
Your Entity, Your Code
Marco Vito Moscaritolo770 views
Android workshopAndroid workshop
Android workshop
Michael Galpin1.1K views

Recently uploaded(20)

METHOD AND SYSTEM FOR PREDICTING OPTIMAL LOAD FOR WHICH THE YIELD IS MAXIMUM ...METHOD AND SYSTEM FOR PREDICTING OPTIMAL LOAD FOR WHICH THE YIELD IS MAXIMUM ...
METHOD AND SYSTEM FOR PREDICTING OPTIMAL LOAD FOR WHICH THE YIELD IS MAXIMUM ...
Prity Khastgir IPR Strategic India Patent Attorney Amplify Innovation23 views
Liqid: Composable CXL PreviewLiqid: Composable CXL Preview
Liqid: Composable CXL Preview
CXL Forum118 views
[2023] Putting the R! in R&D.pdf[2023] Putting the R! in R&D.pdf
[2023] Putting the R! in R&D.pdf
Eleanor McHugh34 views
ChatGPT and AI for Web DevelopersChatGPT and AI for Web Developers
ChatGPT and AI for Web Developers
Maximiliano Firtman152 views

Why SOLID matters - even for JavaScript

  • 1. Why the SOLID principles matter - even for JavaScript Martin Lippert,VMware mlippert@vmware.com, @martinlippert
  • 2. Why should I care?
  • 3. Its easy and smooth at the beginning...
  • 6. Change happens „Nothing endures but change“ Heraclitus
  • 7. Dependencies matter „Taking care of dependencies is one of the most critical success factors of healthy software.“
  • 8. Utilities structured object-oriented programming programming („Go To Statement Considered Harmful“, 1968) (Simula 1967) functional programming modular (Lambda calculus 1930ies) programming (Modula-2 1978) APIs component-oriented plugins software (Eclipse 2001)
  • 12. now everybody is programming in JavaScript
  • 14. Information Hiding Don‘t Repeat Yourself Interface Segregation Separation Of Concerns Liskov Substitution Simple Design Single Responsibility Avoid Inheritance Speaking Code Open Close Dependency Inversion Tell, Don‘t Ask Acyclic Dependencies
  • 15. Single Responsibility Open Close Liskov Substitution Interface Segregation Dependency Inversion
  • 16. Single Responsibility Principle „A class should have one, and only one, reason to change.“
  • 17. function Product(id, description) { this.getId = function() { return id; }; this.getDescription = function() { return description; }; } (function() { function addToCart() { function Cart(eventAggregator) { var productId = $(this).attr('id'); var items = []; var product = $.grep(products, function(x) { return x.getId() == productId; this.addItem = function(item) { })[0]; items.push(item); cart.addItem(product); }; } var newItem = $('<li></li>') .html(product.getDescription()) var products = [ .attr('id-cart', product.getId()) new Product(1, "Star Wars Lego Ship"), .appendTo("#cart"); new Product(2, "Barbie Doll"), } new Product(3, "Remote Control Airplane")], cart = new Cart(); products.forEach(function(product) { var newItem = $('<li></li>') .html(product.getDescription()) .attr('id', product.getId()) .dblclick(addToCart) .appendTo("#products"); }); })(); http://freshbrewedcode.com/derekgreer/2011/12/08/solid-javascript-single-responsibility-principle/
  • 18. function Product(id, description) { add the item to the cart this.getId = function() { }; return id; when an item is clicked this.getDescription = function() { return description; }; } (function() { function addToCart() { function Cart(eventAggregator) { var productId = $(this).attr('id'); updating the DOM to display var items = []; var product = $.grep(products, function(x) { return x.getId() == productId; the newly added item this.addItem = function(item) { items.push(item); })[0]; cart.addItem(product); }; } var newItem = $('<li></li>') .html(product.getDescription()) var products = [ .attr('id-cart', product.getId()) new Product(1, "Star Wars Lego Ship"), .appendTo("#cart"); new Product(2, "Barbie Doll"), } new Product(3, "Remote Control Airplane")], cart = new Cart(); products.forEach(function(product) { populate the initial list of var newItem = $('<li></li>') products on the page .html(product.getDescription()) .attr('id', product.getId()) .dblclick(addToCart) .appendTo("#products"); }); })(); http://freshbrewedcode.com/derekgreer/2011/12/08/solid-javascript-single-responsibility-principle/
  • 19. MVC pattern instead function Cart() { var items = []; this.addItem = function(item) { items.push(item); eventAggregator.publish("itemAdded", item); }; } var cartView = (function() { eventAggregator.subscribe("itemAdded", function(eventArgs) { var newItem = $('<li></li>') .html(eventArgs.getDescription()) .attr('id-cart', eventArgs.getId()) .appendTo("#cart"); }); })(); var cartController = (function(cart) { eventAggregator.subscribe("productSelected", function(eventArgs) { cart.addItem(eventArgs.product); }); })(new Cart()); http://freshbrewedcode.com/derekgreer/2011/12/08/solid-javascript-single-responsibility-principle/
  • 20. function Product(id, description) { this.getId = function() { return id; }; this.getDescription = function() { return description; }; } var productView = (function() { function onProductSelected() { var productId = $(this).attr('id'); var product = $.grep(products, function(x) { return x.getId() == productId; })[0]; eventAggregator.publish("productSelected", { product: product }); } products.forEach(function(product) { var newItem = $('<li></li>') .html(product.getDescription()) .attr('id', product.getId()) .dblclick(onProductSelected) .appendTo("#products"); }); })(); http://freshbrewedcode.com/derekgreer/2011/12/08/solid-javascript-single-responsibility-principle/
  • 21. Open Close Principle „You should be able to extend a classes behavior, without modifying it.“
  • 22. var AnswerType = { var view = (function() { Choice: 0, function renderQuestion(target, question) { Input: 1 ... // create DOM nodes }; if (question.answerType === AnswerType.Choice) { function question(label, var input = document.createElement('select'); answerType, var len = question.choices.length; choices) { for (var i = 0; i < len; i++) { return { var option = document.createElement('option'); label: label, option.text = question.choices[i]; answerType: answerType, option.value = question.choices[i]; choices: choices input.appendChild(option); }; } } } else if (question.answerType === AnswerType.Input) { var input = document.createElement('input'); input.type = 'text'; } answer.appendChild(input); questionWrapper.appendChild(questionLabel); questionWrapper.appendChild(answer); target.appendChild(questionWrapper); } return { render: function(target, questions) { for (var i = 0; i < questions.length; i++) { renderQuestion(target, questions[i]); }; } }; })(); http://freshbrewedcode.com/derekgreer/2011/12/19/solid-javascript-the-openclosed-principle/
  • 23. var AnswerType = { var view = (function() { Choice: 0, function renderQuestion(target, question) { Input: 1 ... // create DOM nodes }; if (question.answerType === AnswerType.Choice) { function question(label, var input = document.createElement('select'); change here answerType, var len = question.choices.length; for (var i = 0; i < len; i++) { choices) { return { var option = document.createElement('option'); label: label, option.text = question.choices[i]; answerType: answerType, option.value = question.choices[i]; choices: choices input.appendChild(option); }; } } } else if (question.answerType === AnswerType.Input) { var input = document.createElement('input'); change here input.type = 'text'; } answer.appendChild(input); questionWrapper.appendChild(questionLabel); questionWrapper.appendChild(answer); adding new answer types or } target.appendChild(questionWrapper); changing the set{ of answer types return requires for function(target,questions.length; i++) { changes 0; i < questions) { render: (var i = renderQuestion(target, questions[i]); }; } }; })(); http://freshbrewedcode.com/derekgreer/2011/12/19/solid-javascript-the-openclosed-principle/
  • 24. factory method + strategy pattern instead function questionCreator(spec, my) { var that = {}; my = my || {}; my.label = spec.label; my.renderInput = function() { throw "not implemented"; }; that.render = function(target) { var questionWrapper = document.createElement('div'); questionWrapper.className = 'question'; var questionLabel = document.createElement('div'); questionLabel.className = 'question-label'; var label = document.createTextNode(spec.label); questionLabel.appendChild(label); var answer = my.renderInput(); questionWrapper.appendChild(questionLabel); questionWrapper.appendChild(answer); return questionWrapper; }; return that; } http://freshbrewedcode.com/derekgreer/2011/12/19/solid-javascript-the-openclosed-principle/
  • 25. function choiceQuestionCreator(spec) { var my = {}, that = questionCreator(spec, my); my.renderInput = function() { var input = document.createElement('select'); var len = spec.choices.length; for (var i = 0; i < len; i++) { var option = document.createElement('option'); option.text = spec.choices[i]; option.value = spec.choices[i]; input.appendChild(option); } return input; }; function inputQuestionCreator(spec) { return that; } var my = {}, that = questionCreator(spec, my); my.renderInput = function() { var input = document.createElement('input'); input.type = 'text'; return input; }; return that; } http://freshbrewedcode.com/derekgreer/2011/12/19/solid-javascript-the-openclosed-principle/
  • 26. var view = { render: function(target, questions) { for (var i = 0; i < questions.length; i++) { target.appendChild(questions[i].render()); } } }; var questions = [ choiceQuestionCreator({ label: 'Have you used tobacco products within the last 30 days?', choices: ['Yes', 'No'] }), inputQuestionCreator({ label: 'What medications are you currently using?' }) ]; var AnswerType = { Choice: 0, Input: 1 }; function question(label, answerType, choices) { return { label: label, answerType: answerType, choices: choices }; } http://freshbrewedcode.com/derekgreer/2011/12/19/solid-javascript-the-openclosed-principle/
  • 27. Liskov Substitution Principle „Derived classes must be substitutable for their base classes.“
  • 28. function Vehicle(my) { my = my || {}; my.speed = 0; my.running = false; this.speed = function() { function FastVehicle(my) { return my.speed; my = my || {}; }; this.start = function() { var that = new Vehicle(my); my.running = true; that.accelerate = function() { }; my.speed += 3; this.stop = function() { }; my.running = false; return that; }; } this.accelerate = function() { my.speed++; }; this.decelerate = function() { my.speed--; }; this.state = function() { if (!my.running) { return "parked"; } else if (my.running && my.speed) { return "moving"; } else if (my.running) { return "idle"; } }; } http://freshbrewedcode.com/derekgreer/2011/12/31/solid-javascript-the-liskov-substitution-principle/
  • 29. function Vehicle(my) { my = my || {}; my.speed = 0; my.running = false; this.speed = function() { function FastVehicle(my) { return my.speed; my = my || {}; }; this.start = function() { var that = new Vehicle(my); my.running = true; that.accelerate = function() { }; my.speed += 3; this.stop = function() { }; my.running = false; return that; }; } this.accelerate = function() { my.speed++; }; this.decelerate = function() { my.speed--; }; changes behavior, therefore this.state = function() { if (!my.running) { } return "parked"; breaks existing clients else if (my.running && my.speed) { return "moving"; } else if (my.running) { return "idle"; } }; } http://freshbrewedcode.com/derekgreer/2011/12/31/solid-javascript-the-liskov-substitution-principle/
  • 30. Solutions? Design by Contract (avoid inheritance principle) test-driven development but remember: its about behavior not inheritance
  • 31. Interface Segregation Principle „Make fine grained interfaces that are client specific.“
  • 32. var exampleBinder = {}; exampleBinder.modelObserver = (function() { /* private variables */ return { observe: function(model) { /* code */ return newModel; }, onChange: function(callback) { /* code */ } } })(); exampleBinder.viewAdaptor = (function() { /* private variables */ return { bind: function(model) { /* code */ } } })(); exampleBinder.bind = function(model) { /* private variables */ exampleBinder.modelObserver.onChange(/* callback */); var om = exampleBinder.modelObserver.observe(model); exampleBinder.viewAdaptor.bind(om); return om; }; http://freshbrewedcode.com/derekgreer/2012/01/08/solid-javascript-the-interface-segregation-principle/
  • 33. var exampleBinder = {}; exampleBinder.modelObserver = (function() { /* private variables */ return { observe: function(model) { /* code */ return newModel; }, onChange: function(callback) { /* code */ } } })(); implicit interface defined, exampleBinder.viewAdaptor = (function() { /* private variables */ return { bind: function(model) { callbacks are often similar /* code */ } } })(); exampleBinder.bind = function(model) { /* private variables */ exampleBinder.modelObserver.onChange(/* callback */); var om = exampleBinder.modelObserver.observe(model); exampleBinder.viewAdaptor.bind(om); return om; }; http://freshbrewedcode.com/derekgreer/2012/01/08/solid-javascript-the-interface-segregation-principle/
  • 34. Dependency Inversion Principle „Depend on abstractions, not on concretions.“
  • 35. $.fn.trackMap = function(options) { ... var mapOptions = { center: new google.maps.LatLng(options.latitude,options.longitude), zoom: 12, mapTypeId: google.maps.MapTypeId.ROADMAP }, map = new google.maps.Map(this[0], mapOptions), pos = new google.maps.LatLng(options.latitude,options.longitude); var marker = new google.maps.Marker({ position: pos, title: options.title, icon: options.icon }); marker.setMap(map); options.feed.update(function(latitude, longitude) { marker.setMap(null); var newLatLng = new google.maps.LatLng(latitude, longitude); marker.position = newLatLng; marker.setMap(map); map.setCenter(newLatLng); }); $("#map_canvas").trackMap({ latitude: 35.044640193770725, return this; longitude: -89.98193264007568, }; icon: 'http://bit.ly/zjnGDe', title: 'Tracking Number: 12345', feed: updater }); http://freshbrewedcode.com/derekgreer/2012/01/22/solid-javascript-the-dependency-inversion-principle/
  • 36. $.fn.trackMap = function(options) { ... var mapOptions = { depends on the Google center: new google.maps.LatLng(options.latitude,options.longitude), zoom: 12, mapTypeId: google.maps.MapTypeId.ROADMAP Maps API }, map = new google.maps.Map(this[0], mapOptions), pos = new google.maps.LatLng(options.latitude,options.longitude); var marker = new google.maps.Marker({ position: pos, title: options.title, icon: options.icon }); depends on the feed API marker.setMap(map); options.feed.update(function(latitude, longitude) { marker.setMap(null); var newLatLng = new google.maps.LatLng(latitude, longitude); marker.position = newLatLng; marker.setMap(map); map.setCenter(newLatLng); }); $("#map_canvas").trackMap({ latitude: 35.044640193770725, return this; longitude: -89.98193264007568, }; icon: 'http://bit.ly/zjnGDe', title: 'Tracking Number: 12345', feed: updater }); http://freshbrewedcode.com/derekgreer/2012/01/22/solid-javascript-the-dependency-inversion-principle/
  • 37. introduce implicit interface $.fn.trackMap = function(options) { var defaults = { /* defaults */ }; options = $.extend({}, defaults, options); options.provider.showMap( this[0], options.latitude, options.longitude, options.icon, options.title); options.feed.update(function(latitude, longitude) { options.provider.updateMap(latitude, longitude); }); return this; }; $("#map_canvas").trackMap({ latitude: 35.044640193770725, longitude: -89.98193264007568, icon: 'http://bit.ly/zjnGDe', title: 'Tracking Number: 12345', feed: updater, provider: trackMap.googleMapsProvider }); http://freshbrewedcode.com/derekgreer/2012/01/22/solid-javascript-the-dependency-inversion-principle/
  • 38. trackMap.googleMapsProvider = (function() { var marker, map; return { showMap: function(element, latitude, longitude, icon, title) { var mapOptions = { center: new google.maps.LatLng(latitude, longitude), zoom: 12, mapTypeId: google.maps.MapTypeId.ROADMAP }, pos = new google.maps.LatLng(latitude, longitude); map = new google.maps.Map(element, mapOptions); marker = new google.maps.Marker({ position: pos, title: title, icon: icon }); marker.setMap(map); }, updateMap: function(latitude, longitude) { marker.setMap(null); var newLatLng = new google.maps.LatLng(latitude,longitude); marker.position = newLatLng; marker.setMap(map); map.setCenter(newLatLng); } }; })(); http://freshbrewedcode.com/derekgreer/2012/01/22/solid-javascript-the-dependency-inversion-principle/
  • 39. „Design principles are essential for healthy code. Think about them all the time.“
  • 40. Q&A and thank you for your attention Martin Lippert,VMware mlippert@vmware.com, @martinlippert