Интерфейс на javascript что это
Перейти к содержимому

Интерфейс на javascript что это

  • автор:

Внутренний и внешний интерфейс

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

Один из важнейших принципов ООП – отделение внутреннего интерфейса от внешнего.

Это – обязательная практика в разработке чего угодно сложнее hello world.

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

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

Пример из жизни

Например, кофеварка. Простая снаружи: кнопка, индикатор, отверстия,… И, конечно, результат – кофе 🙂

Но внутри… (картинка из пособия по ремонту)

Масса деталей. Но мы можем пользоваться ей, совершенно не зная об этом.

Кофеварки – довольно-таки надёжны, не правда ли? Можно пользоваться годами, и только когда что-то пойдёт не так – придётся нести к мастеру.

Секрет надёжности и простоты кофеварки – в том, что все детали отлажены и спрятаны внутри.

Если снять с кофеварки защитный кожух, то использование её будет более сложным (куда нажимать?) и опасным (током ударить может).

Как мы увидим, объекты очень схожи с кофеварками.

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

Внутренний и внешний интерфейс

В программировании мы будем разделять методы и свойства объекта на две группы:

  • Внутренний интерфейс – это свойства и методы, доступ к которым может быть осуществлён только из других методов объекта, их также называют «приватными» (есть и другие термины, встретим их далее).
  • Внешний интерфейс – это свойства и методы, доступные снаружи объекта, их называют «публичными».

Если продолжить аналогию с кофеваркой – то, что спрятано внутри кофеварки: трубка кипятильника, нагревательный элемент, тепловой предохранитель и так далее – это её внутренний интерфейс.

Внутренний интерфейс используется для обеспечения работоспособности объекта, его детали используют друг друга. Например, трубка кипятильника подключена к нагревательному элементу.

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

Получив объект, всё, что нужно для пользования им – это знать внешний интерфейс. О внутреннем же знать вообще не обязательно.

Это были общие слова по теории программирования.

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

Шаг 1: публичное и приватное свойство

Конструктор кофеварок будет называться CoffeeMachine .

Локальные переменные, включая параметры конструктора, можно считать приватными свойствами.

В примере выше это power – мощность кофеварки, которая указывается при создании и далее будет использована для расчёта времени кипячения.

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

Свойства, записанные в this , можно считать публичными.

Здесь свойство waterAmount записано в объект, а значит – доступно для модификации снаружи. Можно доливать и выливать воду в любом количестве.

Далее мы будем называть power как «локальной переменной», так и «приватным свойством» объекта.

Это, смотря, с какой стороны посмотреть.

Термины «приватное свойство/метод», «публичное свойство/метод» относятся к общей теории ООП. А их конкретная реализация в языке программирования может быть различной.

Здесь ООП-принцип «приватного свойства» реализован через локальные переменные, поэтому и «локальная переменная» и «приватное свойство» – правильные термины, в зависимости от того, с какой точки зрения взглянуть – кода или архитектуры ООП.

Шаг 2: публичный и приватный методы

Добавим публичный метод run , запускающий кофеварку, а также вспомогательные внутренние методы getBoilTime и onReady :

Приватные методы, такие как onReady , getBoilTime могут быть объявлены как вложенные функции.

В результате естественным образом получается, что доступ к ним (через замыкание) имеют только другие функции, объявленные в том же конструкторе.

Шаг 3: константа

Для расчёта времени на кипячение воды используется формула c*m*ΔT / power , где:

  • c – коэффициент теплоёмкости воды, физическая константа равная 4200 .
  • m – масса воды, которую нужно нагреть.
  • ΔT – температура, на которую нужно подогреть, будем считать, что изначально вода – комнатной температуры 20°С, то есть до 100° нужно греть на ΔT=80 .
  • power – мощность.

Используем её в более реалистичном варианте getBoilTime() , включающем использование приватных свойств и константу:

Удельная теплоёмкость WATER_HEAT_CAPACITY выделена большими буквами, так как это константа.

Внимание, при запуске кода выше в методе getBoilTime будет ошибка. Как вы думаете, почему?

Шаг 4: доступ к объекту из внутреннего метода

Внутренний метод вызывается так: getBoilTime() . А чему при этом равен this ?… Как вы наверняка помните, в современном стандарте он будет undefined (в старом – window ), из-за этого при чтении this.waterAmount возникнет ошибка!

Её можно решить, если вызвать getBoilTime с явным указанием контекста: getBoilTime.call(this) :

