On Github frnz / solid_presentation
Un conjunto de buenas prácticas de OO para preparar el código a cambiar. Mantiene el código los liviano posible, pero preparándolo para poder hacer cambios con el más mínimo esfuerzo.
No es una cantidad de métodos, más bien agrupaciones de métodos que cumplen un objetivo
Cart = function(items) { this.log = function(log) { console.log(log) }, this.calculateTotal = function() { return items.sum("price") // sugar.js <3 <3 <3 }, this.displayInfo = function() { this.log("Total is " + this.calculateTotal()) $("#total").html(total) } }
Cart = function(items, options) { this.log = function(log) { if (options.log) { // D: D: D: if (options.logActive) { console.log(log) } } }, this.calculateSubtotal = function() { return items.sum("price") }, this.calculateTotal = function() {...} this.displayInfo = function() { this.log("Total is " + total) $("#subTotal").html(this.calculateSubtotal()) $("#total").html(this.calculateTotal()) } }
Cart = function(items, options) { this.log = function(log) { if (options.log) { // D: D: D: if (options.logActive) { console.log(log) } } }, this.calculateSubtotal = function() { return items.sum("price") }, this.calculateTotal = function() {...} this.displayInfo = function() { this.log("Total is " + total) if (options.showSubtotal) { // ლ(ಠ益ಠლ) $("#subTotal").html(this.calculateSubtotal()) } $("#total").html(this.calculateTotal()) } }
6
98.986
CartCalculator
CartCalculator = function(items) { this.subTotal = function() { return this.items.sum("price") } this.total = function() { return this.subTotal() + this.subTotal()*0.13 } }
Renderers
CartInfoRenderer = function(cartCalculator) { $("#total").html(cartCalculator.total()) } CartPageInfoRenderer = function(cartCalculator) { $("#subtotal").html(cartCalculator.subTotal()) $("#total").html(cartCalculator.total()) }
Logger
Logger = function() { this.log = function(message) { if (DEBUG) { console.log(message) } } }
Controller
CartController = function(options) { options ||= // default options this.render = function() { options.logger.log("Total is " + options.cart.total()) options.renderer(options.cart) } } // Página de carrito: Cart({renderer: CartPageInfoRenderer}).render() // Otras Cart({renderer: CartInfoRenderer}).render()
Ahí afuera: Backbone patterns:
var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/');
var http = require('http'); http.createServer(function (req, res) { var header=req.headers['authorization']||'', token=header.split(/\s+/).pop()||'', auth=new Buffer(token, 'base64').toString(), parts=auth.split(/:/), username=parts[0], password=parts[1]; res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/');
var http = require('http'); http.createServer(function (req, res) { var header=req.headers['authorization']||'', token=header.split(/\s+/).pop()||'', auth=new Buffer(token, 'base64').toString(), parts=auth.split(/:/), username=parts[0], password=parts[1]; // CSRF CODEZ @.@ // Incio tiempo de respuesta res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); // Log tiempo de respuesta }).listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/');
var app = connect() .use(connect.csrf()) .use(connect.static('public')) .use(function(req, res){ res.end('hello world\n'); }) .listen(3000);
(function ($) { $.iDareYou = $.fn.iDareYou = function () { return this; } $.iDoubleDareYou = $.fn.iDoubleDareYou = function () { return this; } $.motherFucker = $.fn.motherFucker = function () { return this; } })(jQuery); $(function() { // ZOMG!!! COMO SAMUEL L. JACKSON!!! $("#title").hide().iDareYou().iDoubleDareYou().motherFucker() });
Las clases derivadas deben poder sustituirse por sus clases base.
Rectangle
class Rectangle setWidth: (width) -> @width = width setHeight: (height) -> @height = height area: -> @width * height rect = new Rectangle rect.setWidth(2) rect.setHeight(1) rect.area() # 2 :D
var Rectangle; Rectangle = (function() { function Rectangle() {} Rectangle.prototype.setWidth = function(width) { return this.width = width; }; Rectangle.prototype.setHeight = function(height) { return this.height = height; }; Rectangle.prototype.area = function() { return this.width * height; }; return Rectangle; })();
Square
class Square extends Rectangle setLength: (length) -> @width = length @height = length sq = new Square sq.setLength(2) sq.area() # 4 |m|
var Square, sq, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; Square = (function(_super) { __extends(Square, _super); function Square() { return Square.__super__.constructor.apply(this, arguments); } Square.prototype.setLength = function(length) { this.width = length; return this.height = length; }; return Square; })(Rectangle); sq = new Square; sq.setLength(2); sq.area();
Square
class Square extends Rectangle setLength: (length) -> @width = length @height = length sq = new Square sq.setWidth(2) sq.setHeight(1) sq.area() # 2 D: D: D:
Square
class Square extends Rectangle setLength: (length) -> @width = length @height = length setWidth: (length) -> setLength(length) # Ok... setHeight: (length) -> setLength(length) # Ok... sq = new Square sq.setWidth(2) sq.setHeight(1) sq.area() # 1...
Si no se mantiene LSP, la jeraquías de clases empezarían a integrar métodos inútiles, y eventualmente se convertirían en APIs difíciles de entender.
Sin LSP no se pueden crear pruebas unitarias que satisfagan toda la jerarquía de clases. Habría que estar rediseñando las pruebas.
Faye.js sobre extensiones
These methods should accept a message and a callback function, and should call the function with the message once they have made any modifications.Las entidades no deberían ser forzadas a depender de métodos que no usan.
SRP aplicado a librerías y extensiones.
Spine.js
class Contact extend Spine.Model @configure "Contact", "name" @extend Spine.Model.Local
// Backbone var object = {}; _.extend(object, Backbone.Events); object.on("alert", function(msg) { alert("Triggered " + msg); }); object.trigger("alert", "an event"); // Spine Tasks.bind "create", (foo, bar) -> alert(foo + bar) Tasks.trigger "create", "some", "data" // PJAX $('#main').pjax('a.pjax') .on('pjax:start', function() { $('#loading').show() }) .on('pjax:end', function() { $('#loading').hide() })
Signup Wizard
function showStepOne() { $("#step-1").show() // ... } function showStepTwo() { $("#step-1").hide() $("#step-2").show() // ... }
Signup Wizard
function showStepOne() { $("#step-1").show() // ... } function showStepTwo() { $("#step-1").hide() $("#step-2").show() // ... } function showStepThree() { $("#step-2").hide() $("#step-3").show() // ... }
Signup Wizard 2
$mainInfo.on("enter", function() { // ... $mainInfo.show() }) $mainInfo.on("leave", function() { $mainInfo.hide() // ... }) // Al inicio $mainInfo.trigger("enter") // "Observador" $mainInfo.on("leave", function() { $profileInfo.trigger("enter") }) coolWizardBuilder($mainInfo).then($profileInfo).then($promoInfo)
Users
// save user $.ajax({ type: 'POST', url: "/users", data: user_data, success: success }); // update user $.ajax({ type: 'PUT', url: "/users/1", data: user_data, success: success });
Users
// save user saveUser = function(user_data, success...) { $.ajax({ type: 'POST', url: "/users", data: user_data, success: success }); } // update user updateUser = function(user_data, success...) { $.ajax({ type: 'PUT', url: "/users/1", data: user_data, success: success }); }
Método para delegar instanciamiento.
factory.create(MyFactory, options) factory.create(MyOtherFactory, options) // Factories Storage.create() // RestStorage Storage.create({local: true}) // LocalStorage // derbyjs store = derby.createStore(options)
Legacy code: Código sin pruebas.