0:00
[БЕЗ_ЗВУКА] Итак,
мы уже видели разные примеры использования классов.
Самый простой пример —
когда у вас в коде есть какой-то реальный объект: видеолекция, маршрут.
Тогда вы для них делаете структуру или класс.
Или если вы хотите, чтобы у вас были как будто бы именованные аргументы.
Хотите явно указывать день, месяц и год —
где день, где месяц, а где год — вы тоже пишете вспомогательные структуры.
Где же еще могут пригодиться классы?
Давайте рассмотрим пример практически из реальной жизни.
Представьте, что вы пишете поиск по картинкам, например.
У вас есть много картинок, вы их уже как-то отобрали по запросу
пользователя и теперь хотите эти картинки отдать в результаты поиска.
В каком-то порядке их надо отдать, правильно же?
Вот у вас, например, есть картинка, у картинки есть, скажем,
два поля: качество и свежесть.
И вы хотите как-то их отсортировать, отранжировать по этим двум качествам.
И у вас есть какая-то формула, скажем.
Например — вот смотрите — у меня есть параметры моей формулы:
два вещественных числа a и b.
Есть параметры изображения — качество и свежесть,
и у вас откуда-то там, от каких-то специалистов есть правильная формула —
формула вычисления веса изображения, по которому его можно сортировать.
Вот есть функция ComputeImageWeight,
которая принимает на вход параметры формулы и параметры изображения.
И здесь мы что делаем?
Мы берем качество изображения, а затем из него вычитаем свежесть,
умноженную на a, и еще вычитаем b и говорим, что это вес изображения.
Ну вот просто у вас есть такой код.
И теперь вам говорят: «Теперь у изображения есть рейтинг».
Что вы делаете?
Вы, наверное, дописываете поле rating.
Дальше вы интересуетесь, как же этот рейтинг должен участвовать в формуле.
И вам говорят, что к весу нужно прибавить рейтинг, умноженный на некоторое число c.
Соответственно, вы в параметры добавляете c.
А в ComputeImageWeight добавляете
weight += image.rating * params.c.
Код успешно компилируется.
Итак, вы просто добавили новое поле в изображение,
добавили новый параметр в формулу, поправили функцию ComputeImageWeight.
И всё замечательно.
На самом деле, всё работает, кроме одного маленького нюанса.
Внезапно обнаруживается, что есть еще одна функция, про которую вы забыли —
функция ComputeQualityByWeight.
Почему-то нужно еще уметь по весу изображения обратно вычислять
соответствующее качество:
какое должно было бы быть качество изображения,
чтобы у него был вот такой вот вес?
Вот на этот вопрос отвечает вот эта функция.
Она принимает на вход параметры формулы, параметры изображения и вес,
который надо получить.
И здесь, как мы видим, по сути дела некоторая обратная операция.
Мы брали вес, и если мы здесь вычитали свежесть * a + b,
то здесь мы ее прибавляем — свежесть * a + b.
И в принципе, если вы вдруг внезапно обнаружили,
что вам нужно поправить еще одну функцию, вы можете взять ее и поправить:
написать quality
−= image.rating * params.c.
Даже точнее, если делать совсем правильно,
то если мы здесь сначала вычитали что-то про свежесть,
а потом прибавляли что-то про рейтинг, то здесь надо сделать в обратном порядке.
Вот мы так сделаем, и в принципе всё — проблема решится, но нужно все-таки
думать о будущем, нужно думать о тех, кто поправит эту функцию потом, добавляя
еще какие-то новые параметры изображения, и забудет поправить эту функцию.
Вообще говоря, одна из наибольших возможных зол — это дублирование кода,
и в данном случае, оно хоть и не явное, но присутствует:
у вас есть некоторый способ вычисления веса изображения по его качеству.
Вот он закодирован вот здесь, но он также закодирован и вот здесь,
просто в обратном порядке.
Как нам сделать этот код...
как нам убрать дублирование этого кода?
Как нам защитить всех будущих редакторов этого кода от такой ошибки,
которую мы сейчас чуть не допустили?
Если у нас есть такая сущность
как способ вычисления веса изображения по его качеству
и эта сущность используется дважды, мы можем для нее написать некоторый класс.
По сути, это некоторая функция — функция вычисления веса по качеству.
Как она будет выглядеть?
Давайте я сразу представлю, что у меня будет некоторый класс Function.
И у меня будет функция MakeWeightFunction,
которая будет возвращать ту самую функцию — вычисление веса по качеству.
Она будет принимать на вход те же самые параметры формулы
и изображения.
Что мы здесь будем делать?
Мы хотим создать некоторый объект функции.
Давайте поставим перевод строки для лучшей читаемости.
Хотим создать некоторый объект функции, который мы используем
и тут — в ComputeImageWeight, и тут — в ComputeQualityByWeight.
Давайте представим, как мы это используем.
Мы и там, и там создадим WeightFunction.
Напишем Function function =
MakeWeightFunction(params, image).
Напишем так здесь, но и точно так же напишем здесь.
Что теперь?
Когда мы хотим вычислить вес изображения по его качеству,
мы хотим просто вызвать эту функцию от качества изображения.
function... допустим, у нее будет какой-то метод Apply,
который вызывает эту функцию, Apply (от чего? —
от качества) image.quality.
По сути, это получается вес изображения, который можно вернуть из функции.
А что должно происходить вот здесь?
Вот здесь мы хотим, наоборот, по весу получить качество.
То есть нам нужна не эта функция исходная, не функция function, а обратная функция.
Если мы раньше сначала прибавляли что-то, а потом вычитали,
теперь мы должны наоборот — сначала прибавить то, что мы вычли,
а потом вычесть то, что мы прибавили.
То есть, нам нужно научить этот класс делать обратную функцию.
Давайте так и напишем:
function.Invert().
И после того, как мы эту функцию обратили, теперь нужно вызвать ее от того
самого веса, чтобы получить качество.
Может показаться, что обращение функции — это некоторая сложная операция.
На самом деле, мы сейчас увидим, что нет: у нас же всё, что делается в этой функции, —
это прибавить что-то и вычесть что-то.
Поэтому обращение будет элементарным.
Давайте все-таки представим,
как у нас будет выглядеть функция MakeWeightFunction.
В ней мы сначала создадим какую-то функцию пустую,
а затем эту функцию опишем, а именно: добавим
в нее элементарную операцию AddPart (часть этой функции) — прибавить image.
(Что у нас там было?
У нас там была свежесть.) freshness * params.a
+ params.b.
Но, конечно, там было все-таки не сложение, а вычитание.
И еще у нас добавилась часть с рейтингом.
А именно, мы прибавляем
(image.rating * params.c);
Ну, собственно, функция готова.
Сам код по вычислению
веса по качеству и качества по весу получается довольно простым и понятным.
У нас есть функция, которая описывает саму формулу: вычесть
вот это и прибавить вот это.
А также есть функция ComputeImageWeight, которая говорит: просто вызови эту функцию
от качества; и функция ComputeQualityByWeight,
которая говорит: обрати эту функцию и вызови ее от веса — получишь качество.
Итак, вот здесь нам пригодится специальный класс для по сути обратимой функции.