Такой подход будет работать, но он не очень-то удобен. Ведь получается, что теперь везде, где мы хотим вызвать getBoilTime , нужно явно указывать контекст, т.е. писать getBoilTime.call(this) .

К счастью существуют более элегантные решения.

Привязка через bind

Можно при объявлении привязать getBoilTime к объекту через bind , тогда вопрос контекста отпадёт сам собой:

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

Сохранение this в замыкании

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

Теперь getBoilTime получает self из замыкания.

Конечно, чтобы это работало, мы не должны изменять self , а все приватные методы, которые хотят иметь доступ к текущему объекту, должны использовать внутри себя self вместо this .

Вместо self можно использовать любое другое имя переменной, например var me = this .

Итого

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

В терминологии ООП отделение и защита внутреннего интерфейса называется инкапсуляция.

Кратко перечислим бонусы, которые она даёт:

Защита пользователей от выстрела себе в ногу

Представьте, команда разработчиков пользуется кофеваркой. Кофеварка создана фирмой «Лучшие Кофеварки» и, в общем, работает хорошо, но с неё сняли защитный кожух и, таким образом, внутренний интерфейс стал доступен.

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

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

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

Удобство в поддержке

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

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

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

Ближайшая аналогия в реальной жизни – это когда выходит «новая версия» кофеварки, которая работает гораздо лучше. Разработчик мог переделать всё внутри, но пользоваться ей по-прежнему просто, так как внешний интерфейс сохранён.

Люди обожают пользоваться вещами, которые просты с виду. А что внутри – дело десятое.

Программисты здесь не исключение.

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

Задачи

Добавить метод и свойство кофеварке

Улучшите готовый код кофеварки, который дан ниже: добавьте в кофеварку публичный метод stop() , который будет останавливать кипячение (через clearTimeout ).

Интерфейсы — JS: Абстракция с помощью данных

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

Создавать хорошие интерфейсы не так уж просто, как может показаться на первый взгляд. Я бы даже сказал, что это крайне сложно. Каждый день мы встречаемся с неудобными интерфейсами, начиная от способов открывания дверей и заканчивая работой лифтов. Чем сложнее система (то есть, чем больше возможных состояний), тем сложнее сделать интерфейс. Даже в примитивном примере с кнопкой включения телевизора (два состояния — вкл/выкл) можно реализовать либо две кнопки, либо одну, которая ведёт себя по-разному в зависимости от текущего состояния.

В программировании всё устроено похожим образом. Интерфейсом называют набор функций (имена и их сигнатуры, то есть количество и типы входящих параметров, а также возвращаемое значение), не зависящих от конкретной реализации. Такое определение один в один совпадает с понятием абстрактного типа данных. Например, для точек интерфейсными являются все функции, которые мы реализовывали в практике, и которые описывались в теории.

Как соотносятся между собой понятия абстракция и интерфейс? Абстракция — это слово, описывающее в первую очередь те данные, с которыми мы работаем. Например, почти каждое веб-приложение включает в себя абстракцию "пользователь". На Хекслете есть абстракция "курс", "проект" и многое другое. Интерфейсом же называется набор функций, с помощью которых можно взаимодействовать с данными.

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

Открыть доступ

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

What is an interface in JavaScript? [duplicate]

A lot of MDN pages describe things as "interfaces" — I was surprised that "interface" wasn’t linked to a more explanatory page; it’s just described as an "object type" on MDN’s web APIs page.

nCardot's user avatar

1 Answer 1

An interface describes the shape of an object. (what properties it has, what type of values those properties contain, etc.) It’s not an object itself — it’s a more abstract description of what a particular object that implements the interface looks like.

For example, in the HTML standard, the DragEvent interface is described as such:

So DragEvent is a type of MouseEvent (which is another interface). It has a constructor function, so you can call new on window.DragEvent . When calling the constructor, you call it with the following arguments:

  • type , which is a DOMString (which is basically just any plain string)
  • An optional argument of type DragEventInit (which the documentation defines), which defaults to the empty object

A DragEvent instance also has a dataTransfer property

Note that the "interface" definition you’re linking to is not exactly a JavaScript thing, but more of a thing for web APIs. In other implementations of JavaScript not in browsers (for example, in Node), an interface may mean something different (or nothing at all).

TypeScript, a widely used static type checker for JavaScript, has a very similar notion of interfaces, which describe the shape of a particular object. For example:

[js] Программируйте на уровне интерфейсов

Когда я гуглил эту тему, я планировал выпустить только видео для плейлиста о Паттернах. Но как то мне не очень понравились уже существующие статьи, возможно потому что я пишу на JS, а там примеры были, то на python, то еще на каком языке. Поэтому я решил опубликовать свое видение темы Программируйте на уровне интерфейсов. Возможно это будет кому-то полезным (Данная статья является расшифровкой видео).

Что подразумевается под словом интерфейс?

Начнем с базовых понятий. Что в данном принципе подразумевается под словом интерфейс? Ведь в JS нет интерфейсов, нам что нужен TypeScript, чтобы пользоваться этим принципом? Ответ, однозначно нет

Интерфейс в данном случае, это скорее идея. Мне нравится как эта идея описана в википедии:

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

Интерфейсы в реальном мире

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

Интерфейсы в js

И такого рода интерфейсы нас окружают везде. Точно как и в программировании. Вы например используете какую-то npm библиотеку, допустим classnames , которая позволяет добавлять и убирать CSS классы в зависимости от разных условий. Если вы откроете документацию, вы увидите, что у нее четко описано, какие параметры принимает npm пакет и какой результат возвращает. Это и есть интерфейс npm пакета classnames .

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

Определение принципа

Я думаю вы поняли, что подразумевается под словом интерфейс в данном принципе. Остается лишь ответить на вопрос: “а что значит программировать на уровне интерфейсов?”

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

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

И если вы решите создать свой телевизор, вы будете думать: «Ага! Мой телевизор должен хорошо взаимодействовать с любым пультом». Т.е. интерфейс был разработан ранее и вы сейчас делаете реализацию вашего телевизора, которая будет удовлетворять условиям уже существующего интерфейса, а не наоборот. Это и есть программировать на уровне интерфейсов, а не на уровне реализаций.

Реальный пример

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

При написании любого чата, вам нужно получать сообщения в режиме реального времени. И допустим в нашем проекте решили использовать популярную технологию web socket . Сервер готов, технология которую будем использовать так же прозрачна и понятна. Остается только вопрос: “а зачем в такой ситуации программировать на уровне интерфейсов?» Ведь все и так понятно, уже можно писать реализацию, время только сэкономим”

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

Ваш чат сильно завязан на конкретную технологию web socket . Но в настоящее время, сильно набирает популярность технология SSE (server-sent-events) . И если вы решите мигрировать на нее, вероятно у вас изменится структура присылаемых с сервера данных, вариант ее подключения и многое другое. Вопрос лишь в том, насколько все эти вещи у вас размазаны по проекту. Ведь чем больше они размазаны, тем больше шансов насоздавать баги и тем больше команде тестирования придется проводить регрессию приложения.

Вы зададите вопрос, так а как программирование на уровне интерфейсов решает эту проблему?

Программируем на уровне интерфейсов

Чтобы ответить на этот вопрос, давайте попробуем программировать на уровне интерфейсов. Для этого задумайтесь, есть ли вашему чату разница каким образом вы получаете сообщения: вы получили их по web socket , по SSE или вообще отсылаете каждые 3 секунды https запрос на сервер, чтобы узнать, а не пришли ли новые сообщения. Чату мне кажется на это абсолютно все равно. Чат хочет знать лишь сообщение, которое пришло.

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

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

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

Думаю каждому из вас под силу быстро написать генератор рандомных сообщений, например каждые от 1 до 20 секунд модуль вам будет присылать рандомный набор букв. В итоге, то что сервер медлит с выбором технологий — это не будет вас никак блокировать, вы сможете разрабатывать следующие фичи чата.

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

А если завтра опять захотят заменить на новую технологию, вам это не составит никакого труда, вам придется обновить код лишь в одном изолированном модуле. И такой модуль гораздо проще тестировать, чем размазанный по всему проекту код. А уже завтра вы можете разрабатывать мобильное приложение, и такой модуль можно вынести в npm пакет и пере использовать как в веб проекте, так и React Native .

Дополнительная мотивация

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

А многие могут ответить, ой да у нас нативные разработчики делают мобилки, и сервер точно не будет мигрировать с сокетов ближайшие 5 лет, а там уже никто не знает что будет, может уже на новый Фреймворк переезжать будем и смысла особого нет, писать на уровне интерфейсов.

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

Только представьте насколько facebook нуждается в такого рода модулях. У них десятки тысяч компонентов в проекте и если не придерживаться такого рода программирования, вы никогда не сможете зарефакторить такой сложный проект. И как думаете наймут они вас, если вы скажете, что кодить на уровне интерфейсов, все это фигня или вы никогда этим не занимались, потому что видите ли у вас проект по проще?

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

Добавить комментарий

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