Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Инкапсуляция JS: Введение в ООП

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

Сложно

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

В большом числе источников под инкапсуляцией понимают сокрытие данных (data hiding) от прямого внешнего обращения (обычно с помощью ключевых слов private, protected). Более того, именно это определение захотят от вас услышать на собеседовании, но оно правильно лишь частично. Несмотря на то, что это распространенное определение, стоит разделять объединение данных с методами и сокрытие этих данных. Есть языки, например JavaScript и Python, в которых есть объединение данных, но нет сокрытия данных. Причем если в этих языках ввести сокрытие данных, то архитектура программ не изменится, а вот если разъединить данные и методы, то придется переписать практически весь код. Примерно такая же картина и с языками в которых есть сокрытие данных. Если его убрать, то мало что поменяется, кроме того, что разработчикам придется быть чуть аккуратнее при работе с объектами.

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

Зачем нужно сокрытие данных, разбирается в уроке про инварианты

Конец Сложно :)

// Вызов метода
user.getName();

// Вызов функции
getName(user);

О том, как работают методы внутри, мы поговорим в следующем уроке. А сейчас рассмотрим внешние особенности методов.

Работа с методами вместо функций приводит к одному неожиданному эффекту – появляется возможность реализовать автодополнение методов в редакторах. Это снижает ментальную нагрузку и очень радует программистов. Существует теория, что именно эта особенность методов стала причиной такой популярности ООП (не подтвержденная, но вполне вероятная).

В языках с развитой системой модулей автодополнение есть и при работе с обычными функциями. Но там в любом случае надо сначала написать правильное имя модуля. Пример из эликсира: User.getName(user). С другой стороны, существуют языки с Unified Function Call (например Nim), там обычные функции можно вызывать как методы и получать автодополнение.

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

Это, конечно, не так. Достаточно пройти курс JS: Абстракции с помощью данных, чтобы убедиться в этом. Абстракции и моделирование реального мира существуют не только в ООП. Они существовали до и будут существовать после.

Попробуйте представить себе добавление в друзья в ООП-стиле. Кто кого должен добавить (первый друг второго или второй первого) и как не допустить рекурсии при взаимном добавлении?

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

// send просто функция, поэтому ее надо импортировать перед использованием
import send from 'mail';

const sendEmail = (user) => {
  send('Subject', user.getEmail()); // Не надо импортировать getEmail потому что это метод
}

А что делать в том случае, когда объекта нет, как в примере выше? Разработчики языков и библиотек поступают по-разному. В JavaScript обычные функции и методы спокойно уживаются вместе. Примерно то же самое происходит в Python. В Ruby и PHP (в современных фреймворках) обычные функции выглядят уже не так естественно, хотя их по-прежнему можно создавать. В Java вообще нет возможности создавать обычные функции. Любая функция будет методом. Поэтому в Java объекты создают практически на каждый чих. Это значительно раздувает программу и усложняет реализацию простых вещей. Но есть и другие языки. В Elixir и Clojure методов в текущем понимании просто нет и самое главное, они там просто не нужны, а код при этом лаконичный, простой и расширяемый.

Для имитации обычных функций в Java используют статические методы. Они позволяют работать без создания объектов.

Четвертая особенность – цепочки. Вспомните такой вызов:

const brand = 'bmw';
// Этот метод не изменяет строку, а возвращает новую!
brand.toUpperCase(); // BMW

Этот метод возвращает новую строку, у которой тоже есть методы, а значит их можно вызвать. Например:

// Переменная, так как перезаписываем
let brand = 'bmw';
brand = brand.toUpperCase(); // BMW
brand = brand.concat(' & Kia'); // BMW & Kia
brand = brand.replace('BMW', 'Opel') // Opel & Kia
console.log(brand); // => Opel & Kia

А теперь немного магии. Что если не создавать промежуточные переменные, а делать вызовы сразу? Пробуем:

const brand = 'bmw';
// Теперь лучше сделать новую константу, вместо переменной
const newBrand = brand.toUpperCase().concat(' & Kia').replace('BMW', 'Opel');
console.log(newBrand); // => Opel & Kia

Код получился компактнее и в некоторых случаях он будет понятнее. Но не увлекайтесь, очень легко перейти границу. Этот код всегда можно разбить на несколько строк:

const brand = 'bmw';
const newBrand = brand.toUpperCase()
                      .concat(' & Kia')
                      .replace('BMW', 'Opel');
console.log(newBrand); // => Opel & Kia

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

const brand = 'bmw';
const newBrand = brand.toUpperCase()
                      .split('') // ['B', 'M', 'W']
                      .reverse() // ['W', 'M', 'B']
                      .join(''); // WMB
console.log(newBrand); // => WMB

У таких цепочек есть специальное имя: fluent interface

Как и практически все остальное в современном понимании ООП, цепочки не являются чем-то эксклюзивным. Более того, они повторяют такую вещь, как пайплайн (pipeline). Если вы знакомы с командной строкой, то скорее всего не раз видели такой код:

# | называется пайпом (pipe - труба)
# Пайплайн часто сравнивают с бусинками (функции), через которые пропускают веревку (данные).
$ cat sample | grep -v a | sort -r

Эта цепочка команд последовательно передает данные слева направо, пропуская их сквозь разные обработчики. Сама концепция пришла из математики и появилась задолго до программирования. Во многих языках пайплайн реализован как языковая конструкция (либо макрос в Lisp). Это настолько удачная концепция, что сейчас её стараются интегрировать во многие языки. Например, она есть в F#, OCaml, Elixir, Elm, Julia, Hack. Прямо сейчас пайплайн находится в стадии рассмотрения в JavaScript. Посмотрите пример:

// Так с функциями работать неудобно
const result = exclaim(capitalize(doubleSay('hello')));
console.log(result) // => "Hello, hello!"

// А вот это совсем другое дело (но нужно привыкнуть)
// |> – это пайп, он "отправляет" данные пришедшие слева в функцию справа
const result = 'hello'
  |> doubleSay
  |> capitalize
  |> exclaim;

console.log(result) // => "Hello, hello!"

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Для полного доступа к курсу нужна профессиональная подписка

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

Получить доступ
900
упражнений
2000+
часов теории
3200
тестов

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

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

  • 120 курсов, 2000+ часов теории
  • 900 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Фронтенд-разработчик
Профессия
Разработка фронтенд-компонентов веб-приложений
29 сентября 8 месяцев
Иконка программы Node.js-разработчик
Профессия
Разработка бэкенд-компонентов веб-приложений
29 сентября 8 месяцев

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг»