cacao Posted January 17, 2017 Share Posted January 17, 2017 (edited) Декораторы скоро войдут в спецификацию языка, это чудесный и очень гибкий инструмент, позволяющий умело внедрять мета программирование; изменять поведение функции; валидировать ваши данные; элегантный код; и многое другое... Всецело, рай для программиста! Но пока, мы можем их использовать без нативных операторов, благодаря функциям высшего порядка. Данный урок содержит в основном пересказ из оригинальной статьи на ресурсе learn javascript, по названию "Декораторы". Данный урок предполагает наличие неплохих знаний в программировании, в javascript. Итак, к коду! Внимание! Версия без комментариев, http://pastebin.com/JZNgHwdY ## метод afterLogin все так находится внутри метода login, фиксану чуть позднее, вся проблема в асинхронности /* Цель - Не допускать юзеров, которые находятся в черном списке */ /* Паттерн декораторы, подробнее - https://learn.javascript.ru/decorators */ /* Имеем обычный метод логина пользователя, куда передаются два параметра - логин и пароль соответственно. Однако, мы захотели навесить на метод login событие, чтобы при login у нас проверялся (условно) IP адрес из черного списка и если все впорядке, проводить логин. Самый очевидный вариант это просто дополнить метод login проверкой вида if ( User.blackList.indexOf( player.ip ) >= 0 ) return false; Однако, предположим что наш класс может быть унаследован и метод login перезаписан по своему или даже нам банально нужно вести какую нибудь статистику под авторизацию пользователя, и записывать попытки входа Не вопрос! Скажем мы, можно все пихнуть в метод login, он правда расбухнет и станет очень не читабельным, однако это реализация вполне годная на жизнь Но опять же, к нашим проблемам, -- метод могут переопределить, а поведение мы все таки хотим оставить так как же быть? На помощь приходят Декораторы! Суть декораторов проста -- мы подмешиваем в исходную функцию новое поведение, мы вольны задавать новое поведение как угодно! Давайте попробуем... Для начала реализация метода декоратора, метод bind Все просто -- метод bind всего лишь возвращает новую функицю и все! Все просто! (Немного теории: в функциональщине когда функция возвращает другую функцию, такая функция называется функцией высшего порядка) */ let mysql = require('mysql') // подключаем либу mysql, npm install mysql , SHA256 = require('crypto-js/sha256') // подключаем либу криптографии, можно стянуть npm install crypto , config = require('./config') // подключаем условный конфиг config.js, который якобы лежит у нас в корне каталога и возвращает объект { config: { } } , connection = mysql.createConnection(config.db) // создаем коннект к бд // описываем класс class User { constructor() { // по дефолту ставим статус авторизации как false this.logged = false // по дефолту ставим попытки входа на 0 this.loginAttempts = 0 // а максимальное количество попыток ставим равное 3 this.maxLoginAttempts = 3 // здесь навешиваем события, соответственно до и после this.login = this.bind (this.login, this.onBeforeLogin, this.onAfterLogin) } // собственно здесь вся магия, мы переиначиваем поведение func так как нам // захочется, добавляя до и после вызов событий // причем, заметьте что afterFunc передается в качестве параметра, ведь // node.js асинхронный, а значит мы должны подождать конца запроса // прежде чем использовать событие после авторизации bind (func, beforeFunc, afterFunc) { return function() { // здесь важно указать именно function() а не () => { }, // так как () => { } не имеет своего arguments и this // валидируем, может ли пользователь приступить к авторизации // параметр this обозначает наш экземпляр класса User let canLogin = beforeFunc.call(this, arguments) // func.call вызывает функцию func с параметрами arguments, afterFunc и передает туда // наш экземпляр объекта User // если canLogin = true, то совершаем попытку входа, иначе возвращаем ничего return canLogin ? func.call(this, arguments, afterFunc) : null } } // условный черный список static get blackList() { return ['127.0.0.1']; } login (player, username, password, callback) { if (this.logged) return true // условный запрос к базе данных connection.query('SELECT * FROM users WHERE username = ?, password = ?',[ username.toString(), SHA256(password.toString()), // хэшируем пароль ], function(err, result) { if (!err) { // ошибок нет, все окей // Запись с такой парой найдена? if (result.length) { this.logged = true return callback(username, this.logged, this.loginAttempts) } else { return callback(username, this.logged, ++this.loginAttempts) } } else { debug(`User::login, ошибка запроса к БД, причина: ${err}`) } }) } // сюда ушли все те же самые параметры что и в login // (т.е. player, username, password, без callback, его мы подключаем позже) // однако здесь их явно не указываем за не надобностью beforeLogin (player) { return this.loginAttempts > this.maxLoginAttempts || User.blackList.indexOf(player.ip) >= 0 ? false : true } afterLogin (username, status, count) { if (status) { // Пишем в какой-нибудь условный дебаг debug(`Пользовател ${username} авторизовался! ${count ? 'Его количество попыток входа = ' + count : ''}`) this.loginAttempts = 0 } else { // Пишем в какой-нибудь условный дебаг debug(`Ошибка авторизации. Количество попыток входа с логином ${username} = ${count.toString()}`) } return status } } статья дорабатывается... вопросы, пожелания, критика Edited January 17, 2017 by cacao 3 Link to comment Share on other sites More sharing options...
vaskidze Posted January 18, 2017 Share Posted January 18, 2017 Есть пример использование данного кода ? Link to comment Share on other sites More sharing options...
cacao Posted January 18, 2017 Author Share Posted January 18, 2017 (edited) "use strict" class Base { constructor(params) { params = params || Base.defaultParams this.name = params.name this.cost = params.cost this.type = params.type this.count = params.count this.use = this.bind (this.use, this.onUse) } bind () { return function (decorate, handler) { decorate.apply(this, [arguments, handler]) } } static control(instance) { if (instance.count <= 0) delete instance } use (player) { throw new Error("Method must be Implemented!") } onUse(player) { this.count-- console.log(`${player.name} только что съел ${this.name}`) Base.control(this) } } class Food extends Base { static get defaultParams() { return { name: "Pizza", cost: 12, type: "food", count: 1 } } use (args, callback) { let [player, ...values] = args if (player.hungry) { player.health = player.health + this.health >= 100 ? 100 : player.health + this.health callback(player) } } } let pizza = new Food(Food.defaultParams) pizza.use(player) Edited January 18, 2017 by cacao 4 Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now