LINUX.ORG.RU

Полиморфизм? - Можно кормить Зайку мясом?

 


0

3

Какие принципы ООП вы знаете?

Их четыре:
▫️наследование;
▫️инкапсуляция;
▫️полиморфизм;
▫️абстракция.

🔹Наследование
Наследование позволяет новому классу наследовать атрибуты и методы уже существующего класса. Новый класс называется производным (дочерним). Существующий — базовым (родительским).

🔹Инкапсуляция
Этот принцип заключается в ограничении доступа к внутренним методам и переменным класса извне. В Python принцип реализован лишь на уровне соглашений: приватные атрибуты выделяются подчёркиванием — одинарным _ или двойным __. Эти подчёркивания сигнализируют другим программистам о приватности. Однако доступ к ним всё равно можно получить. 

🔹Полиморфизм
Полиморфизм позволяет использовать одну функцию для разных форм (типов данных). В Python это проявляется, например, когда дочерний класс переопределяет методы родительского класса или когда разные классы имеют методы с одинаковыми именами, но собственной реализацией.

🔹Абстракция
Абстракция позволяет определить общее поведение для группы объектов. Это достигается путём создания классов, которые имеют некоторые общие свойства и методы, но не включают все детали реализации.

#вопросы_с_собеседований

Наследование - всё ясно. Из одного Зайки - можно сделать второго. Гораздо большего размера с разным цветом глаз - Свойства

Инкапсуляция - Зайку резать нельзя. Можно кормить - вход. Убирать дерьмо - выход. Зайка может прыгать - Методы.

Полиморфизм? - Можно кормить Зайку мясом? Или можно «прикрутить» к нему крылья?

Абстракция? - Зайка и Крокодил могут прыгать вместе? - Используя одно и тоже Свойство?

Перемещено leave из general



Последнее исправление: sokolov (всего исправлений: 1)

Ответ на: комментарий от monk

с протоколами получается что-то вроде

Множество вариантов есть с протоколами, можно оптимизировать по любому критерию, в том числе и по краткости.

в кложе придётся копипастить

Зачем копипастить, есть же обычная, стандартная, скучная композиция функций. Прошу заметить, не приколоченная к диспетчеризации %)

Хочешь наворотить в своей функции адовый конвейер обработки данных, с :before, :after, :around, :maybe и :dammit? На здоровьичко, как грится. Не хочешь? Ну и не делай, никто не заставляет. Свобода.

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 4)
Ответ на: комментарий от Nervous

Зачем копипастить, есть же обычная, стандартная, скучная композиция функций

А как? У нас есть insert для tree. Но передать balanced-tree туда нельзя.

monk ★★★★★
()
Ответ на: комментарий от monk

Для каждого defrecord frob добавить defprotocol Frob. Чтобы можно было в foo требовать протокол.

Не, это не так работает. Один раз определяем протокол (по-нашему, по-крестьянски, это будет интерфейс), и много раз его реализуем, для разных типов. В том числе для тех, которые не можем изменять (скажем, из сторонней библиотеки).

Есть у нас тип Bar, мы хотим его передать в foo. Говно вопрос, реализуем для него протокол Frob, готово — foo работает с Bar (потому что он теперь ещё и немножечко Frob).

Можем реализовать Frob для хреновой тучи разных типов, можем реализовать хренову тучу разных протоколов для Bar. Open-closed principle, однако.

Nervous ★★★★★
()
Ответ на: комментарий от Nervous

Кстати, ещё одна проблема есть. Если tree и balanced-tree делают разные люди, то мы получаем жёсткую зависимость по именам полей. Если разработчик базового класса переименует поле, то у всех «наследников по протоколу» поломаются все унаследованные методы.

monk ★★★★★
()
Ответ на: комментарий от monk

получаем жёсткую зависимость по именам полей. Если разработчик базового класса переименует поле, то у всех «наследников по протоколу» поломаются все унаследованные методы

