Русский
Русский
English
Статистика
Реклама

Прототип

Экстренная психологическая помощь Prototyping Weekend

05.09.2020 16:05:22 | Автор: admin

#openDevelopment #codeSaveLives
Привет Хабр! Я завершил работу над прототипом платформы, которая объединяет психологов-добровольцев и людей, нуждающихся в экстренной помощи. Это инициатива в ответ на насилие, происходящее в настоящее время в Беларуси и Ливане:
https://brmlab.cz/project/belhack/start

Я попытался запрототипировать веб-приложение для удаленной неотложной психологической помощи. Меня мотивировало к этому проекту то, что сегодня многие тысячи людей оказались в безвыходном положении и подверглись насилию, по моему мнению они нуждаются в поддержке.

Это может быть не только психологическая помощь, но и онлайн-инструкции о том, как поступить с пострадавшим. Пользователь этого приложения - человек, которому немедленно нужна неотложная помощь, и мед-волонтер, кто готов помощь оказать. Это расширение концепции Online Doctor на экстренную помощь.

Презентация механики:

*** Технические подробности ***

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

#googleMeet #googleSpreadSheet #googleAppsScript #googleChromeExtension

Для создания прототипа я использовал технологии и сервисы Google: электронную таблицу, встречу, расширение, apps script.

На странице приложения отображаются психологи, которые готовы оказать помощь прямо сейчас.

Как пациент: Нажав на кнопку, я присоединюсь к встрече, после чего смогу получить поддержку от свободного в данную минуту специалиста - волонтера.

Как психолог: Я готов начать прием людей, нажимаю кнопку Ready to help. Мне нужно нажать кнопку Help in Progress (используя расширения), когда кто-то присоединяется, и статус в таблице этого доктора меняется на 1. Затем строка с врачом исчезает со страницы.

Общение происходит в среде Google Meet.

Буду рад новым людям, которые готовы помочь провести тестирования, отловить жуков и помочь спуску в продакшен.

Подробнее..

Будущее JavaScript классы

22.01.2021 14:05:41 | Автор: admin


Доброго времени суток, друзья!

Сегодня я хочу поговорить с вами о трех предложениях, относящихся к JavaScript-классам, которые находятся на 3 стадии рассмотрения:


Учитывая, что указанные предложения в полной мере соответствуют логике дальнейшего развития классов и используют существующий синтаксис, можно быть уверенным, что они будут стандартизированы без каких-либо серьезных изменений. Об этом также свидетельствует реализация названных фич в современных браузерах.

Вспомним, что такое классы в JavaScript.

По большей части, классы представляют собой так называемый синтаксический сахар (абстракцию или, проще говоря, обертку) для функций-конструкторов. Такие функции используется для реализации паттерна проектирования Конструктор. Данный паттерн, в свою очередь, реализуется (в JavaScript) с помощью модели прототипного наследования (prototypal inheritance). Модель прототипного наследования иногда определяют в качестве самостоятельного паттерна Прототип. Подробнее о паттернах проектирования можно почитать здесь.

Что такое прототип? Это объект, который выступает в роли проекта или схемы (blueprint) для других объектов экземпляров (instances). Конструктор это функция, позволяющая создавать объекты-экземпляры на основе прототипа (класса, суперкласса, абстрактного класса etc.). Процесс передачи свойств и функций от прототипа к экземпляру называется наследованием. Свойства и функции в терминологии классов, обычно, именуются полями и методами, но, де-факто, это одно и тоже.

Как выглядит функция-конструктор?

// обратите внимание на включение строгого режима'use strict'function Counter(initialValue = 0) {  this.count = initialValue  // смотрим на то, что такое this  console.log(this)}

Мы определяем функцию Counter, принимающую параметр initialValue со значением по умолчанию, равным 0. Этот параметр присваивается свойству экземпляра count при инициализации экземпляра. Контекстом this в данном случае является создаваемый (возвращаемый) функцией объект. Для того, чтобы указать JavaScript на вызов не просто функции, но функции-конструктора, необходимо использовать ключевое слово new:

const counter = new Counter() // { count: 0, __proto__: Object }

Как мы видим, функция-конструктор возвращает объект с определенным нами свойством count и прототипом (__proto__) в виде глобального объекта Object, к которому восходят цепочки прототипов почти всех типов (данных) в JavaScript (за исключением объектов без прототипа, создаваемых с помощью Object.create(null)). Поэтому говорят, что в JavaScript все является объектом.

Если вызвать функцию-конструктор без new, то будет выброшено исключение TypeError (ошибка типа), говорящее о том, что свойство 'count' не может быть присвоено undefined:

const counter = Counter() // TypeError: Cannot set property 'count' of undefined// в нестрогом режимеconst counter = Counter() // Window

Это объясняется тем, что значением this внутри функции в строгом режиме является undefined, а в нестрогом глобальный объект Window.

Добавим в функцию-конструктор распределенные (совместно используемые, общие для всех экземпляров) методы по увеличению, уменьшению, сбросу и получению значения счетчика:

