Всем привет!
Недавно мы прослушали лекцию про наследование классов в языке C++.
Наследование нам помогало с дедупликацией повторяющегося кода в различных классах.
Соответственно, дедупликацию мы исключали за счет
создания базового класса и вынесения общего кода в методы этого класса.
Давайте восстановим код из этой лекции,
но не полностью, только те фрагменты, которые нам потребуются в дальнейшем.
Давайте, во-первых, проверим, что мы правильно восстановили код.
Добавим в функцию main недостающую строчку return 0,
соберем и запустим.
Все отработало правильно,
в нашем случае кошка съела яблоко, собака съела апельсин.
Вот.
Соответственно, дедупликацию кода, общего кода, мы знаем, как делать.
Давайте теперь рассмотрим не общий код, а специфичный для каждого класса.
Давайте напишем функции,
которые позволяют нам единообразно заставлять,
просить животных издавать разные звуки.
Соответственно, самый простой вариант — это написать
отдельную функцию для кошки и для собаки.
[ЗВУК] Давайте
добавим вызовы этих
функций в функцию main и проверим, что все работает.
[ЗВУК] Да,
все верно,
кошка и собака издали звук.
Но с такой реализацией мы возвращаемся к нашей исходной
проблеме: у нас есть несколько однотипных функций,
которые принимают схожие аргументы и делают примерно одно и то же.
Что это означает?
Это означает, что если внезапно у нас появляются дополнительные
классы-наследники от класса Animal, то у нас количество функций
возрастает согласно количеству классов, то есть равно количество этих классов.
С другой стороны, если у наших классов есть другие специфичные методы,
помимо подачи голоса, в нашей модели, то нам могут потребоваться другие функции,
помимо MakeSound, которые бы обобщали это свойство специфично для каждого класса.
То есть у нас получалось бы, что и количество функций росло.
То есть если у нас, например,
будет X классов: собака, кошка, лошадь и так далее,
и нам потребуется Y специфичных свойств использовать
из этих классов, то есть написать Y различных функций типа MakeSound,
то у нас получается, в сумме количество функций будет X * Y.
Это очень много, неэффективно и сложно поддерживаемо.
Давайте подумаем, что же мы можем сделать.
Первая же идея, которая приходит нам в
голову — это воспользоваться методикой из предыдущей лекции.
Давайте мы объединим функцию MakeSound и унесем ее в базовый класс.
Давайте посмотрим, как бы это могло выглядеть.
[ЗВУК]
[ЗВУК]
[ЗВУК]
[ЗВУК] Так
как код в данном случае не является общим для различных классов-потомков,
то нам приходится делать довольно громоздкую конструкцию,
которая явно проверяет в базовом классе тип класса-потомка.
Функции Bark и Meow нам больше не нужны.
Теперь у нас есть общая функция Voice в классе Animal.
Давайте попробуем ее использовать.
[ЗВУК]
[ЗВУК] Код
в функции main остается точно таким же.
Давайте проверим, что у нас по-прежнему все работает.
М-м.
У нас возникла ошибка,
потому что мы забыли указать константность метода Voice.
Повторим.
Все заработало, и результат у нас получился точно такой же.
Но какие минусы в текущем варианте?
Самый очевидный из минусов — это то, что у нас различное поведение
классов-потомков необходимо переносить в базовый класс.
Соответственно, в нашем случае у нас тривиальная печать,
но на самом деле там может оказаться гораздо более сложная логика,
которую нам придется поддерживать в одном месте,
и, соответственно, разделять ее различными проверками на тип.
В данном случае у нас всего одно поле «тип»,
но на самом деле различных классификаций может быть больше.
Это, на самом деле, довольно неудобно, но с этим можно как-то жить.
Давайте дополним наше семейство животных
и добавим класс «попугай», Parrot.
[ЗВУК] Как известно,
попугаи гораздо более разговорчивы, чем кошки и собаки.
Они при должном умении могут даже произнести собственное имя.
Во-первых, давайте мы дадим попугаю это имя.
Соответственно, в конструкторе класса Parrot мы
будем передавать строчку.
[ЗВУК] И
в нашем случае попугай у нас будет прямо говорить,
то есть изначально у него должна была быть функция Talk.
И говорить он будет следующее.
Он будет говорить, что он сам хороший.
[ЗВУК] Вот.
А теперь давайте попробуем опять произвести манипуляцию
с переносом логики из класса-потомка в базовый класс.
Добавляем проверку
на тип «попугай» и пытаемся вывести его
имя.
Так, эта функция нам здесь больше не должна понадобиться.
Мы забыли вызвать конструктор базового класса.
Давайте его вызовем здесь.
Итак, проверяем.
Давайте для начала попробуем собрать.
И у нас это не получается.
То есть мы, даже не создавая объект типа «попугай», уже обнаружили ошибку.
В чем же она заключается?
Она заключается в том, что в базовом классе не известно поле name,
которое является приватным для класса «попугай».
В языке C++ из базового класса мы никаким образом не можем добраться
до приватных полей классов-потомков, иначе бы это нарушало инкапсуляцию,
то есть сокрытие данных и деталей внутренней реализации.
Таким образом, текущих наших знаний недостаточно,
чтобы разрешить проблему с тем,
чтобы перенести функцию Talk из класса «попугай» в класс Animal.
О том, как же нам справиться с этой проблемой, мы расскажем в следующем видео.