Они у любых наследников поломаются, по протоколу или по иерархии классов. Лекарство в обоих случаях одинаковое — не завязываться на имена полей (и вообще конкретную структуру данных), а завязываться на её интерфейс %)

Да, интерфейсы внутри интерфейсов, all the way down.

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 1)
Ответ на: комментарий от Nervous

Не, это не так работает. Один раз определяем протокол (по-нашему, по-крестьянски, это будет интерфейс), и много раз его реализуем, для разных типов. В том числе для тех, которые не можем изменять (скажем, из сторонней библиотеки).

Вот наш balanced-tree. У него есть функция rebalance, которой нет у tree. Есть функция bar, которая что-то делает с balanced-tree и выполняет rebalance. Ей надо дать аргумент с типом-протоколом. Протокол Tree давать нельзя, так как в нём нет rebalance. То есть нужен некий Balanced-tree. Кстати, методы в нём все дублировать или только новые?

monk ★★★★★
()
Ответ на: комментарий от Nervous

Они у любых наследников поломаются, по протоколу или по иерархии классов.

По иерархии классов они не пишутся в наследнике, поэтому не поломаются.

не завязываться на имена полей (и вообще конкретную структуру данных), а завязываться на её интерфейс

Так речь именно при реализацию интерфейса. DefaultTree очевидно будет реализовывать обработку через имена полей, так как в этот момент никакого другого интерфейса нет. А если имена полей в tree (и в DefaultTree поменяются), то balanced-tree получить сломанную реализацию интерфейса.

monk ★★★★★
()
Ответ на: комментарий от monk

Вот наш balanced-tree. У него есть функция rebalance, которой нет у tree

Это значит, что у них разные интерфейсы (протоколы). Можно объявить протокол Tree (N функций) и протокол Balanced (одна функция rebalance).

Есть функция bar, которая что-то делает с balanced-tree и выполняет rebalance. Ей надо дать аргумент с типом-протоколом. Протокол Tree давать нельзя, так как в нём нет rebalance. То есть нужен некий Balanced-tree. Кстати, методы в нём все дублировать или только новые?

Чтобы bar работала с каким-то значением, достаточно, чтобы его тип реализовал протоколы Tree и Balanced — то есть чтобы к нему можно было успешно применить методы этих протоколов и получить осмысленный результат. Что это значение представляет собой «на самом деле» (тм), каков его «истинный тип» — её не колышет. Что за глупости вообще, какой истинный тип, there is no such thing — мы делим вещи по типам исходя из того, что собираемся с ними делать, у них нет никаких внутренне присущих им типов. Для одной задачи дерево — это стройматериал, для другой — топливо, для третьей — развлекательный аттракцион.

Таким образом, дублировать ничего не надо — берём наш тип, реализующий Tree, реализуем для него ещё и Balanced (+1 функция) — вуаля, bar работает с ним.

В языках со статической типизацией, наверное, придётся ещё немного поприседать, чтобы ублажить систему типов — к счастью, в кложе типизация динамическая.

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 2)
Ответ на: комментарий от Nervous

Таким образом, дублировать ничего не надо — берём наш тип, реализующий Tree, реализуем для него ещё и Balanced (+1 функция) — вуаля, bar работает с ним.

Вот это я и имел в виду, что для каждой структуры будет дополнительно свой протокол (ведь если нет новых функций, зачем сделали структуру?).

monk ★★★★★
()
Ответ на: комментарий от monk

для каждой структуры будет дополнительно свой протокол

Протокол — это просто именованный набор функций. Класс — это тоже своего рода протокол, именованный набор методов. Ничего не мешает переиспользовать функции из одного протокола в другом точно так же, как методы одного класса переиспользуются в другом с помощью наследования или композиции.

Точно так же можно предъявить ООПшникам, что у них на каждый задуманный класс надо дополнительно, кхем, этот самый класс ещё и написать.

Nervous ★★★★★
()
Ответ на: комментарий от Nervous