Counter.prototype.increment = function () {  this.count += 1  // возвращаем this, чтобы иметь возможность выстраивания цепочки из вызовов методов  return this}Counter.prototype.decrement = function () {  this.count -= 1  return this}Counter.prototype.reset = function () {  this.count = 0  return this}Counter.prototype.getInfo = function () {  console.log(this.count)  return this}

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

Добавление нескольких методов в прототип функции-конструктора можно оптимизировать следующим образом:

;(function () {  this.increment = function () {    this.count += 1    return this  }  this.decrement = function () {    this.count -= 1    return this  }  this.reset = function () {    this.count = 0    return this  }  this.getInfo = function () {    console.log(this.count)    return this  }// привязываем методы к прототипу функции-конструктора// https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Function/call}.call(Counter.prototype))

Или можно сделать еще проще:

// это современный синтаксис, раньше такой возможности не былоObject.assign(Counter.prototype, {  increment() {    this.count += 1    return this  },  decrement() {    this.count -= 1    return this  },  reset() {    this.count = 0    return this  },  getInfo() {    console.log(this.count)    return this  }})

Воспользуемся нашими методами:

counter  .increment()  .increment()  .getInfo() // 2  .decrement()  .getInfo() // 1  .reset()  .getInfo() // 0

Синтаксис класса является более лаконичным:

class _Counter {  constructor(initialValue = 0) {    this.count = initialValue  }  increment() {    this.count += 1    return this  }  decrement() {    this.count -= 1    return this  }  reset() {    this.count = 0    return this  }  getInfo() {    console.log(this.count)    return this  }}const _counter = new _Counter()_counter  .increment()  .increment()  .getInfo() // 2  .decrement()  .getInfo() // 1  .reset()  .getInfo() // 0

Для демонстрации работы механизма наследования в JavaScript рассмотрим более сложный пример. Создадим класс Person и его подкласс SubPerson.

В классе Person определяются свойства firstName (имя), lastName (фамилия) и age (возраст), а также методы getFullName (получение имени и фамилии), getAge (получение возраста) и saySomething (произнесение фразы).

Подкласс SubPerson наследует все свойства и методы Person, а также определяет новые поля lifestyle (образ жизни), skill (навык) и interest (интерес, хобби), а также новые методы getInfo (получение полного имени посредством вызова родительского-унаследованного метода getFullName и образа жизни), getSkill (получение навыка), getLike (получение хобби) и setLike (определение-установка хобби).

Функция-конструктор:

const log = console.logfunction Person({ firstName, lastName, age }) {  this.firstName = firstName  this.lastName = lastName  this.age = age};(function () {  this.getFullName = function () {    log(`Этого человека зовут ${this.firstName} ${this.lastName}`)    return this  }  this.getAge = function () {    log(`Этому человеку ${this.age} лет`)    return this  }  this.saySomething = function (phrase) {    log(`Этот человек говорит: "${phrase}"`)    return this  }}.call(Person.prototype))const person = new Person({  firstName: 'Иван',  lastName: 'Петров',  age: 30})person.getFullName().getAge().saySomething('Привет!')/*  Этого человека зовут Иван Петров  Этому человеку 30 лет  Этот человек говорит: "Привет!"*/function SubPerson({ lifestyle, skill, ...rest }) {  // привязываем конструктор Person к экземпляру SubPerson применительно к наследуемым свойствам  Person.call(this, rest)  this.lifestyle = lifestyle  this.skill = skill  this.interest = null}// делаем прототип Person прототипом SubPersonSubPerson.prototype = Object.create(Person.prototype)// и добавляем в него новые функцииObject.assign(SubPerson.prototype, {  getInfo() {    this.getFullName()    log(`Он ${this.lifestyle}`)    return this  },  getSkill() {    log(`Этот ${this.lifestyle} умеет ${this.skill}`)    return this  },  getLike() {    log(      `Этот ${this.lifestyle} ${        this.interest ? `любит ${this.interest}` : 'ничего не любит'      }`    )    return this  },  setLike(value) {    this.interest = value    return this  }})const developer = new SubPerson({  firstName: 'Петр',  lastName: 'Иванов',  age: 25,  lifestyle: 'разработчик',  skill: 'писать код на JavaScript'})developer  .getInfo()  .getAge()  .saySomething('Программирование - это круто!')  .getSkill()  .getLike()/*  Этого человека зовут Петр Иванов  Он разработчик  Этому человеку 25 лет  Этот человек говорит: "Программирование - это круто!"  Этот разработчик умеет писать код на JavaScript  Этот разработчик ничего не любит*/developer.setLike('делать оригами').getLike()// Этот разработчик любит делать оригами

Класс:

const log = console.logclass _Person {  constructor({ firstName, lastName, age }) {    this.firstName = firstName    this.lastName = lastName    this.age = age  }  getFullName() {    log(`Этого человека зовут ${this.firstName} ${this.lastName}`)    return this  }  getAge() {    log(`Этому человеку ${this.age} лет`)    return this  }  saySomething(phrase) {    log(`Этот человек говорит: "${phrase}"`)    return this  }}const _person = new Person({  firstName: 'Иван',  lastName: 'Петров',  age: 30})_person.getFullName().getAge().saySomething('Привет!')/*  Этого человека зовут Иван Петров  Этому человеку 30 лет  Этот человек говорит: "Привет!"*/class _SubPerson extends _Person {  constructor({ lifestyle, skill /*, ...rest*/ }) {    // вызов super() почти аналогичен вызову Person.call(this, rest)    // super(rest)    super()    this.lifestyle = lifestyle    this.skill = skill    this.interest = null  }  getInfo() {    // super.getFullName()    this.getFullName()    log(`Он ${this.lifestyle}`)    return this  }  getSkill() {    log(`Этот ${this.lifestyle} умеет ${this.skill}`)    return this  }  get like() {    log(      `Этот ${this.lifestyle} ${        this.interest ? `любит ${this.interest}` : 'ничего не любит'      }`    )  }  set like(value) {    this.interest = value  }}const _developer = new SubPerson({  firstName: 'Петр',  lastName: 'Иванов',  age: 25,  lifestyle: 'разработчик',  skill: 'писать код на JavaScript'})_developer  .getInfo()  .getAge()  .saySomething('Программирование - это круто!')  .getSkill().like/*  Этого человека зовут Петр Иванов  Он разработчик  Этому человеку 25 лет  Этот человек говорит: "Программирование - это круто!"  Этот разработчик умеет писать код на JavaScript  Этот разработчик ничего не любит*/developer.like = 'делать оригами'developer.like// Этот разработчик любит делать оригами

Думаю, тут все понятно. Двигаемся дальше.

Основной проблемой наследования в JavaScript было и остается отсутствие встроенной возможности множественного наследования, т.е. возможности подкласса наследовать свойства и методы нескольких классов одновременно. Разумеется, поскольку в JavaScript возможно все, мы можем создать имитацию множественного наследования, например, с помощью такого миксина:

// https://www.typescriptlang.org/docs/handbook/mixins.htmlfunction applyMixins(derivedCtor, constructors) {  constructors.forEach((baseCtor) => {    Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {      Object.defineProperty(        derivedCtor.prototype,        name,        Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||          Object.create(null)      )    })  })}class A {  sayHi() {    console.log(`${this.name} говорит: "Привет!"`)  }  sameName() {    console.log('Метод класса А')  }}class B {  sayBye() {    console.log(`${this.name} говорит: "Пока!"`)  }  sameName() {    console.log('Метод класса B')  }}class C {  name = 'Иван'}applyMixins(C, [A, B])const c = new C()// вызываем метод, унаследованный от класса Ac.sayHi() // Иван говорит: "Привет!"// вызываем метод, унаследованный от класса Bc.sayBye() // Иван говорит: "Пока!"// одноименный последующий метод перезаписывает предыдущийc.sameName() // Метод класса B

Однако, это не является полноценным решением и представляет собой всего лишь хак для того, чтобы втиснуть JavaScript в рамки объектно-ориентированного программирования.

Перейдем непосредственно к новшествам, предлагаемым обозначенными в начале статьи предложениями.

На сегодняшний день, учитывая стандартизированные возможности, синтаксис класса выглядит следующим образом:

const log = console.logclass C {  constructor() {    this.publicInstanceField = 'Публичное поле экземпляра'    this.#privateInstanceField = 'Приватное поле экземпляра'  }  publicInstanceMethod() {    log('Публичный метод экземпляра')  }  // получаем значение приватного поля экземпляра  getPrivateInstanceField() {    log(this.#privateInstanceField)  }  static publicClassMethod() {    log('Публичный метод класса')  }}const c = new C()console.log(c.publicInstanceField) // Публичное поле экземпляра// при попытке прямого доступа к приватной переменной выбрасывается исключение// console.log(c.#privateInstanceField) // SyntaxError: Private field '#privateInstanceField' must be declared in an enclosing classc.getPrivateInstanceField() // Приватное поле экземпляраc.publicInstanceMethod() // Публичный метод экземляраC.publicClassMethod() // Публичный метод класса

Получается, что мы имеем возможность определять публичные и приватные поля и публичные методы экземпляров, а также публичные методы класса, но не можем определять приватные методы экземпляров, а также публичные и приватные поля класса. Ну, на самом деле возможность определить публичное поле класса все же имеется:

C.publicClassField = 'Публичное поле класса'console.log(C.publicClassField) // Публичное поле класса

Но, согласитесь, что выглядит это не очень хорошо. Складывается впечатление, что мы вернулись к работе с прототипами.

Первое предложение позволяет определять публичные и приватные поля экземпляра без использования конструктора:

publicInstanceField = 'Публичное поле экземпляра'#privateInstanceField = 'Приватное поле экземпляра'

Второе предложение позволяет определять приватные методы экземпляра:

#privateInstanceMethod() {  log('Приватный метод экземпляра')}// вызываем приватный метод экземпляраgetPrivateInstanceMethod() {  this.#privateInstanceMethod()}

И, наконец, третье предложение позволяет определять публичные и приватные (статические) поля, а также приватные (статические) методы класса:

static publicClassField = 'Публичное поле класса'static #privateClassField = 'Приватное поле класса'static #privateClassMethod() {  log('Приватный метод класса')}// получаем значение приватного поле классаstatic getPrivateClassField() {  log(C.#privateClassField)}// вызываем приватный метод классаstatic getPrivateClassMethod() {  C.#privateClassMethod()}

Вот как будет выглядеть (в действительности, уже выглядит) полный комплект:

const log = console.logclass C {  // class field declarations  // https://github.com/tc39/proposal-class-fields  publicInstanceField = 'Публичное поле экземпляра'  #privateInstanceField = 'Приватное поле экземпляра'  publicInstanceMethod() {    log('Публичный метод экземляра')  }  // private methods and getter/setters  // https://github.com/tc39/proposal-private-methods  #privateInstanceMethod() {    log('Приватный метод экземпляра')  }  // получаем значение приватного поля экземпляра  getPrivateInstanceField() {    log(this.#privateInstanceField)  }  // вызываем приватный метод экземпляра  getPrivateInstanceMethod() {    this.#privateInstanceMethod()  }  // static class features  // https://github.com/tc39/proposal-static-class-features  static publicClassField = 'Публичное поле класса'  static #privateClassField = 'Приватное поле класса'  static publicClassMethod() {    log('Публичный метод класса')  }  static #privateClassMethod() {    log('Приватный метод класса')  }  // получаем значение приватного поля класса  static getPrivateClassField() {    log(C.#privateClassField)  }  // вызываем приватный метод класса  static getPrivateClassMethod() {    C.#privateClassMethod()  }  // пытаемся получить публичное и приватное поля класса из экземпляра  getPublicAndPrivateClassFieldsFromInstance() {    log(C.publicClassField)    log(C.#privateClassField)  }  // пытаемся получить публичное и приватное поля экземпляра из класса  static getPublicAndPrivateInstanceFieldsFromClass() {    log(this.publicInstanceField)    log(this.#privateInstanceField)  }}const c = new C()console.log(c.publicInstanceField) // Публичное поле экземпляра// при попытке прямого доступа к значению приватного поля экземпляра выбрасывается исключение// console.log(c.#privateInstanceField) // SyntaxError: Private field '#privateInstanceField' must be declared in an enclosing classc.getPrivateInstanceField() // Приватное поле экземпляраc.publicInstanceMethod() // Публичный метод экземляра// попытка прямого доступа к приватному методу экземпляра также заканчивается ошибкой// c.#privateInstanceMethod() // Errorc.getPrivateInstanceMethod() // Приватный метод экземпляраconsole.log(C.publicClassField) // Публичное поле класса// console.log(C.#privateClassField) // ErrorC.getPrivateClassField() // Приватное поле классаC.publicClassMethod() // Публичный метод класса// C.#privateClassMethod() // ErrorC.getPrivateClassMethod() // Приватный метод классаc.getPublicAndPrivateClassFieldsFromInstance()// Публичное поле класса// Приватное поле класса// публичное и приватное поля экземпляра недоступны из класса,// поскольку на момент доступа к ним экземпляра не существует// C.getPublicAndPrivateInstanceFieldsFromClass()// undefined// TypeError: Cannot read private member #privateInstanceField from an object whose class did not declare it

Все бы ничего, вот только существует один интересный нюанс: приватные поля не наследуются. В TypeScript и других языках программирования существует особое свойство, обычно именуемое protected (защищенное), к которому нельзя получить доступ напрямую, но которое, вместе с тем, может наследоваться наряду с публичными свойствами.

Стоит отметить, что слова private, public и protected в JavaScript являются зарезервированными. При попытке их использования в строгом режиме выбрасывается исключение:

const private = '' // SyntaxError: Unexpected strict mode reserved wordconst public = '' // Errorconst protected = '' // Error

Поэтому надежда на реализацию защищенных полей класса в отдаленной перспективе остается.

Обращаю ваше внимание, что техника инкапсуляции переменных, т.е. их защиты от доступа извне, стара, как сам JavaScript. До стандартизации приватных полей класса для скрытия переменных, обычно, использовались замыкания, а также паттерны проектирования Фабрика и Модуль. Рассмотрим эти паттерны на примере корзины для товаров.

Модуль:

const products = [  {    id: '1',    title: 'Хлеб',    price: 50  },  {    id: '2',    title: 'Масло',    price: 150  },  {    id: '3',    title: 'Молоко',    price: 100  }]const cartModule = (() => {  let cart = []  function getProductCount() {    return cart.length  }  function getTotalPrice() {    return cart.reduce((total, { price }) => (total += price), 0)  }  return {    addProducts(products) {      products.forEach((product) => {        cart.push(product)      })    },    removeProduct(obj) {      for (const key in obj) {        cart = cart.filter((prod) => prod[key] !== obj[key])      }    },    getInfo() {      console.log(        `В корзине ${getProductCount()} товар(а) на ${          getProductCount() > 1 ? 'общую ' : ''        }сумму ${getTotalPrice()} рублей`      )    }  }})()// модуль представляет собой обычный объект с методамиconsole.log(cartModule) // { addProducts: , removeProduct: , getInfo:  }// добавляем товары в корзинуcartModule.addProducts(products)cartModule.getInfo()// В корзине 3 товар(а) на общую сумму 300 рублей// удаляем товар с идентификатором 2cartModule.removeProduct({ id: '2' })cartModule.getInfo()// В корзине 2 товар(а) на общую сумму 150 рублей// пытаемся получить доступ к инкапсулированому полю и методуconsole.log(cartModule.cart) // undefined// cartModule.getProductCount() // TypeError: cartModule.getProductCount is not a function

Фабрика:

function cartFactory() {  let cart = []  function getProductCount() {    return cart.length  }  function getTotalPrice() {    return cart.reduce((total, { price }) => (total += price), 0)  }  return {    addProducts(products) {      products.forEach((product) => {        cart.push(product)      })    },    removeProduct(obj) {      for (const key in obj) {        cart = cart.filter((prod) => prod[key] !== obj[key])      }    },    getInfo() {      console.log(        `В корзине ${getProductCount()} товар(а) на ${          getProductCount() > 1 ? 'общую ' : ''        }сумму ${getTotalPrice()} рублей`      )    }  }}const cart = cartFactory()cart.addProducts(products)cart.getInfo()// В корзине 3 товар(а) на общую сумму 300 рублейcart.removeProduct({ title: 'Молоко' })cart.getInfo()// В корзине 2 товар(а) на сумму 200 рублейconsole.log(cart.cart) // undefined// cart.getProductCount() // TypeError: cart.getProductCount is not a function

Класс:

class Cart {  #cart = []  #getProductCount() {    return this.#cart.length  }  #getTotalPrice() {    return this.#cart.reduce((total, { price }) => (total += price), 0)  }  addProducts(products) {    this.#cart.push(...products)  }  removeProduct(obj) {    for (const key in obj) {      this.#cart = this.#cart.filter((prod) => prod[key] !== obj[key])    }  }  getInfo() {    console.log(      `В корзине ${this.#getProductCount()} товар(а) на ${        this.#getProductCount() > 1 ? 'общую ' : ''      }сумму ${this.#getTotalPrice()} рублей`    )  }}const _cart = new Cart()_cart.addProducts(products)_cart.getInfo()// В корзине 3 товар(а) на общую сумму 300 рублей_cart.removeProduct({ id: '1', price: 100 })_cart.getInfo()// В корзине 1 товар(а) на общую сумму 150 рублейconsole.log(_cart.cart) // undefined// console.log(_cart.#cart) // SyntaxError: Private field '#cart' must be declared in an enclosing class// _cart.getTotalPrice() // TypeError: cart.getTotalPrice is not a function// _cart.#getTotalPrice() // Error

Как мы видим, паттерны Модуль и Фабрика ничем не уступают классу, разве что, синтаксис последнего является немного более кратким, но позволяют полностью отказаться от использования ключевого слова this, основная проблема которого заключается в потере контекста при использовании в стрелочных функциях и обработчиках событий. Это обуславливает необходимость их привязки к экземпляру в конструкторе.

Напоследок, рассмотрим пример создания веб-компонента кнопки с помощью синтаксиса класса (из текста одного из предложений с небольшой модификацией).

Наш компонент расширяет встроенный HTML-элемент кнопки, добавляя в его функционал следующее: при нажатии кнопки левой кнопкой мыши, значение счетчика увеличивается на 1, при нажатии кнопки правой кнопкой мыши, значение счетчика уменьшается на 1. При этом мы можем использовать любое количество кнопок с собственным контекстом и состоянием:

// https://developer.mozilla.org/ru/docs/Web/Web_Componentsclass Counter extends HTMLButtonElement {  #xValue = 0  get #x() {    return this.#xValue  }  set #x(value) {    this.#xValue = value    // привязываем к экземпляру метод рендеринга    // https://developer.mozilla.org/ru/docs/DOM/window.requestAnimationFrame    // https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Function/bind    requestAnimationFrame(this.#render.bind(this))  }  #increment() {    this.#x++  }  #decrement(e) {    // отменяем вызов контекстного меню    e.preventDefault()    this.#x--  }  constructor() {    super()    // привязываем к экземпляру обработчики событий    this.onclick = this.#increment.bind(this)    this.oncontextmenu = this.#decrement.bind(this)  }  // монтирование в терминологии React/Vue или, проще говоря, встраивание элемента в DOM  connectedCallback() {    this.#render()  }  #render() {    // для упрощения будем считать, что 0 - это положительное число    this.textContent = `${this.#x} - ${      this.#x < 0 ? 'отрицательное' : 'положительное'    } ${this.#x & 1 ? 'нечетное' : 'четное'} число`  }}// регистрация веб-компонентаcustomElements.define('btn-counter', Counter, { extends: 'button' })

Результат:



Представляется, что, с одной стороны, классы не получат повсеместного признания в сообществе разработчиков до решения, назовем ее так, проблемы this. Не случайно после продолжительного использования классов (классовых компонентов), команда React отказалась от них в пользу функций (хуков). Похожая тенденция наблюдается в Vue Composition API. С другой стороны, многие причастные к разработке ECMAScript, инженеры из Google, занимающиеся веб-компонентами, а также команда TypeScript активно работают над развитием объектно-ориентированной составляющей JavaScript, поэтому сбрасывать классы со счетов в ближайшие несколько лет точно не стоит.

Весь код, приводимый в статье, находится здесь.

Дополнительно про объектно-ориентированный JavaScript можно почитать здесь.

Статья получилась несколько длиннее, чем я планировал, но, надеюсь, вам было интересно. Благодарю за внимание и хорошего дня.
Подробнее..

Чем хорош сайт на Тильде? И почему не надо лезть в дорогостоящие решения

03.03.2021 20:06:54 | Автор: admin

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

Сразу отметим, что данный материал ни в коем случае не является рекламным, а представляет из себя анализ Тильды, как решение для бизнеса.

Если говорить коротко - это конструктор сайтов, который приобрел большую популярность в последние годы на территории России и стран СНГ в частности. Конечно, основной офер компании заключается в том, что любой новичок никогда до этого не имеющий опыта в web- разработке и в целом digital, сможет сделать для себя или своего небольшого начинания посадочную страницу. Казалось бы, причем тут вообще могут быть агентства или студии? Давайте разбираться.

Все опять исходит из профессионализма конкретных участников разработки, будь то дизайнер или маркетолог. В любом деле необходим опыт и сноровка, чтобы хорошо делать свое дело. Человек, который ни разу не работал со смыслами, не знает, что такое прототип и в целом не имеет представления, что такое дизайн, и из чего он состоит - не сможет справиться с поставленной задачей. Да, конечно Tilda предоставляет в своем ассортименте решения в виде готовых блоков из которых можно собрать небольшой MVP (минимальный жизнеспособный продукт), но вряд ли такой продукт сможет потягаться со средними сайтами в той нише для которой этот сайт предназначается. Вот именно в таких случаях на помощь приходят команды со своей экспертизой в решениях данных задач. Казалось бы, за что некоторые команды берут по 200.000 за разработку на условной бесплатной платформе?

Давайте сначала определимся с типажом заказчика, который обращается за данной услугой. Возьмем за основу новичка, который ранее не имел опыта в создании собственного сайта, не знает о всех тонкостях и нюансах. В свою очередь максимум, что ранее ему приходилось слышать о сайтах - нужен человек, разбирающийся в компьютерах и что там есть какой-то пресловутый код. В большинстве своем именно по этой причине в дальнейшем заказчик будет противиться и стараться нивелировать любые попытки сделать сайт ни на коде. Так как для него это почему-то очень важно.

По факту в более чем 50% случаев от всех задач бизнеса в медиа пространстве, ваш сайт не нуждается в том, чтобы он был разработан кодом. Не обязательно ставить такие условия где необходимо будет привлекать верстальщика или программиста. В таких проектах обычно нуждается средний или крупный бизнес, так как к тому времени у компании сформировалось представление и сложные многоуровневые задачи, которые решаются только таким же сложными решениями.

Если ваша задача начинается со слов:
Чтобы продавать или Нужна презентация - вам не нужен код.

Tilda в свою очередь тоже не является панацеей и не возведена в абсолютное значение, можно пользоваться другими решениями похожими на Тильду.

Что чаще всего приходится слышать про Тильду?
1. Тильда? - ну это как-то несерьезно для компании
Во-первых, это вполне себе компактное и быстрое решение которое позволит незатратно для компании реализовать задуманное. В среднем сайт на Тильде стоит в 2 раза дешевле чем на CMS. Пользователю абсолютно неважно на чем разработан сайт, он пришел за конкретным товаром или услугой. Имеет смысл делать упор на предложение и на сервис. Сайт это всего лишь один из инструментов в вашем бизнесе.

Во-вторых, скорее всего меня могут закидать тухлыми помидорами, но серьезная компания не будет стараться выгрызать серьезный сайт за условные 20.000 рублей. Тут 2 пути: или компания не такая серьезная, или же у директора нет понимания, что такое сайт и для чего он нужен.

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

Очень часто уровень брифинга коррелирует с качеством самого конвейера, чтобы убедиться в этом можете попробовать обсудить проект с фрилансером, затем с какой-нибудь студией. Спойлер - фрилансер не задаст вам и половины вопросов, на которые вам придется ответить в студии, фрилансеру попросту нечего делать с этой информацией.

Отсюда вы сразу сможете понять профессионализм человека с которым обсуждаете будущий сайт еще на этапе брифинга. И насторожиться в случае если вам не задали ни одного вопроса относящегося к вашим пожеланиям, но почему-то сразу назвали тильду.

Тогда скорее всего вы общаетесь с командой, которая работает только на этой платформе или же с непрофессионалом.

3. Да нет, Тильда - это как-то слишком просто, там не сделать хороший дизайн
Отвечая коротко - сделать. Такое представление сложилось из-за того, что данная платформа создавалась ни для дизайнеров, а в первую очередь для обычных пользователей. Поэтому встретить зачастую похожие сайты на Тильде довольно просто, так как стандартный набор библиотек весьма ограничен. Но во всем остальном дизайнер сможет разработать уникальный дизайн конкретно под вас.

Теперь, когда мы прониклись всей серьезностью данного решения. Сформулируем реальную ситуацию и ряд задач с которыми пришлось столкнуться заказчикам.

Рассмотрим параллельно 2 ситуации, которые могут показаться разными, но по факту объединены одним и тем же.
1. Заказчик не имеет серьезного бюджет, но ему срочно необходим небольшой сайт для мероприятия, которое стартует уже в конце недели.
2. Заказчик имеет серьезный бюджет, но у него отсутствуют амбициозные задачи и в целом планы на будущий сайт. Сроки не превышают 14 дней, но для простоты понимания давайте приведем также к 7 дням.

Задача
По факту перед нами стоит задача, как сделать симпатичный MVP-проект, в срок не превышающий 7 дней.

Да, давайте здесь не будем заострять внимание и формировать представление, что вся web-разработка такая же быстрая и не затратная. В данной ситуации разбираются частные случаи, чтобы показать, что даже казалось бы в безвыходной ситуации есть решение

День 1. Подбор референсов и обсуждение проекта
Для экономии времени и ресурсов приступаем к аналитике, но акцентируем внимание только на самых важных моментах, а именно:

  • какова будет общая концепция продукта;

  • какие есть конкуренты и что они из себя представляют;

  • Что нравится целевой аудитории;

  • что из референсов может лучше всего подойти.

Конец дня ознаменовывается обсуждением выбранных решений с заказчиками. На каждый из этапом тратим примерно по 2 часа.

День 2. Прототипирование
Так как у нас выделен только 1 день на прототип - прибегаем к быстрому решению при помощи Figma. В рамках этого сервиса есть уже готовые ui киты, другими словами блоки, которые можно компоновать в дальнейшем как угодно. Опираемся на те примеры сайтов, которые утвердили с заказчиками на предыдущем этапе и на основе их логики / структуры - переносим все это на наш прототип в Figma. В завершении идем презентовать и защищать структуру перед заказчиками.

День 3-4. Дизайн
На данном этапе делаем акцент, выделяя под него 2 суток. Так как он является самым основным в рамках работы на Тильде. Определяемся с 3 наиболее интересными и продуманными работами в данной нише, предварительно все это согласовав с заказчиками. Первый день занимает подбор и поиск будущих элементов сайта, а именно:

  • Иконки

  • Изображения

  • иные визуальные элементы, которые нельзя отнести к чему-то конкретному, но от этого они не становятся менее важными

Все должно быть объединено общей идей, сочетаться между собой, что также отсылает нас к проработке первичной логики по которой уже идет дальнейший отбор элементов. На чем первый день удачно заканчивается. Собрав бэкграунд элементов, вспоминаем о тех исходных работах, которые мы взяли, как ориентир. После чего объединяем удачные идеи этих сайтов с теми элементами, что мы нашли, находясь в свободном плавании на ресурсах для дизайнеров.

День 5. Верстка
Верстка на Тильде представляет из себя работу в zero-blockах, что значительно упрощает нашу задачу. Другими словами, если вам когда-то приходилось сталкиваться с Powerpoint/Photoshop и тд то вы без труда сможете представить сложность при работе с данным инструментом. Как правило все завязано на интуитивно понятном интерфейсе и функционале. В целом вся верстка - это своеобразный конструктор где единственное, что остается делать это переносить элементы с прототипа и двигать их в соответствии с дизайном. Но не стоит забывать, что некоторый пулл-задач не получится решить при помощи zero-blockов, что отсылает нас обратиться за помощью к верстальщику для добавления сложного элемента на сайт. Как правило такие задачи составляют менее 5% от общего числа.

День 6. Подключение домена
Одним из заключительных этапов - подключение домена. Долго не раздумывая, идем на любой из популярных хостингов-провайдеров (reg.ru,Timeweb.comи др.) Указываем DNS сервера Тильды, обновляем всю информацию и жмем подключить домен. Весь этот процесс заканчивается проставлением галочек и индексированием на новый домен. По сути основная работа на этом заканчивается. 6 и 7 день можно было бы объединить в один, но зачастую приходится долго ждать обратной связи от провайдеров, срок ожидания которых может составлять до 1 дня.

День 7. Видео инструкции и обучение заказчика
Не забываем, что каждый должен заниматься своим делом. Дизайнер - рисовать, предприниматель - развивать свое дело. Исходя из этой логики стараемся экономить время заказчиков и не заставляем самостоятельно разбираться в том, что им возможно даже не нужно.

Для этого записываем видеоинструкции:

  • как добавлять контент;

  • Как редактировать текст и менять изображения;

  • Как пользоваться панелью администратора

Где они уже самостоятельно сможет все настроить, повторяя шаги из видео.

Вывод
Тильда - это отличный инструмент для вашего бизнеса если вы:

  • только начинаете;

  • самозанятые или у вас небольшой стартап, когда нет уверенности в бизнес-моделе;

  • когда все, что требуется от сайта - это продавать или демонстрировать портфолио.

Экономия на более чем половине команды разработчиков, отсекая программиста и верстальщика. Используя при этом 2-3 пары рук квалифицированных специалистов, позволяет получить продукт за весьма скромный бюджет, досягаемый для многих, кто развивает свое дело.

Подробнее..

Что нам стоит дом построить? (часть 2)

21.06.2021 12:17:59 | Автор: admin

Привет, Хабр. В прошлой статье я рассказал о начальном анализе предметной области и базовом проектировании нашей новой ECM-системы. Теперь я расскажу о первой практической задаче, которую мы решили. А именно - о выборе способа организации структуры хранилища бизнес-атрибутов объектов.

Напомню, что в результате анализа мы пришли к следующей структуре объекта:

Системные и интеграционные поля содержат техническую информацию и редко подвержены модификациям. А вот бизнес-поля - искомые атрибуты объектов - могут и будут различаться для разных типов документов, которых у нас на входе предполагается около 1000. Проанализировав возможность повторного использования полей, мы пришли к выводу, что у разных типов документов может быть не более 15% общих свойств. И при этом всё равно придется скрепя сердце согласиться на неполное соответствие названия и содержания.

Таким образом, есть большое количество сущностей с разнообразным атрибутивным составом, который нужно хранить, тратя как можно меньше ресурсов, и расширять который хотелось бы также с минимальными усилиями.

Какие есть варианты?

Любые концепции хранения, которые будем рассматривать подразумевают использование промышленных баз данных, организацию хранилища в виде таблиц и связей между ними. Сразу можно выделить два наиболее часто используемых принципа организации атрибутивного хранилища.

Первый, условно классический, заключается в том, что структура представляется в виде набора таблиц, в каждой из которых данные сохраняются в колонках, имеющих четко заданный тип данных, размерность, ограничения и проверки. Общие колонки и некий глобальный идентификатор выделяются в одной корневой таблицу - базовую для всех остальных, что позволяет гибко связывать между собой объекты разных типов. На эту таблицу ссылаются расширяющие, которые содержат в себе поля, уникальные для каждой сущности.

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

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

А впоследствии могут начать возникать проблемы с восприятием. Если поля данных добавляются в одну общую таблицу, то растет размер впустую выделяемых ячеек. Если для каждой сущности добавляется отдельная таблица со специфичными для нее данными, то со временем структура хранилища начинает напоминать примерно такой "одуванчик".

Именно с этим я столкнулся на двух прошлых (не самых старых) проектах со схожей декомпозицией объектов, разработчики которых выбрали именно такой путь организации данных.

Также стоит упомянуть про еще один способ организации данных, с которым я сталкивался. Он используется не в пример реже и больше напоминает "велосипед" - назовем его вертикальный. В хранилище создается таблица, которая содержит, условно, название поля, данные в нем, тип данных и связь с объектом. Возможно создание нескольких таких таблиц для разных типов данных или несколько колонок в одной таблице. Далее, при извлечении такие данные трансформируются из списка в плоский набор полей. Понимаете, почему я упомянул про велосипед?

А что у нас?

Второй вариант - это использование специализированных документоориентированных (или документных, как больше нравится) баз данных, реализующих NoSQL-подход к хранению и обработкенеструктурированной или слабоструктурированной информации. Наиболее часто данные хранятся в виде JSON объектов, но с предоставлением производителями СУБД инструментария для доступа к данным внутри этих структур.

У такого подхода, применительно к проектируемой системе, можно выделить несколько плюсов:

  • можем управлять структурой объектов без программирования, в том числе в реальном времени, путем администрирования. Администратор сможет зайти в некий интерфейс справочника полей и задать новые.

  • ограничиваем ситуации случайного воздействия на данные - модифицировать json объект гораздо сложнее, чем данные в колонке.

  • проще описывать объекты в коде - иногда можно вообще не описывать структуру документа в коде, а работать прямо с полями в JSON.

Но есть и минусы:

  • невозможно нативно реализовать проверки данных при размещении в хранилище.

  • валидацию данных придется проводить в коде.

  • невозможно организовать внутреннюю связность подчиненных данных, например, справочников.

Минусы, касающиеся необходимости дополнительной работы с данными в коде не так уж серьезны. Проблема со связностью решается организацией таблицы, в которой к любому объекту можно привязать любой другой. Правила связывания при этом должны также быть описаны в коде обработки данных. Плюсы же дают несравнимо больший потенциал для развития системы.

Чтобы продемонстрировать применимость такого подхода, мы решили сравнить, насколько оба варианта будут отличаться друг от друга по производительности, расширяемости и объемам хранилища.

Делаем прототип

Возьмем гипотезу, что NoSQL-подход для нашей системы применим как минимум не хуже, чем классический. Для ее проверки создадим прототип, в рамках которого реализуем оба подхода. В качестве СУБД возьмем Postgre, который уже давно умеет хорошо работать с JSON полями.

Создадим следующие таблицы:

Для описания объектов в табличном виде:

  • r_objects, базовые данные по объектам: тип, дата создания и ссылка на хранилище атрибутов.

  • r_attributes. атрибуты, во всех возможные колонки для всех объектов. В идеале конечно же хранилища атрибутов для разных объектов лучше разбить по разным таблицам, но для прототипа это не критично.

Для описания объектов в виде JSON:

  • objects. Данные по объектам, где в поле data формата jsonb хранятся искомые атрибуты.

Остальные таблицы - это различные вспомогательные хранилища.

Реализуем в коде 5 разных типов объектов, для каждого из них описав программную структуру, механизм извлечения данных из хранилища и наполнения этими данными самих объектов.

Методика тестирования

Для тестирования обоих подходов хранения данных используем следующие методы:

  • добавление данных по объекту. Критерий успешности: объект с данными появился в хранилище, метод вернул в ответе его идентификатор.

  • обновление данных по объекту. Критерий успешности: данные в хранилище были изменены, метод вернул отметку об успехе. Удаление данных в системе не предусматривается, поэтому удаление объекта является операцией, аналогичной обновлению.

  • извлечение данных по объекту. Критерий успешности: объект с данными возвращен в ответе на запрос. Извлечение объекта происходит по конкретному идентификатору, по критериям поиска и постранично (пагинация).

Генерация запросов будет происходить в 20 параллельных потоков по 50 запросов в каждом потоке. Для того, чтобы тестирование было действительно показательным с точки зрения производительности, предварительно наполним базу 200 млн. объектов.

Тестирование показало следующие результаты:

График по тестированию табличного хранилищаГрафик по тестированию табличного хранилищаГрафик по тестированию NoSQL-хранилищаГрафик по тестированию NoSQL-хранилища

Первая (высокая) часть графика - это получение объектов по случайной странице - пагинация. Здесь в обоих случаях пришлось применить небольшой трюк - так как Postgres не агрегирует точное число строк в таблице, то узнать, сколько всего записей на объеме данных теста простым count - это долго, и для получения количества записей пришлось брать статистику данных по таблице. Также время получения данных на страницах свыше 10000-й неприлично велико, поэтому верхняя планка для получения случайного номера страницы была установлена в 10000. Учитывая специфику нашей системы, получение пагинированных данных не будет частой операцией, и поэтому такое извлечение данных применяется исключительно в целях тестирования.

Вторая (средняя) часть графика - вставка или обновление данных.

Третья (низкая) часть графика - получение данных по случайному идентификатору.

В случае табличной структуры данных - линий на графике больше, потому что с точки зрения программной структуры, получение данных для каждого типа объекта идет по разным методам, тогда как для NoSQL - по одинаковым.

В данном тесте связность данных не учитывается, потому что она организовывается одинаково для обоих случаев.

Результаты тестов на 40000 запросов приведу в виде таблицы:

Табличная

NoSQL

Объем хранилища

74

66

Среднее количество операций в секунду

970

1080

Время тестирования, секунды

42

37

Количество запросов

40000

40000

Отмечу, что в течение месяца после реализации прототипа проводилось еще несколько тестирований по схожей методике, но с разными показателями нагрузки, показавших схожие результаты.

Что получилось?

В итоге, можно с уверенностью сказать, что для нашей системы NoSQL-подход к хранению данных оказался вполне подходящим. Даже с учетом потенциальных минусов, которые можно решить на этапе разработки серверной части, можно сказать, что он дает лучшие показатели по сравнению с табличным хранением. А плюсы в виде возможности настройки структуры объекта на лету, позволяют высвободить ресурсы от рутинного расширения объектов в сторону разработки новой функциональности.

Подробнее..

Попытка использовать современный C и паттерны проектирования для программирования микроконтроллеров

31.01.2021 18:12:32 | Автор: admin
Всем привет!

Проблема использования С++ в микроконтроллерах терзала меня довольно долгое время. Дело было в том, что я искренне не понимал, как этот объектно ориентированный язык может быть применим к встраиваем системам. Я имею ввиду, как выделять классы и на базе чего составлять объекты, то есть как именно применять этот язык правильно. Спустя некоторое время и прочтения n-ого количества литературы, я пришёл к кое каким результатам, о чем и хочу поведать в этой статье. Имеют ли какую либо ценность эти результаты или нет остается на суд читателя. Мне будет очень интересно почитать критику к моему подходу, чтобы наконец ответить себе на вопрос: Как же правильно использовать C++ при программировании микроконтроллеров?.

Предупреждаю, в статье будет много исходного кода.

В этой статье, я, на примере использования USART в МК stm32 для связи с esp8266 постараюсь изложить свой подход и его основные преимущества. Начнем с того, что главное преимущество использование C++ для меня это возможность сделать аппаратную развязку, т.е. сделать использование модулей верхнего уровня независимым от аппаратной платформы. Это будет вытекать в то, что система станет легко модифицирована при каких либо изменениях. Для этого я выделил три уровня абстракции системы:

  1. HW_USART аппаратный уровень, зависит от платформы
  2. MW_USART средний уровень, служит для развязки первого и третьего уровней
  3. APP_ESP8266 уровень приложения, ничего не знает о МК

HW_USART


Самый примитивный уровень. Я использовал камень stm32f411, USART 2, также выполнил поддержку DMA. Интерфейс реализован в виде всего трех функций: инициализировать, отправить, получить.

Функция инициализации выглядит следующим образом:

bool usart2_init(uint32_t baud_rate){  bool res = false;    /*-------------GPIOA Enable, PA2-TX/PA3-RX ------------*/  BIT_BAND_PER(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN) = true;    /*----------GPIOA set-------------*/  GPIOA->MODER |= (GPIO_MODER_MODER2_1 | GPIO_MODER_MODER3_1);  GPIOA->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR2 | GPIO_OSPEEDER_OSPEEDR3);  constexpr uint32_t USART_AF_TX = (7 << 8);  constexpr uint32_t USART_AF_RX = (7 << 12);  GPIOA->AFR[0] |= (USART_AF_TX | USART_AF_RX);            /*!---------------USART2 Enable------------>!*/  BIT_BAND_PER(RCC->APB1ENR, RCC_APB1ENR_USART2EN) = true;    /*-------------USART CONFIG------------*/  USART2->CR3 |= (USART_CR3_DMAT | USART_CR3_DMAR);  USART2->CR1 |= (USART_CR1_TE | USART_CR1_RE | USART_CR1_UE);  USART2->BRR = (24000000UL + (baud_rate >> 1))/baud_rate;      //Current clocking for APB1    /*-------------DMA for USART Enable------------*/     BIT_BAND_PER(RCC->AHB1ENR, RCC_AHB1ENR_DMA1EN) = true;    /*-----------------Transmit DMA--------------------*/  DMA1_Stream6->PAR = reinterpret_cast<uint32_t>(&(USART2->DR));  DMA1_Stream6->M0AR = reinterpret_cast<uint32_t>(&(usart2_buf.tx));  DMA1_Stream6->CR = (DMA_SxCR_CHSEL_2| DMA_SxCR_MBURST_0 | DMA_SxCR_PL | DMA_SxCR_MINC | DMA_SxCR_DIR_0);       /*-----------------Receive DMA--------------------*/  DMA1_Stream5->PAR = reinterpret_cast<uint32_t>(&(USART2->DR));  DMA1_Stream5->M0AR = reinterpret_cast<uint32_t>(&(usart2_buf.rx));  DMA1_Stream5->CR = (DMA_SxCR_CHSEL_2 | DMA_SxCR_MBURST_0 | DMA_SxCR_PL | DMA_SxCR_MINC);    DMA1_Stream5->NDTR = MAX_UINT16_T;  BIT_BAND_PER(DMA1_Stream5->CR, DMA_SxCR_EN) = true;  return res;}

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

Тогда функция отправки выглядит следующим образом:

bool usart2_write(const uint8_t* buf, uint16_t len){   bool res = false;   static bool first_attempt = true;      /*!<-----Copy data to DMA USART TX buffer----->!*/   memcpy(usart2_buf.tx, buf, len);      if(!first_attempt)   {     /*!<-----Checking copmletion of previous transfer------->!*/     while(!(DMA1->HISR & DMA_HISR_TCIF6)) continue;     BIT_BAND_PER(DMA1->HIFCR, DMA_HIFCR_CTCIF6) = true;   }      first_attempt = false;      /*!<------Sending data to DMA------->!*/   BIT_BAND_PER(DMA1_Stream6->CR, DMA_SxCR_EN) = false;   DMA1_Stream6->NDTR = len;   BIT_BAND_PER(DMA1_Stream6->CR, DMA_SxCR_EN) = true;      return res;}

В функции есть костыль, в виде переменной first_attempt, которая помогает определить самая ли первая это отправка по DMA или нет. Зачем это нужно? Дело в том, что проверку о том, успешна ли предыдущая отправка в DMA или нет я сделал ДО отправки, а не ПОСЛЕ. Сделал я так, чтобы после отправки данных не тупо ждать её завершения, а выполнять полезный код в это время.

Тогда функция приема выглядит следующим образом:

uint16_t usart2_read(uint8_t* buf){   uint16_t len = 0;   constexpr uint16_t BYTES_MAX = MAX_UINT16_T; //MAX Bytes in DMA buffer      /*!<---------Waiting until line become IDLE----------->!*/   if(!(USART2->SR & USART_SR_IDLE)) return len;   /*!<--------Clean the IDLE status bit------->!*/   USART2->DR;      /*!<------Refresh the receive DMA buffer------->!*/   BIT_BAND_PER(DMA1_Stream5->CR, DMA_SxCR_EN) = false;   len = BYTES_MAX - (DMA1_Stream5->NDTR);   memcpy(buf, usart2_buf.rx, len);   DMA1_Stream5->NDTR = BYTES_MAX;   BIT_BAND_PER(DMA1->HIFCR, DMA_HIFCR_CTCIF5) = true;   BIT_BAND_PER(DMA1_Stream5->CR, DMA_SxCR_EN) = true;      return len;}

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

На этом предлагаю закончить с низким уровнем и перейти непосредственно к C++ и паттернам.

MW_USART


Здесь я реализовал базовый абстрактный класс USART и применил паттерн прототип для создания наследников (конкретных классов USART1 и USART2). Я не буду описывать реализацию паттерна прототип, так как его можно найти по первой ссылке в гугле, а сразу приведу исходный код, и пояснения приведу ниже.

#pragma once#include <stdint.h>#include <vector>#include <map>/*!<========Enumeration of USART=======>!*/enum class USART_NUMBER : uint8_t{  _1,  _2};class USART; //declaration of basic USART classusing usart_registry = std::map<USART_NUMBER, USART*>; /*!<=========Registry of prototypes=========>!*/extern usart_registry _instance; //Global variable - IAR Crutch#pragma inline=forced static usart_registry& get_registry(void) { return _instance; }/*!<=======Should be rewritten as========>!*//*static usart_registry& get_registry(void) {   usart_registry _instance;  return _instance; }*//*!<=========Basic USART classes==========>!*/class USART{private:protected:     static void add_prototype(USART_NUMBER num, USART* prot)  {    usart_registry& r = get_registry();    r[num] = prot;  }    static void remove_prototype(USART_NUMBER num)  {    usart_registry& r = get_registry();    r.erase(r.find(num));  }public:  static USART* create_USART(USART_NUMBER num)  {    usart_registry& r = get_registry();    if(r.find(num) != r.end())    {      return r[num]->clone();    }    return nullptr;  }  virtual USART* clone(void) const = 0;  virtual ~USART(){}    virtual bool init(uint32_t baudrate) const = 0;  virtual bool send(const uint8_t* buf, uint16_t len) const = 0;  virtual uint16_t receive(uint8_t* buf) const = 0;};/*!<=======Specific class USART 1==========>!*/class USART_1 : public USART{private:  static USART_1 _prototype;    USART_1()   {      add_prototype( USART_NUMBER::_1, this);  }public:  virtual USART* clone(void) const override final  {   return new USART_1; }  virtual bool init(uint32_t baudrate) const override final; virtual bool send(const uint8_t* buf, uint16_t len) const override final; virtual uint16_t receive(uint8_t* buf) const override final;};/*!<=======Specific class USART 2==========>!*/class USART_2 : public USART{private:  static USART_2 _prototype;    USART_2()   {      add_prototype( USART_NUMBER::_2, this);  }public:  virtual USART* clone(void) const override final  {   return new USART_2; }  virtual bool init(uint32_t baudrate) const override final; virtual bool send(const uint8_t* buf, uint16_t len) const override final; virtual uint16_t receive(uint8_t* buf) const override final;};

Сначала файла идёт перечисление enum class USART_NUMBER со всеми доступными USART, для моего камня их всего два. Затем идёт опережающее объявление базового класса class USART. Далее идёт объявление контейнер а всех прототипов std::map<USART_NUMBER, USART*> и его реестра, который реализован в виде синглтона Мэйерса.

Тут я напоролся на особенность IAR ARM, а именно то, что он инициализирует статические переменные два раза, в начале программы и непосредственно при входе в main. Поэтому я несколько переписал синглтон, заменив статическую переменную _instance на глобальную. То, как это выглядит в идеале, описано в комментарии.

Далее объявлен базовый класс USART, где определены методы добавления прототипа, удаления прототипа, а также создания объекта(так как конструктор классов наследников объявлен как приватный, для ограничения доступа).

Также объявлен чисто виртуальный метод clone, и чисто виртуальные методы инициализации, отправки и получения.

После всего лишь, мы наследуем конкретные классы, где определяем чисто виртуальные методы, описанные выше.

Код определения методов привожу ниже:

#include "MW_USART.h"#include "HW_USART.h"usart_registry _instance; //Crutch for IAR/*!<========Initialization of global static USART value==========>!*/USART_1 USART_1::_prototype = USART_1();USART_2 USART_2::_prototype = USART_2();/*!<======================UART1 functions========================>!*/bool USART_1::init(uint32_t baudrate) const{ bool res = false; //res = usart_init(USART1, baudrate);  //Platform depending function return res;}bool USART_1::send(const uint8_t* buf, uint16_t len) const{  bool res = false;    return res;}uint16_t USART_1::receive(uint8_t* buf) const{  uint16_t len = 0;    return len;} /*!<======================UART2 functions========================>!*/bool USART_2::init(uint32_t baudrate) const{ bool res = false; res = usart2_init(baudrate);   //Platform depending function return res;}bool USART_2::send(const uint8_t* buf, const uint16_t len) const{  bool res = false;  res = usart2_write(buf, len); //Platform depending function  return res;}uint16_t USART_2::receive(uint8_t* buf) const{  uint16_t len = 0;  len = usart2_read(buf);       //Platform depending function  return len;}

Здесь реализованы методы НЕ пустышки только для USART2, так как его я и использую для общения с esp8266. Соответственно, наполнение может быть любое, также оно может быть реализовано с помощью указателей на функции, которые принимают свое значение исходя из текущего чипа.

Теперь же я предлагаю перейти к APP уровню и посмотреть, зачем же все это было нужно.

APP_ESP8266


Определяю базовый класс для ESP8266 по паттерну одиночка. В нем определяю указатель на базовый класс USART*.

class ESP8266{private:  ESP8266(){}  ESP8266(const ESP8266& root) = delete;  ESP8266& operator=(const ESP8266&) = delete;    /*!<---------USART settings for ESP8266------->!*/  static constexpr auto USART_BAUDRATE = ESP8266_USART_BAUDRATE;  static constexpr USART_NUMBER ESP8266_USART_NUMBER = USART_NUMBER::_2;  USART* usart;    static constexpr uint8_t LAST_COMMAND_SIZE = 32;  char last_command[LAST_COMMAND_SIZE] = {0};  bool send(uint8_t const *buf, const uint16_t len = 0);    static constexpr uint8_t ANSWER_BUF_SIZE = 32;  uint8_t answer_buf[ANSWER_BUF_SIZE] = {0};    bool receive(uint8_t* buf);  bool waiting_answer(bool (ESP8266::*scan_line)(uint8_t *));    bool scan_ok(uint8_t * buf);  bool if_str_start_with(const char* str, uint8_t *buf);public:    bool init(void);    static ESP8266& Instance()  {    static ESP8266 esp8266;    return esp8266;  }};

Здесь же есть constexpr переменная, в которой и хранится номер используемого USART. Теперь для изменения номера USART нам достаточно только лишь поменять её значение! Связывание же происходит в функции инициализации:

bool ESP8266::init(void){  bool res = false;    usart = USART::create_USART(ESP8266_USART_NUMBER);  usart->init(USART_BAUDRATE);    const uint8_t* init_commands[] =   {    "AT",    "ATE0",    "AT+CWMODE=2",    "AT+CIPMUX=0",    "AT+CWSAP=\"Tortoise_assistant\",\"00000000\",5,0",    "AT+CIPMUX=1",    "AT+CIPSERVER=1,8888"  };    for(const auto &command: init_commands)  {    this->send(command);    while(this->waiting_answer(&ESP8266::scan_ok)) continue;  }      return res;}

Строка usart = USART::create_USART(ESP8266_USART_NUMBER); связывает наш уровень приложения с конкретным USART модулем.

Вместо выводов, просто выражу надежду, что материал окажется кому-нибудь полезен. Спасибо за прочтение!
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru