cacao

javascript Создаем свои события, используя декораторы

Recommended Posts

cacao    13

Декораторы скоро войдут в спецификацию языка, это чудесный и очень гибкий инструмент, позволяющий

  • умело внедрять мета программирование;
  • изменять поведение функции;
  • валидировать ваши данные;
  • элегантный код;
  • и многое другое...

Всецело, рай для программиста!

Но пока, мы можем их использовать без нативных операторов, благодаря функциям высшего порядка.

Данный урок содержит в основном пересказ из оригинальной статьи на ресурсе 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 by cacao
  • Like 2

Share this post


Link to post
Share on other sites
cacao    13
"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 by cacao
  • Like 3

Share this post


Link to post
Share on other sites

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 account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.