Базовый CS с нуля
Связка данных и поведения
В разделе 09 ты видел объект: значение, составленное из именованных полей. Объект-счётчик
может хранить единственное поле count. Чтобы увеличить счётчик, ты написал бы функцию
increment, которая принимает объект и прибавляет единицу к его полю count.
Заметь, что эти две вещи — поле count и функция increment — принадлежат друг другу.
Данные бесполезны без операции, а операция бессмысленна без данных. Но до сих пор они
жили порознь: поле внутри объекта, функция где-то ещё.
Этот урок закрывает этот разрыв. Поле объекта не обязано хранить число — оно может хранить функцию. Когда так, функция и данные, с которыми она работает, оказываются внутри одной именованной единицы. Эта единица — базовая форма почти каждой абстракции, которую ты будешь строить, и этот урок трассирует ровно то, как работает вызов внутрь неё.
После этого урока ты сможешь определить метод как функцию, хранимую как поле объекта, объяснить, как метод достигает других полей объекта, трассировать вызов метода шаг за шагом и сказать, что связка предоставляет и что скрывает.
Метод — это функция, которая хранится как поле объекта и работает с данными того же объекта.
Вспомни две вещи. Из урока 03 раздела 09: объект — это набор именованных полей, а поле хранит значение. Из раздела 08: функция сама является значением — её можно хранить и вызывать. Сложи это вместе: поле может хранить функцию. Когда поле хранит функцию, работающую с собственными другими полями объекта, эта функция — метод.
Это даёт объекту два рода полей:
- Поля данных — хранят состояние объекта (число, строку).
- Поля-методы — хранят функции, которые читают или меняют это состояние.
Метод достигает объекта, в котором он живёт, через имя this. Внутри метода this
ссылается на объект, на котором метод был вызван, поэтому this.count означает «поле
count объекта, которому я принадлежу». Вызов метода записывается object.methodName() —
объект слева от точки становится this внутри метода.
Результат — связка: одна именованная единица, хранящая данные вместе с операциями над
этими данными. Интерфейс связки (вспомни урок 01 раздела 10) — это набор методов,
которые можно вызвать. Устройство полей — какие поля данных есть, как они названы — это
скрытая реализация. Пользователь связки вызывает counter.increment() и никогда не
трогает count напрямую. Это объединение данных с операциями над ними при сокрытии
внутренних данных от внешнего кода называется инкапсуляцией.
Код ниже строит объект-счётчик с одним полем данных и двумя методами, затем вызывающая сторона использует только методы.
1
const counter = {
2
count: 0, // поле данных — состояние
3
increment() { // поле-метод — операция
4
this.count = this.count + 1;
5
},
6
value(): number { // поле-метод — другая операция
7
return this.count;
8
},
9
};
10
11
counter.increment(); // вызов метода: counter становится `this`
12
counter.increment();
13
let n = counter.value(); // n становится 2
- L2 count — поле данных: оно хранит состояние объекта, число
- L3 increment — поле-метод: его значение — функция, а не число
- L4 this ссылается на counter; this.count читает и записывает поле count объекта counter
- L7 value — метод, который читает состояние и возвращает его; он ничего не меняет
- L11 counter.increment(): объект слева от точки становится this внутри метода
- L13 вызывающая сторона использует метод value() — она сама никогда не называет поле count
Пройди три вызова внизу шаг за шагом. Каждая ячейка показывает состояние объекта —
значение его поля count — в этот момент.
1
const counter = {
2
count: 0,
3
increment() {
4
this.count = this.count + 1;
5
},
6
value(): number {
7
return this.count;
8
},
9
};
10
11
counter.increment();
12
counter.increment();
13
let n = counter.value();
Почему это работает
Зачем связывать их, а не держать порознь? Когда данные и операции над ними в одной
единице, есть ровно одно место, которое трогает count: методы. Если ты завтра решишь
переименовать count, или хранить его как строку, или разбить на два числа, ты изменишь
только два метода. Каждая вызывающая сторона по-прежнему пишет counter.increment() и
counter.value() — без изменений, потому что интерфейс выдержал. Это идея из урока 01
раздела 10 — неизменный интерфейс, заменяемая реализация — применённая к структуре
данных, а не к одной функции.
Частая ошибка
Распространённая ошибка — читать counter.increment (без скобок) как вызов метода. Это
не так. counter.increment — это поле-метод: оно вычисляется в само значение-функцию.
Только counter.increment(), со скобками, действительно вызывает функцию и выполняет
её тело. Здесь работает то же различие, что ты видел для обычных функций в разделе 08:
назвать функцию — не то же, что вызвать её.
Счётчик начинается с count = 0. После того как counter.increment() выполнен 4 раза, какое значение возвращает counter.value()?
Метод — это функция, хранимая как поле объекта. У объекта counter есть поля count, increment и value. Сколько из его 3 полей — поля-методы?
Внутри increment this.count означает поле count объекта, на котором метод был вызван. Если вызван counter.increment(), this.count ссылается на поле count какого объекта? Введи 1, если это объект counter, 0, если это какой-то другой объект.
Связка предоставляет свои методы и скрывает устройство своих полей данных. Вызывающая сторона хочет прибавить 1 к счётчику. Сколько полей данных она должна назвать напрямую, чтобы это сделать?
counter.value() вызван, когда count хранит 7. value() возвращает this.count и ничего не меняет. После вызова какое значение хранит count?
Что такое метод и что даёт связка данных с методами?
Метод — это функция, хранимая как поле объекта, которая работает с собственными
данными этого объекта. Поэтому объект может хранить два рода полей: поля данных,
несущие его состояние, и поля-методы, хранящие операции над этим состоянием. Внутри
метода this называет объект, на котором метод был вызван, поэтому this.count достигает
собственного поля count этого объекта; вызов object.method() делает объект слева от
точки значением this. Хранение данных вместе с операциями над ними в одной именованной
единице — это связка. Интерфейс связки — это набор методов, которые можно вызвать;
устройство полей — скрытая реализация. Вызывающая сторона использует counter.increment()
и counter.value() и никогда сама не называет поле count — поэтому устройство может
меняться, а вызывающие стороны остаются неизменными. Объединение данных с операциями над
ними при сокрытии внутренних данных называется инкапсуляцией, и это базовая форма
почти каждой абстракции крупнее одной функции.