Михаил Давыдов
Курс по паттернам, 2014
На каждую сущность должна быть возложена одна единственная ответственность.
JSON.parse
& JSON.stringify
function addToCart(e) {
var $el = $(this);
cart.push($el.attr('id'), $el.attr('title'));
var newItem = $('<li></li>')
.html($el.attr('title'))
.attr('id-cart', $el.attr('id'))
.appendTo('#cart');
}
function addToCart(item) {
cart.add(_.pick(item, ['id', 'title']));
}
this.bindTo(cart, 'add', (item) => {
render(item).appendTo(this.$el);
});
render()
.appendTo()
this.$el
bind('#cart', {
cart: cart
});
<ul id="cart">
<li data-each-item="cart" id="{item.id}">
{item.title}
</li>
</ul>
class MyMailer {
send (options, cb) {
this.smtpTransport.sendMail(options, () => {
cb();
console.log('Message sent');
});
}
}
class MyMailer {
/**
* @param {ILogger} logger
*/
constructor(logger) {
this.logger = logger;
}
}
class MyMailer {
send (options, cb) {
this.smtpTransport.sendMail(options, => {
cb();
this.logger.log('Message sent');
});
}
}
class MyMailer extends EventEmitter {
send (options, cb) {
this.smtpTransport.sendMail(options, => {
cb();
this.emit('done', 'Message sent');
});
}
}
Сущности (классы, модули, функции) должны быть открыты к раширению, но закрыты от модификаций.
function renderQuestion (q) {
switch (q.type) {
case 'yes-no':
case 'multi':
return q.title + renderAnswers(q.answers);
}
}
class QuestionRenderer {
constructor () {
this.methods = {};
}
use (methods) {
Object.assign(this.methods, methods);
}
}
class QuestionRenderer {
render (question) {
return this.methods[question.type](question);
}
}
class MyMailer {
send (mail, cb) {
this.smtpTransport.sendMail(mail, => () {
cb();
console.log('Message sent');
});
}
}
class MyMailer extends EventEmitter {
send (mail) {
this.emit('before-send', mail);
this.smtpTransport.sendMail(mail, => (err, st) {
if (err) return this.emit('error', err);
this.emit('after-send', st);
});
}
}
var mailer = new MyMailer(options);
listenTo(mailer, 'before-send', formatAndSaveLogToDb);
listenTo(mailer, 'after-send', formatAndPipeToStdout);
listenTo(mailer, 'after-send', () => res.render('ok'));
listenTo(mailer, 'error', sendAlertSmsTo(cfg.adminEmail));
mailer.send(someMail);
class Robot {
constructor (weapons) {
this.weapons = weapons;
this.weapon = weapons[0];
}
shoot () {}
}
shoot () {
if (this.weapon instanceof Shotgun) {
this.weapon.loadShell(1);
this.weapon.pressTrigger();
}
if (this.weapon instanceof Grenade) {
this.weapon.throw();
}
}
class Robot {
constructor (weapons) {
this.weapons = weapons;
this.weapon = weapons[0];
}
shoot () {
this.weapon.use();
}
}
Сущности, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа не зная об этом.
Подкласс не должен требовать от вызывающего кода больше, чем базовый класс, и не должен предоставлять вызывающему коду меньше, чем базовый класс.
var robot = new Robot();
robot.shot();
// class SniperRobot extends Robot
var robot = new SniperRobot();
robot.reload();
robot.shot();
reload()
class PointFromIp extends Geolocation {
getLocation () {
return super().point;
}
}
getLocation()
(меняет)function Robot(name) {
this.name = name;
}
function RobotMk2 (name, weapons) {
Robot.call(this, name);
if (!weapons) throw new Error();
}
weapons
class Vehicle {
constructor () {
this.speed = 0;
}
accelerate () { this.speed++; }
decelerate () { this.speed--; }
}
class FastVehicle extends Vehicle {
accelerate () { this.speed++; this.speed++; }
}
var vehicle = new FastVehicle();
vehicle.accelerate();
vehicle.decelerate();
expect(vehicle.speed).to.equal(0);
class Person {
decelerateBike () {
this.bike.decelerate();
if (this.bike instanceof FastVehicle) {
this.bike.decelerate();
}
}
}
class Point {
center() {
return this._center;
}
}
class MutablePoint extends Point {
center(value) {
if (!value) return this._center;
this._center = value;
}
}
Point._center
стал мутабильныйclass Circle extends Point {
radius(value) {
if (!value) return this._radius;
this._radius = value;
}
}
class Square extends Rectangle {
width(value) {
if (!value) return this._width;
this._width = value;
this._height = value;
}
height(value) { return this.width(value); }
}
Square
var sq = new Square();
sq.width(1);
sq.height(2);
expect(sq.width() * sq.height()).to.equal(2);
Наследники не должны имплементировать методы, которыми они не пользуются.
Class: events.EventEmitter// EventEmitter
{
on: function(event, cb) {}
off: function(event, cb) {}
emit: function(event, data) {}
}
Promise/A+ Specification// Promise aka thenable
{
// @param {Function} [onFulfilled]
// @param {Function} [onRejected]
// @returns {Promise}
then: function(onFulfilled, onRejected) {}
}
class IWeapon {
reload() {
throw new AbstractMethodError();
}
fire() {
throw new AbstractMethodError();
}
}
class Rifle extends IWeapon {
reload() {
this._pullHammer();
}
fire() {
this._pullTrigger();
}
}
class Grenade extends IWeapon {
reload() {}
fire() {
this._pullCheck();
this._throw();
}
}
Grenade
имплементирует заглушку reloadclass IProduct {
log(value) {throw new AbstractMethodError();}
addTo(cart) {throw new AbstractMethodError();}
destroy() {throw new AbstractMethodError();}
getLink() {throw new AbstractMethodError();}
updateImage(img) {throw new AbstractMethodError();}
loadImage(img, cb) {throw new AbstractMethodError();}
}
class IButton {
getIcon() {throw new AbstractMethodError();}
getLabel(cart) {throw new AbstractMethodError();}
getUrl() {throw new AbstractMethodError();}
onClick(cb) {throw new AbstractMethodError();}
onLongPress(cb) {throw new AbstractMethodError();}
}
Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции.
Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
class TrackMap {
renderMap() {
return new google.maps.Map(this.el, this.options);
}
addMarker(options) {
new google.maps.Marker(options).setMap(this.map);
}
}
google.maps
google.maps.Map
google.maps.Marker
var MapProvider = {
// @param {Object} options
// @param {Number[]} options.ll
// @param {String} options.title
// @returns {IMarker}
Marker(options) {}
Map(options) {}
}
class TrackMap {
constructor(mapProvider) {
this.api = mapProvider;
}
addMarker(options) {
var marker = new this.api.Marker(options);
marker.addTo(this.map);
}
}
g.maps
*.maps
*.maps
Попытка реиспользовать модуль в новом проекте не должна привести к копипасту половины модулей из старого проекта.
Михаил Давыдов