Протокол нужен для того, чтобы когда внутри bar встретится rebalance или insert, он был вызван именно для того типа, который передан в bar. А так как потенциально пользователи библиотеки структуру-наследник могут объявить для любой структуры, то протокол надо также делать для каждой структуры. И еще публиковать DefaultTree, DefaultBalanced, … чтобы при наследовании не приходилось копипастить.

monk ★★★★★
()
Ответ на: комментарий от Nervous

Точно так же можно предъявить ООПшникам, что у них на каждый задуманный класс надо дополнительно, кхем, этот самый класс ещё и написать.

В классе меньше строк, чем в defrecord + defprotocol + Default*.

monk ★★★★★
()
Ответ на: комментарий от monk

Протокол нужен для того, чтобы когда внутри bar встретится rebalance или insert, он был вызван именно для того типа, который передан в bar.

Так он и будет вызван именно для того типа, для которого ты (или не ты) реализовал Tree и Balanced, а потом создал значение этого типа и передал в bar. Или для другого типа, который реализовал Вася из соседнего кабинета — но тоже реализующего Tree и Balanced. У протоколов диспетчеризация по типу первого аргумента, да.

Если ты передашь тип, который какой-то из этих протоколов не реализует, bar сломается.

Ну я догадываюсь, чего ты от меня хочешь — чтобы я сказал, какой тип первого аргумента указать в сигнатуре bar. Ну что тут можно сказать %)

Nervous ★★★★★
()
Ответ на: комментарий от monk

В классе меньше строк, чем в defrecord + defprotocol

Можно ещё попробовать класс зафигачить в одну строку, а defprotocol по одному символу в строке — так-то надёжнее будет! %)

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 1)
Ответ на: комментарий от monk

пользователи библиотеки структуру-наследник могут объявить для любой структуры, то протокол надо также делать для каждой структуры

Новая структура, новые возможности — новый интерфейс. Логично же.

Nervous ★★★★★
()
Ответ на: комментарий от monk

По иерархии классов они не пишутся в наследнике, поэтому не поломаются

Ты когда метод переопределяешь, к полям класса в нём принципиально не обращаешься? Если обращаешься (а почему нет, доступны же) и родитель их изменит, твой наследник сломается, разве нет?

Nervous ★★★★★
()
Ответ на: комментарий от Nervous

Ну я догадываюсь, чего ты от меня хочешь — чтобы я сказал, какой тип первого аргумента указать

Нет. Я обосновываю тезис «то протокол надо также делать для каждой структуры».

monk ★★★★★
()
Ответ на: комментарий от Nervous

Ты когда метод переопределяешь, к полям класса в нём принципиально не обращаешься?

К родительским? Нет конечно. К родительскому классу только через интерфейс. Если добавляю поле, то к своему полю обращаюсь напрямую (хотя бы для того, чтобы интерфейс доступа к нему сделать).

а почему нет, доступны же

В Common Lisp доступно всё. Но есть правила хорошего тона: не обращаться напрямую к полям объекта и к неэкспортированным символам пакета.

monk ★★★★★
()
Ответ на: комментарий от Forum0888

Десятки гетов, сетов, ... ИМХО сильно портят readable текст кода.

В ООП на каждую разновидность чиха нужна функция.
В процедурном программировании достаточно одной функции с switch.
ИМХО код много readable, а это весьма хорошо.
Хороший пример, подтверждающий суждение, код проекта harbour.

Впрочем суждение сугубо индивидуальное.
Главное «чтобы код был хороший».

Одно знаю точно - нужно разработкой заниматься.

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 6)
Ответ на: комментарий от Forum0888

Стойкое ощущение, что ты понятия не имеешь, о чём вообще говоришь.

В процедурном программировании достаточно одной функции с switch.

Ну напиши процедурно ядро ОС с одной функцией с switch, я посмотрю. Или ладно, не ядро, что-нибудь простое. Текстовый редактор, музыкальный плеер, что там ещё модно переписывать гуёвого. В общем, что угодно, что сложнее утилитки из coreutils.

