В предыдущем видео мы рассмотрели, как работает наследование и множественное наследование в классах Python. Как я уже говорил, если создать достаточно большую иерархию классов и использовать множественное наследование, а также большое количество классов-примесей, то итоговый код может показаться очень сложным, и его будет очень трудно читать программисту и разбираться, как он работает, что делает его менее выразительным. В Python существует альтернативный подход наследованию — это композиция. В этом видео мы с вами на примере разберем, как работает композиция, и вы сможете сравнить, какой из подходов вам больше нравится — наследование или композиция. Давайте немного вспомним классы из предыдущего видео. У нас был класс "питомец", мы от него унаследовали класс "собачку" (класс Dog). Затем мы захотели, чтобы наши объекты классов "собака" могли выполнять экспорт данных, и мы ввели класс-примесь ExportJSON. После этого наш финальный класс ExDog использовал множественное наследование и наследовался от класса "собачка" и ExportJSON, тем самым он решал все наши задачи. Давайте представим, что нам понадобится экспортировать данные не только в формате json, но и еще в другом формате, например, пусть будет это XML. Как тогда изменится структура нашей программы? Тогда, очевидно, нам нужно будет добавить еще один класс-примесь, и тогда наш код будет выглядеть так, как показано на слайде, то есть пока не вдаемся в подробности реализации самих классов-примесей. У нас появился наш новый класс-примесь ExportXML, у него есть свой собственный метод to_xml, и наш итоговый класс ExDog теперь наследуется уже от трех классов-родителей. Тем самым объекты этого класса ExDog смогут экспортировать данные как в json, так и в xml. Давайте представим, что нам нужно будет добавлять еще несколько методов для экспорта данных. Какие сложности могут возникнуть в таком случае? Во-первых, нам постоянно придется изменять код нашего класса ExDog, постоянно дописывать туда новые классы-примеси. Во-вторых, это уже сильно усложнит сам код, и в итоговой программе нам нужно будет вызывать разные методы этих классов-примесей, то есть мы будем постоянно что-то изменять в своей программе. И это уже не то, чего хотелось бы нам достичь. Давайте попробуем рассмотреть, как в таком случае работает композиция. Для этого нам понадобится Jupyter Notebook. Давайте попробуем решить эту задачу немного другим способом. Предположим, у нас будет класс для экспорта. Давайте объявим его. Пусть он будет называться PetExport и у него будет один метод, export. Обратите внимание, я сейчас использовал генерацию исключения. Об исключениях мы с вами будем говорить в последующих видео, а пока вам нужно для себя усвоить, что мы не будем создавать объекты данного класса PetExport и он предназначен только для наследования. Давайте объявим другие классы, которые будут заниматься экспортом данных. JSON. Также наш класс export должен принимать сам объект, который он будет экспортировать. Пока опустим реализацию самого экспорта. Также добавим класс для экспорта данных в формате xml. Итак, у нас готова иерархия классов для экспорта данных. Давайте теперь вспомним наши классы. "Питомец", у которого у каждого питомца было имя, мы его сохраняли в атрибуте name, и также класс "собачка", у которого появился дополнительный атрибут порода, breed, и мы сохранили его в одноименном атрибуте. Давайте теперь объявим класс ExDog. Теперь мы не будем использовать свое множественное наследование, мы будем расширять существующий класс Dog. Переопределим инициализатор. Он тоже будет принимать на вход имя питомца, породу, а также дополнительный объект для экспорта данных. Вызовем конструктор базового класса и сохраним объект exporter в self. Обратите внимание, что в данном классе мы не используем наследование. Вместо этого мы используем композицию и передаем нужный объект для экспорта в инициализаторе этого класса. Давайте добавим ему метод export, и теперь наш класс сможет экспортировать данные. Делать он это будет при помощи нашего exporter'а. Передадим ему self для экспорта. Давайте попробуем скомпилировать код. У нас получилось. Давайте попробуем создать экземпляр нашего класса ExDog. Пусть это будет собачка "Шарик", порода "Дворняга". Предположим, мы хотим, чтобы объект этого класса умел экспортировать свои данные в xml. Давайте передадим нужный exporter. Обратите внимание, что при использовании композиции нужный объект создается именно в момент выполнения уже конкретной программы. Давайте попробуем выполнить метод export. Итак, у нас получилось. Осталось реализовать только методы для экспорта в начальной иерархии классов. Давайте сделаем это. С json все просто, мы уже разбирали пример, используя модуль json и метод dumps. Давайте реализуем теперь метод export в классе ExportXML. Реализация может выглядеть достаточно просто. Создаем сам xml. В нем будут объекты name и порода нашей собаки. Всё, осталось отформатировать данную строку при помощи метода format. Давайте попробуем скомпилировать и выполнить наш код. Итак, у нас получилось. Точно так же мы с легкостью можем сделать экспорт данных и в формате json. Можем создать другую собачку. Пусть это будет "Тузик" другой породы. Однако, неудобно, если каждый раз задавать метод для экспорта. Давайте немного изменим наш класс ExDog и зададим метод для экспорта по умолчанию. Можно это сделать следующим образом. Давайте выполним еще одну важную вещь. Мы проверим на то, является ли переданный объект экземпляром класса PetExport и вообще может ли он выполнять экспорт данных. Для этого мы можем воспользоваться проверкой isinstance. Нам нужен наш exporter и указываем класс. Что делать, если нам передали объект, который не может выполнять экспорт? Давайте пока сгенерируем исключение. Для вас это будет означать, что программа дальше не сможет продолжить свою работу и будет остановлена. Например, ValueError нам подойдет. Давайте скомпилируем снова измененный код, попробуем выполнить. Так. Действительно, нам нужно в итоговую иерархию добавить класс PetExport. Давайте попробуем еще раз скомпилировать. У нас получилось. Теперь, если мы не объявим объект exporter, по умолчанию наша собачка будет экспортировать данные в json. Опять у нас снова получилось. Давайте еще раз посмотрим на то, какой код у нас получился, и подумаем, что с ним произойдет, если его нужно будет расширить. Предположим, если нам нужно будет добавить в этот код новый метод для экспорта, мы с легкостью сможем сделать это. Просто объявим новый класс, добавим его в существующую иерархию для экспорта, а класс ExDog теперь мы больше менять не будем. Таким образом, если у нас система будет усложняться, наш класс ExDog будет оставаться неизменяемым или неизменным. А экспортировать в различные форматы мы уже сможем легко и удобно в итоговой программе, подставив нужный exporter или создав его. Не пугайтесь, если вам сейчас непонятны плюсы данного подхода по отношению к наследованию. Чем вы больше будете программировать на Python, пробовать и реализовывать различные подходы, тем вам станет понятно, где действительно лучше применять наследование, а в каких местах следует использовать композицию. До встречи в следующем видео.