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.