P.S. Если что, я и сам считаю, что ООП overused.

CrX ★★★
()
Последнее исправление: CrX (всего исправлений: 1)
Ответ на: комментарий от CrX

Ну напиши процедурно ядро ОС с одной функцией с switch, я посмотрю.

Есть ядро линукса. Он написан на Си, в котором нет ООП.

И вот пример: https://github.com/torvalds/linux/blob/dccb07f2914cdab2ac3a5b6c98406f765acab803/fs/ramfs/inode.c#L69

На языке с ООП здесь было бы inode.init(mode, dev), а для конкретных подтипов (каталог, ссылка) реализация была бы в классах-наследниках от базового inode.

monk ★★★★★
()
Ответ на: комментарий от monk

Есть ядро линукса. Он написан на Си, в котором нет ООП.

Я в курсе. Я вообще ни слова в своём сообщении про ООП не сказал. Кроме PS.

Утверждалось, что:

В процедурном программировании достаточно одной функции с switch.

В ядре линукса одна функция с switch? Нет.

Неужели придётся прям разжёвывать, почему это утверждение — несусветная чушь?

CrX ★★★
()
Ответ на: комментарий от CrX

     switch ( pFunc->pCode[ nPos ] ) {

      case META_P_LINE:

       ulLine = META_PCODE_MKUSHORT( &pFunc->pCode[ nPos + 1 ] );
       break;

      case META_P_MODULENAME:

       pszModuleName = (char *) &pFunc->pCode[ nPos + 1 ];
       pInfo = NULL;
       break;

      
// This enables checking also code block bodies,
//     if it's not necessary then simply remove the code below. [ druzus ]
//
      case META_P_PUSHBLOCKLARGE:

       nSkip = 8 + META_PCODE_MKUSHORT( &pFunc->pCode[ nPos + 6 ] ) *2;
       break;

      case META_P_PUSHBLOCK:

       nSkip = 7 + META_PCODE_MKUSHORT( &pFunc->pCode[ nPos + 5 ] ) *2;
       break;

      case META_P_PUSHBLOCKSHORT:

       nSkip = 2;
       break;

     }

Так понятно?

Forum0888
()
Ответ на: комментарий от monk

По контексту же должно быть понятно.

OK, сойдёмся на том, что мне конкретно сообщения Forum0888 трудно понять. В том числе зачастую их контекст. Не только в этом треде, в целом.

CrX ★★★
()
Ответ на: комментарий от monk

Именно так.

Разве у меня не об этом пост был?

Ещё раз акцентирую (правда сугубое мнение) - логика кода на ООП не readable.

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 1)
Ответ на: комментарий от Forum0888

логика кода на ООП не readable

Спорно. Без ООП отличия обработки обычного файла от каталога размазаны по 89 местам. При наличии ООП они были бы все компактно в одном месте.

В целом, если новые виды объектов появляются очень редко (как в примере с файлом), то логично использовать switch. Если же предполагается, что они появляются регулярно (например, драйвер торгового оборудования), то лучше ООП, так как при появлении нового драйвера не забыть добавить ветку во всей сотне мест, где он используется гораздо сложнее, чем компактно описать класс драйвера.

С другой стороны, если предполагается, что часто появляются новые действия, которые по-разному работают для разных типов, то опять лучше switch. Так как всё действие в одной функции, а не кусочками по всей иерархии.

monk ★★★★★
()
Ответ на: комментарий от monk

Да, всё зависит от задачи.

Разрабатываемое API позволяет писать обобщённый код.

Расскажу маленькую историю из разработки.

Первая вариант кода, позволяющий расспарсить все объекты
конфигурации на 1С 7.7 был процедурный (где-то 3000 строк).
Обобщённый код менее 500 строк.

