[МУЗЫКА] [МУЗЫКА] Итак, наши комплексные числа уже, в принципе, достаточно хороши. Они умеют много чего: печататься, создаваться, складываться, умножаться. Это очень здорово, но не очень понятно, зачем городить этот огород. Для комплексных чисел, конечно, красиво, что мы можем «плюс» и «умножить» использовать, но если бы это были какие-то нематематические объекты, то для них операции «плюс» и «умножить», конечно, вводить не стоило. В принципе, можно было обойтись каким-то набором функций, который работает с чем-то стандартным, типа словаря или кортежа, или еще что-то. Одна из прелестей объектно-ориентированного программирования состоит в наследовании. То есть если вы уже что-то определили и хотите теперь немножко более уточненный объект какой-то создать, то это очень легко сделать. Если вы определили, что такое мячик, то какими он свойствами обладает, что он шарообразный и прыгает, если по нему стукнуть его об пол, то вам уже описывать баскетбольные мячи, футбольные, волейбольные и так далее гораздо проще. Какие-то базовые свойства описаны, вы описываете только особенности, отличия или дополнительную какую-то функциональность. С нашим классом комплексных чисел тоже такая история может произойти. Как я рисовал на доске, у нас комплексное число очень похоже на точку на плоскости. Давайте введем класс «точка», будем иметь в виду, что она на плоскости, которая пронаследует все методы, которые определены для комплексных чисел, и добавит что-нибудь свое. Как это делается? Мы создаем новый класс, называем его, новое имя даем — point, и в скобках, как мы это делали для исключений, указываем, от кого он наследуется. В нашем случае не от стандартного какого-то base exception, а от нашего класса «комплексное число», которое, в свою очередь, ни от кого не наследуется. Теперь наш point, наша точка умеет абсолютно все то же самое, что умело комплексное число, пока что мы просто дали новое имя. Что мы хотим, например, для точки сделать? Определить метод, считающий длину вектора от начала координат, от 00, до нашей точки. Это теперь не стандартная какая-то операция, а просто метод с названием. Давайте чтобы не путаться с функцией len, назовем его length — длина. Он принимает также self, как абсолютно любой метод в классе, это конкретный объект, и с его полями мы должны работать. Что мы можем здесь сделать? Обращаться давайте... Чтобы, конечно, по-хорошему нужно назвать поля x и y, переназвать их. Это можно сделать, но давайте сейчас пока делать не будем. Воспользуемся полями re и im, real and imaginary parts для нашего числа комплексного. Итак, как считается длина? Это квадрат x координаты + квадрат y координаты, и из всего этого квадратный корень. Давайте я здесь уберу пробелы лишние, чтобы было красиво. И корень квадратный из всего этого извлечь. Корень квадратный можно извлекать разными способами. Давайте возведением в степень 1/2, можно так сделать. Собственно, это всё. Вот такой метод. Посмотрим, что из этого всего получилось. Я удалю наши комплексные числа. Здесь мы уже знаем, что у нас все работает, вся эта отладка. Давайте создавать точки на плоскости теперь. Ну, например, первая точка — мы для нее вызываем конструктор, хотя конструктор не описан в классе point. У нас вызовется конструктор для нашего предка — для комплексного числа, и мы уже знаем, что он сделает: он разложит по полям наши числа в том порядке, как они шли. И давайте напечатаем длину вектора до нашей точки. Вот что-то получилось. Я не могу понять, правильно это или нет, но надеюсь, что правильно. Давайте, вот здесь корень из 2 должен получиться, это мы знаем, как это выглядит. Да. Получается корень из 2. Всё. То есть мы определили метод у наследника. Когда какой-то метод не определен, например, конструктор, то он вызывается у родительского класса. Ну, то есть мы идем, идем, идем наверх, пока не найдем нужный нам метод. Находим мы его быстро, непосредственно у нашего предка. А что было бы, если бы мы создали комплексное число, то есть объект типа комплекс, и для него вызвали метод length? Такого метода нет. О чем оно нам сообщает: у него нет атрибута, в нашем случае метода length, потому что он есть только у точки, у комплексного числа он не определен. Давайте посмотрим, какие забавные еще можно делать эффекты, которые не сказать, что очень разумны в нашем случае, но демонстрируют возможности. Итак, а давайте-ка сложим точку с комплексным числом. Ну и положим ее в отдельный объект новый и напечатаем его длину. Вот такая вот интересная конструкция: точка складывается с комплексным числом. Объекты немного разной природы, но у нас, смотрите, оно не сработало. Почему? У нас в случае сложения point и комплекс вызывается метод сложения для комплексного числа, и он возвращает объект в комплексное число. То есть если мы хотим, чтобы у нас возвращалась точка, нужно делать преобразование либо сконструировать объект типа point от комплексного числа. Для этого достаточно написать здесь свой конструктор от комплексного числа. То есть такая штука не прошла, автоматически на point комплекс не исправился, ну и было бы странно, если бы он исправился. Но, если у нас есть точка, мы ее можем использовать как комплексное число, то есть результат является комплексным числом. Если мы напечатаем, то у нас вот оно напечаталось. Точка сложилась с комплексным числом. Отлично! А что будет, если мы напечатаем точку, point? Оно напечатается в виде опять же комплексного числа, так как мы это описали, но для точки мы хотим делать это по-другому. Поэтому мы можем взять и переопределить метод вывода. Как печатаются точки обычно? Давайте печатать ее в круглых скобках, как кортеж по сути, то есть сначала x координата, потом y координата. Переопределение методов — это и есть в некотором смысле то, что называется полиморфизм, изменчивость объекта. У нас у родительского класса есть метод превращения объекта в строку. У нас произошел полиморфизм, и теперь у наследника этого класса метод переопределен. Что мы хотим сделать? Да, здесь, кстати, все очень просто. Мы будем возвращать строку — стандартный str от кортежа. Потому что выводиться точка, в принципе, должна примерно так, то есть это то, чего мы хотим. Конструируем кортеж из нашего x и y, который у нас называется re и im, и возвращаем его строковое представление. Давайте посмотрим, как оно печатается. Всё. Теперь печатается уже как точка, уже не как комплексное число. Ну и давайте посмотрим, что в комплексном числе в результате такого полиморфизма у нас не сломалось, а комплексное число по-прежнему печатается так, как нужно. Таким образом, какой метод вызывать, определяется типом объекта: если это point, то вызывается метод для point, если это комплексное число — вызывается метод из класса Complex. Иногда, бывает, нужно явно использовать метод родительский — это, в принципе, можно сделать. Вот смотрите, у нас здесь даже подсказочка появляется, что это переопределенный метод, но в простой ситуации, как у нас, когда нужно в чистом виде использовать какие-то методы родительского класса, мы в наследники просто ничего не пишем, и они автоматически используются из родительского класса. Собственно, сейчас мы посмотрели значительную часть работы с классами, переопределяли стандартный метод. В реальной жизни, конечно, это возникает очень редко. В основном, методы класса описываются примерно, как наша функция length. То есть у них есть какое-то название нестандартное, и вы обращаетесь к ней, написав имя вашего объекта, точка, название метода. В принципе, мы посмотрели всё: и полиморфизм, и наследование, и инкапсуляцию, избавив пользователя от доступа к нашим данным, предоставив ему интерфейс. Собственно, все три основных кита, на которых держится ООП, у нас в нашей маленькой программе присутствуют. В реальности, конечно, все сложнее, и писать классы везде для маленьких простых задач не нужно. Они нужны для больших проектов, над которыми работают несколько людей, где есть сложная логика. И подробнее об этом мы поговорим в следующем видео. [МУЗЫКА] [МУЗЫКА]