Или вот другой пример.
Попробывал использовать API для работы с doc, xlsx, ... Microsoft Office.
Разработал обобщённую функцию, которая путём анализа сотни xlsx
смогла сформировать все struct, которые позволяют использовать xlsx.

Всё работает ok!

А для загрузки в memory скажем xlsx вообще ни строчки кода не нужно разрабатывать потому, что имеются метаданные о xlsx.

PS: Вот поэтому к ООП ныне «не тянет».

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 3)
Ответ на: комментарий от rtxtxtrx

Я слово «сокрытие» вообще не использовал бы, оно реально путает, как и слово «прозрачный», хотя под «прозрачным» подразумевают именно сокрытие деталей реализации.

LongLiveUbuntu ★★★★★
()
Ответ на: комментарий от LongLiveUbuntu

Ну таким образом, если отойти от ереси джабофагов, то:

  • Абстракция - это выделение каких-то общих свойств у однотипных объектов (а не все эти ваши интерпейсы)
  • Инкапсуляция - объединение данных и методов для работы с ними в классы (а не модификаторы доступа public/private/protected)
  • Наследование - возможность расширять классы
  • Полиморфизм - а вот ему определение универсальное сложно дать… В рамках жООПэ, это связано с наследование, где есть класс Animal от которого наследуются Cat и Dog, дергается один и тот же метод say, и кот говорит meow, а собака - bark

Остается лишь не нарваться на фОната Мартина у которого свое понимание

rtxtxtrx
()
Последнее исправление: rtxtxtrx (всего исправлений: 2)
Ответ на: комментарий от rtxtxtrx

Полиморфизм - а вот ему определение универсальное сложно дать…

Возможность использовать один алгоритм для разных структур данных с похожим интерфейсом.

И тогда сюда попадает хоть утиная типизация на шаблонах, хоть классы типов хаскеля, хоть динамическая типизация, хоть ООП.

monk ★★★★★
()
Ответ на: комментарий от monk

Проблема таких определений в том, что интерфейс - это структура данных у еретиков, а подразумевается интерфейс как просто класс, набор классов с которыми ты взаимодействуешь, не вникая во внутреннию реализацию. У него тема про ООП в Python. А значит тут про переопределение методов с одинаковыми именами. Короче, я был не прав. В Python есть все, я просто про эту шизу никогда не задумывался, но когда про инкапсуляцию начинают рассказы про _ и __ — это говорит лишь, что те кто этому учат сами ничего не понимают в теме. Определения всех этих терминов есть в естественных науках от химии и биологии до философии. И там они ближе к искомым чем те что рачье тащит из джавы, да еще всякие шкиллбоксы и подобные невежество лишь приумножают

rtxtxtrx
()
Последнее исправление: rtxtxtrx (всего исправлений: 1)
Ответ на: комментарий от rtxtxtrx

Определения всех этих терминов есть в естественных науках от химии и биологии

Ну так Alan Kay:

He attended Brooklyn Technical High School. Having accumulated enough credits to graduate, he then attended Bethany College in Bethany, West Virginia, where he majored in biology and minored in mathematics.

Собсна, Smalltalk был придуман по подобию клеток и их взаимодействия.

Смешно, что ты продолжаешь вещать в темах, являясь, скажем мягко, нубом, высокопарно накидывая…

Дядюшка Боб - это хто?

Продолжай. Без тупых эти треды были бы не так радостны.

masterOf
()
Ответ на: комментарий от masterOf

Смешно, что ты продолжаешь вещать в темах, являясь, скажем мягко, нубом, высокопарно накидывая…

Зато ты ненуб с опытом быдлокодинга в полгода. Юношеский максимализм - он такой. Армия вылечит или покалечит

anonymous
()
Ответ на: комментарий от monk

У тебя причинно-следственные связи в сообщении не прослеживаются.

Можно сделать с выносом в виртуальный метод, можно сделать на свитче. Это зависит от того, как лучше сделать в данном конкретном случае. А не от того, есть в языке встроенные средства ООП или нет.

wandrien ★★
()