Некоторое время назад пытался устроиться на подобные вакансии (меня не взяли). Но теперь есть список вопросов, которые помогут подготовиться и углубиться в мир программирования.
(список вопросов взят из самого долгого интервью, остальные были в разы короче)
Список вопросов
Что такое Game Object? Что такое scene?
Что такое canvas? Где и для чего его нужно использовать?
Что такое MonoBehaviour? От чего он наследуется?
Расскажите Жизненный цикл MonoBehaviour.
В каком порядке вызываются события MonoBehaviour в процессе рендеринга кадра?
Зачем нужны отдельные Update и FixedUpdate?
Что такое deltaTime и fixedDeltaTime? Отличия между ними.
Для чего нужны корутины и какую проблему они решают?
Какие компоненты позволяют работать с физикой?
Какие знаете типы камер и параметры для их настройки?
Зачем нужен аниматор?
Можно ли дописывать логику к состояниям аниматора?
Для чего нужны нормали?
Что такое mesh?
Какие данные в себе хранит меш?
Опыт работы с шейдерами. Приходилось ли писать шейдеры?
Что такое drawcall, batching? Какие возможности предоставляет Unity?
Для чего используется асетс бандл?
Какие инструменты для диагностики проблем производительности вы знаете(profiler, deep profiling, frame debugger, memory profiling, profiling on device)?
Unity Web Requests. Что это? Приходилось ли работать с клиент-серверным взаимодействием? Если да, то расскажите про этот опыт.
Есть ли опыт работы с нативным слоем? Android Studio, XCode.
Есть ли опыт интеграции SDK(реклама, аналитика, конфиги, БД, пуш уведомления)?
Какие типы данных вы знаете?
Что такое Nullable-тип?
Что такое Array, List, HashSet, Dictionary? Приведите примеры использования этих структур данных.
Какая разница между Array, List?
Как работает Dictionary, почему он работает быстрее чем List?
Можно ли хранить разные типы данных в объекте класса System.Array?
Что такое Key-value структуры?
Что такое enum flags?
Что такое ООП?
Основные принципы ООП и рассказать про каждый принцип с примерами.
Как вы понимаете SOLID (СОЛИД)?
Какие знаете паттерны? Расскажите про каждый из перечисленных.
Как можно получить доступ к private переменной из другого класса?
Чем абстрактный класс отличается от интерфейса?
В чем различие между классом и структурой?
Почему в структуре нет конструктора по умолчанию?
Может ли класс реализовать два интерфейса, у которых объявлены одинаковые методы? Каким образом?
Какая разница между перегрузкой и переопределением методов?
Что такое Boxing и Unboxing?
Что означает модификатор virtual?
Что обозначает ключевое слово “virtual” для метода или свойства?
Что такое immutable object? Какие преимущества дает использование immutable object?
Как создать собственный immutable-тип?
Чем отличается event от delegate?
Что такое замыкание?
Кому доступны переменные с модификатором protected на уровне класса?
Можно ли наследовать переменные с модификатором private?
Когда использовать StringBuilder, а когда string? Как работает StringBuilder?
В чем разница между throw ex; и throw;?
В чём разница между ключевыми словами final, finally, finalize?
Какая разница между checked и unchecked Exception ?
Что такое дженерики? Какие проблемы они решают?
Приведите пример использования Graphs и Trees.
Что такое коллизии и как с ними бороться?
В чем разница между IEnumerable и IQueryable?
Что такое рефлексия? Расскажите где она используется и для чего.
ООП, interface, SOLID
Если ты, как и я обучался по видосикам на етубе, то вероятнее всего не слышал о подобных БАЗОВЫХ понятиях, звучат они страшно и непонятно, но на самом деле, все куда проще чем кажется. В сети много информации по этим вопросам, однако все они разрознены.
Пока читаешь одно, другое уже забывается, поэтому решил собрать свой вариант буклета, который должен помочь быстрее въехать в эти странные понятия.
ООП
(развернутая информация дальше по ссылкам)
(ссылки оставляю полные, если так неудобно, пишите в комментарии - сокращу)
(маленькие статьи про все принципы ООП)
https://habr.com/ru/post/87205/ - ООП с примерами (словесные примеры, без кода)
https://vertex-academy.com/tutorials/ru/chto-takoe-oop/ - (словесные примеры, без кода)
https://training.epam.ua/#!/News/275?lang=ru - ООП с примерами (немножко кода) https://www.youtube.com/watch?v=od_hbf_sdvU - (видео) примеры принципов ООП
(большие текстовые статьи с кучей примеров - отдельно про каждый принцип)
- инкапсуляция
https://skillbox.ru/media/code/oop_chast_3_modifikatory_dostupa_inkapsulyatsiya/
https://metanit.com/sharp/tutorial/3.2.php
- полиморфизм
https://habr.com/ru/post/37576/
https://skillbox.ru/media/code/oop_chast_4_polimorfizm_peregruzka_metodov/
https://qna.habr.com/q/319480 - (первый комментарий) текстовое описание разных видов
https://medium.com/devschacht/polymorphism-207d9f9cd78 - в целом про полиморфизм (и его разные виды)
- наследование
https://skillbox.ru/media/code/oop_chast_5_polimorfizm/
https://ppt-online.org/22757 - разбор большого примера
(дополнительно)
https://forum.unity.com/threads/c-overriding-interface-virtual-usage-situation.245880/ -
(англ) некоторое объяснения понятий - interface, internal, virtual, override, abstract
Interface
(дальше идет много статей, практически об одном и том же, но в каждой есть что-то новое)
(маленькие статьи)
https://thecode.media/oop-abstract/ - “зачем нужны абстракции и интерфейсы?”
https://metanit.com/sharp/tutorial/3.9.php - короткий текст с примерами
https://zen.yandex.ru/media/zdgzdgzdg/oop-interfeisy-5eebc544f4871a7b4d9a2c3f - короткий текст с примерами
https://skillbox.ru/media/code/oop_chast_6_abstraktnye_klassy_i_interfeysy/ - короткий текст с примерами
(большие статьи)
https://vectree.ru/text/132/4/0 - большой текст с примерами
https://highload.today/zachem-v-c-nuzhny-interfejsy/ - большой текст с кучей примеров
https://shwanoff.ru/practice-interface-csharp/ - большой текст с другими примерами
https://circuitstream.com/blog/learn-c-for-unity-lesson-6-inheritance-and-interfaces/ - (англ) - большой текст
https://ru.stackoverflow.com/questions/136909/Интерфейсы-в-ООП-java-по-простому - много разного о интерфейсах
(видео)
https://www.youtube.com/watch?v=50_qBoKGKxs - (англ) туториал от юнити
https://www.youtube.com/watch?v=2LA3BLqOw9g - (англ) interactble
https://youtu.be/MZOrGXk4XFI?t=503 - (англ) пример с пулями и IDamageble
https://www.youtube.com/watch?v=qkwGpXtUvIs - (англ) пример IClickable
(дополнительно)
https://www.youtube.com/watch?v=gb3r__izKuI - отличие интерфейса от абстрактного класса
SOLID
(супер упрощённое описание принципов)
https://itnan.ru/post.php?c=1&p=353840 - короткие текстовые описания принципов
https://web-creator.ru/articles/solid - другие короткие текстовые описания
https://habr.com/ru/company/productivity_inside/blog/505430/ -текстовые описания с картинками
https://professorweb.ru/my/it/blog/net/solid.php - средняя статья с примерами
https://metanit.com/sharp/patterns/5.1.php - несколько статей с примерами (кнопка вперед)
https://ru.stackoverflow.com/questions/900455/Принципы-solid-доступным-языком-на-ПРОСТЫХ-примерах-кода-С - еще примеры
https://en.ppt-online.org/543101 - слайды с примерами
https://docs.microsoft.com/ru-ru/archive/msdn-magazine/2014/may/csharp-best-practices-dangers-of-violating-solid-principles-in-csharp - Опасность нарушения принципов SOLID в C#
(на мой взгляд один из самых сложных принципов (Барбары Лисков) для понимания, поэтому дополнительный небольшой текст с примерами)
https://telegra.ph/Princip-podstanovki-Barbary-Liskov-predusloviya-i-postusloviya-04-24 -
Принцип подстановки Барбары Лисков (предусловия и постусловия)
(видео)
https://www.youtube.com/watch?v=TxZwqVTaCmA - длинное видео с кучей примеров
https://www.youtube.com/watch?v=ll6bxQGkyCk - (англ) разбор большого примера
https://www.youtube.com/watch?v=_yf5vzZ2sYE - (англ) часть0 - Selecting Objects with Raycast
https://www.youtube.com/watch?v=QDldZWvNK_E - (англ) часть1 - рефакторинг в SOLID
https://www.youtube.com/watch?v=Fs8jy7DHDyc - (англ) часть2 - продолжение рефакторинга
(полезное видео для ознакомления)
https://www.youtube.com/watch?v=jqeoq6X-5S8 - "От джуниор/инди разработчика до мидл+"
P.S.
Какие у меня были тестовые задания.
Анимация передвижения и рагдольное падение.
Краткое описание:
Человек стреляет в другого человека. Пуля дает импульс в место попадания. Рагдольное падение. Передвижение.
Полное описание:
! На небольшой поверхности находится герой, враг и башня в разных местах. ! При нажатии мышкой в любое место карты, герой бежит в это место.
! Если герой забегает на верх башни (и может попасть во врага), то его режим переключается в стрельбу.
! При нажатии на экран в любое место из вытянутой вперед руки игрока вылетает пуля и летит в место нажатия на экране.
! Если зажать экран, пули летят без перерыва, направление руки меняется соответственно.
! Когда пуля врезается во врага, должен произойти удар в место попадания пули, который вызовет рагдольное падение врага.
! При желании можно добавить выброс партиклов на выстрел.
Важные примечания:
! ТЗ будет проверяться в эдиторе версии 2020.1.. (не используйте альфа и бета релизы юнити).
! Башню для задания можно найти по ссылке: (была приложена ссылка)
! Необходимо сделать кнопку, которая перезапустит уровень.
! Необходима 3д модель для игрока и врага. Можно использовать любые бесплатные модели и анимации.
Пример ресурсов для моделей и анимаций:
https://assetstore.unity.com/packages/3d/characters/humanoids/zombie-30232 http://mixamo.com/
! Камеру настройте на свой вкус + играбельно.
Мы делаем игры, поэтому ценим творческий подход не меньше, чем четкую реализацию =)
Оружее в двух руках + "магия".
Необходимо сделать десктопный прототип.
Игрок может ходить.
Рядом с игроком в воздухе висит пара пистолетов и две пары камней: огненные и водные.
Пистолет может стрелять и наносить 20 урона за простое попадание (хит-скан или прожектель, не важно, как будет удобно), за попадание по мокрому на 10 урона меньше и на 10 больше за попадание по горящему.
При помощи левой кнопки мыши можно использовать предмет в левой руке. С правой аналогично.
Он может брать камни или пистолет в левую или правую руку кнопками Q и E соответственно. Повторное нажатие отпускает предмет.
Перед игроком стоит пугало с 1000hp. Если hp кончаются, оно исчезает.
Если нажать на клавиатуре R пугало восстановится или вылечится. HP должно быть видно.
Также пугало может намокать по шкале от 0 до 100 и гореть.
Когда пугало мокрое, оно имеет синий оттенок, когда горит — красный,
*Можно намутить hp-bar и шкалу мокрости, меняющий цвет в зависимости от оставшегося hp.
Огненный камень позволяет пускать огонь из руки (простенькая система частиц с эмиссией в 10 частиц/секунду). Попадая по врагу частица наносит 1 урона и поджигает его/обновляет горение. Если цель мокрая, то не поджигает его, а сушит на одну единицу. Когда пугало загорается, оно теряет 5hp/секунду в последующие 10 секунд.
Водный камень выпускает водный шарик, который при касании тушит огонь и мочит цель на 10 единиц. При этом сам шарик исчезает (даже если ничего не намочил). Движется он, как брошенный мячик.
Требуемая версия Unity: 2020.3.20
В каком виде нужно прислать решение: билд + проект на гитхабе + сопроводительный ролик, как это все работает.
Текстовая игра "угадай слово".
Сделать простую quiz-игру на Unity.
Игра должна брать читать текст (был приложен), выделять уникальные слова и предлагать игроку их угадать.
Для угадывания у игрока есть набор букв (английский алфавит) на кнопках, слово закрытое за квадратами (на манер «Поле Чудес»), количество попыток и очки.
Когда игрок нажимает на букву то она исчезает и, если встречается в слове, то соответствующие квадраты открываются.
Если не встречается — то уменьшается число попыток. Если число попыток
становится отрицательным — то игрок проигрывает, показывается сообщение и игра начинается снова с нулевого количества очков.
Если слово полностью угадано — то количество очков игрока увеличивается на количество оставшихся попыток и игра начинается снова.
Доступные буквы и количество попыток так же сбрасываются.
Слова которые были использованы не должны повторятся до тех пор пока игрок не проиграет.
Если подходящих слов больше нет — то игроку сообщается что он прошёл игру и игра начинается снова с полным сбросом очков и использованных слов.
Отдельная настройка указывает какая минимальная длина слова должна быть, слова меньшей длины должны игнорироваться.
Регистр так же игнорируется, слова "the” и “The” считаются одним и тем же.
Для настройки игры должен быть конфиг на основе ScriptableObject где указывается какая минимальная длина слова должна быть и сколько попыток у игрока есть изначально.
Графика и звук не имеют значения. Весь код должен быть написан лично, использовать сторонние ассеты и модули нельзя.
Предоставить проект или в архиве с папками Assets и ProjectSettings
Шарик на бесконечной лестнице
Концепция и функциональность
Игрок это шарик на бесконечной лестнице.
Игрок контролирует шарик нажатием на экран, шарик прыгает через ступеньку, свайпом шарик прыгает в сторону, на шарик сверху нападают “враги” - прямоугольники, при столкновении игрок умирает и игра начинается заново.
Цель игры - пройти как можно больше ступенек.
Дополнительная (необязательная) функциональность:
Разработать топ игроков (игроки вводят свой ник, и могут добавлять свои результаты в общий топ).
Пример :http://apple.co/2mVu2JP
Требования:
Unity 5.4.3, C#
ООП
Результатом должна быть сборка под Android
Время, отведенное на выполнение тестового задания — несколько дней, но не более недели. Нам нужно оценить Ваш результат как разработчика, который не просто пишет код, а решает задачу. Глубину проработки и объем необходимого функционала Вы определяете сами, исходя из возможностей по времени и собственных представлений о том, что минимально необходимо для решения данной задачи. Желаем удачи!
P.P.S.
Если вы заметили ошибку или хотите добавить полезную ссылку или у вас есть более простое и понятное описание понятий или любое другое замечание - пишите комментарий - исправлю/добавлю и скажу спасибо.
Комментарии (42)
TheTryProgrammerName
18.06.2022 20:31Сколько бы умных штук я не читал и видео не смотрел, всегда находится что-то, чего я не знаю. Будем навёрстывать, спасибо за "пинок".
undrAlex Автор
18.06.2022 20:33ох, к сожалению это ощущение будет преследовать нас до конца). Радует что статья уже дает результаты)
oblakooblako
18.06.2022 20:31Интересно узнать у других членов комьюнити, какие им задают вопросы на собеседованиях на мидл позицию. По моему опыту мидлу задают вопросы по работе с конкретными фраемворками, которые использует компания, архитектурные паттерны или математические задачи.
undrAlex Автор
18.06.2022 20:38На данный момент у меня было 4 собеса на мидла, и везде были вопросы в основном про ООП/солид и использование юнити + какие сторонние ассеты приходилось "трогать".
SadOcean
19.06.2022 01:16+1Вполне компетентно, такие вопросы задавали мне, нечто похожее задавал Я.
Обычно люди не владеюсь всем аспектом тем, поэтому есть уточняющие вопросы.
Например, если человек говорит, что писал шейдеры, можно уточнить, для чего используются varying и чем отличается пиксельный свет от вершинного.
Gigatrop
19.06.2022 02:41Думаю неверно расставлены акценты, объясню по-своему, может будет полезно.
Инкапсуляция:
Имеет много определений - в широких смыслах и в узких. ООП начинается, когда объединили данные с методами их обработки. Уже это можно называть инкапсуляцией. Как следствие данного объединения появляется возможность скрыть данные и методы. Потому что лежащие независимо данные и методы не могут быть скрыты, иначе их нельзя будет использовать. Разве что могут быть помещены друг в друга или использовать какие-то правила именования для имитации сокрытия.Сокрытие:
У вас C#, но в целом может достигаться и без модификаторов типа private. Например это могут быть подчёркивания __такие, или локальный скоуп, откуда переменные не выходят наружу. Например в JS модификатора private до сих пор нет, но сокрытие всегда в нём было и будет. Сейчас для этого выдумали решётку #такую.Наследование:
Для переиспользования кода при ООП. Переиспользование может достигаться и без наследования, например через вызов обычной внешней функции, или через делегирование, или через "воровство" методов других объектов с подменой this, и много ещё как.
Наследник может не иметь доступа к родительским данным и методам. Переиспользование тогда выходит только в вызове конструктора предка или в прочих неявных механизмах языка.
Наследуются экземпляры, а не классы. Классы остаются отдельными, в частности статичные поля у каждого класса свои.
Наследник не "берёт у родителя", а является родителем. "Берёт" при делегировании.Полиморфизм:
Для обобщённого программирования, то есть чтобы один код работал с разными типами данных. Достигается как угодно, лишь бы один код работал с разными типами данных. В языках со слабой динамической типизацией полиморфно всё, потому что переменные и функции не знают, какой тип в них попадёт. Так происходит в JS например, где различные объекты подходят в одни и те же места просто потому, что нет проверки типов. В языках со строгой статической типизацией есть специальные механизмы для обобщённого программирования, например интерфейсы или пропуск потомка там, где требуется предок, то есть полиморфизм потомков.Интерфейсы:
Для обобщённого программирования (полиморфизма) в языках со статической типизацией. Чтобы можно было засунуть различные объекты, не имеющие одинакового предка, в одно место использования. Например если требуется где-то объект с методом work, тогда делаем интерфейс с таким методом. И тогда без разницы какой будет класс.
Интерфейсы не являются фундаментальными кирпичами архитектуры проекта, как вы пишете. И они создаются не в начале проекта, а когда потребовались. Не для удобства обращения к объектам и не для большой команды, а для обобщённого программирования. Если такового нет или не требуется - не нужно делать интерфейсы. Также зачастую можно обойтись классами, так как создавать интерфейс под каждое место использования может быть накладно.
То, что интерфейсы нельзя изменять, - не правда. Никакой негативной практики, как вы пишете, в этом нет. Это абсолютно обычное дело. В IDE даже есть рефакторинг интерфейсов - во всём проекте всё синхронно меняется и ничего не ломается. Также компилятор проверяет все места использования интерфейса и все ошибки после изменений легко найти. Смена методов интерфейса и смена методов предка никак не отличаются по негативности. Но бывает мнение, что код можно только наращивать, но никогда не менять, в таком случае можно со мной не согласиться.
В вашем примере IMovable есть поля private, protected и static в интерфейсе. На мой взгляд это глупость, потому что интерфейс может быть только публичным, иначе не имеет смысла. И служит для экземпляров, а не для классов, поэтому static там быть не может. Также const в интерфейсе не может быть, потому что константы не принадлежат экземплярам, и интерфейс не может требовать константу от экземпляра. Разве что это какие-то заморочки конкретного языка, например в C# у интерфейсов есть реализация, что противоречит даже вашим словам "отделить описание от реализации".Gigatrop
19.06.2022 14:38Товарищи, вижу минус, поясните что не понравилось, чтобы я хотя-бы понимал, нужно такие полотна впредь писать или нет. Может вам не понравилось "много буков", или вы и вправду уверовали в те статьи, где интерфейсы для большой команды, или любите private в интерфейсах, или что-то ещё не так? Кнопки удаления нет, так хоть узнаю может.
undrAlex Автор
19.06.2022 15:01Спасибо)
Что ж начнем полемику и возможно появится какое то среднее мнение)
Про интерфейсы - в грамотном подходе к архитектуре проекта для большой команды, в начале создаются интерфейсы - что бы большая часть команды уже начала делать свои задачи просто наследуясь. Следовательно если в будущем вносить изменения в интерфейс, а наследники будут писаться кучей людей, то в итоге начнется неразбериха и кто то потратит время на исправление всего и вся.
Это один из принципов солида - "не изменяй то, что уже работает - добавляй новое".
Конечно если проект ведешь в одиночку или буквально в несколько человек, то и интерфейсами можно пренебречь и изменять можно что угодно, тут скорее про удобство и быстроту реагирования.
В документации микрософт по интерфейсам есть вот такие слова :Начиная с C# 8.0, член интерфейса может объявлять тело. Этот называется реализацией по умолчанию. Члены с телом позволяют интерфейсу предоставлять реализацию по умолчанию для классов и структур, которые не предоставляют реализацию с переопределением. Кроме того, начиная с C# 8.0, интерфейс может включать следующее.
Объявления членов с использованием явного синтаксиса реализации интерфейса.
Явные модификаторы доступа (доступ по умолчанию —
public
).
Начиная с C# 11 интерфейс может объявлять
static abstract
элементы для всех типов элементов, кроме полей.Да реализация в интерфейсах появилась, но это соблазн в будущем эту реализацию поменять. Обычно интерфейсы как раз и используют для того, что бы реализация была разной у каждого наследника.
Gigatrop
19.06.2022 15:30Подумал подумал, минус заслуженный :) погорячился.
в грамотном подходе к архитектуре проекта для большой команды, в начале создаются интерфейсы
Из моего опыта так не работает. Потому что не возможно всё учесть на этапе планирования. Интерфейсы будут создаваться по ходу разработки, потому что не возможно распланировать проект настолько глубоко и подробно. А дальше будет тупик - запретили себе изменять интерфейсы, и можете реализовывать только то, что запланировали в самом начале. Если же вы говорите не про самое начало, а просто про какой-то очередной этап разработки, то вопросов нет, интерфейсы тогда могут помочь.
Это один из принципов солида - "не изменяй то, что уже работает - добавляй новое"
Этот принцип не работает в полной мере. Если только наращивать и никогда не править, то такой код очень быстро станет перегруженным и не понятным. Просто в каждом месте придётся пробираться через нагромождения. Сегодня рады что не трогали код соседа, а завтра вместе с соседом придётся рефакторить нагромождения.
Начиная с C# 8.0, член интерфейса может объявлять тело
Я теперь понял ваш посыл - перечислить в картинке всё что может C#. Да, в нём захотели использовать интерфейс как склад, как неймспейс или класс. Я придрался потому, что это всё - противоположность назначения интерфейсов. Если я вижу private или static в интерфейсе, то прежде всего думаю, что автор кода не понимает зачем нужны интерфейсы, или что он столкнулся с какими-то проблемами и ему пришлось это написать. Интерфейс нужен по месту требования, например для параметра функции. Нельзя требовать на входе функции чтобы входящий объект имел приватное поле или чтобы его класс имел статичный метод.
undrAlex Автор
19.06.2022 16:42Согласен - все учесть невозможно + в процессе производства игры многое может поменяться.
Однако если на старте (или на новом этапе) есть хорошо описанное тз, то фундамент (из грамотно подобранных интерфейсов) будет надежным.
Но, возможно про изменения интерфейсов речь шла о рефакторинге (его ж никто не отменял), то в таком случае можно и старые поправить и новых добавить.
На мой взгляд это все упирается в решения команды (лидов/сеньеров) - закладывают ли они возможность уделять время на изменения базовых структур архитектуры, либо архитектура изначально подразумевает динамическую структуру (возможность без особых последствий изменять уже существующий код).
Моей основной задачей было - кратко изложить суть так, что бы было понятно человеку, который до этого ни разу не сталкивался с подобными понятиями.
В том числе что можно прописать в интерфейс. Конечно не факт, что этим воспользуются, но хотя бы знать о возможности стоит.
Но почему тогда у меня нет приписки о реализации в интерфейсах?
В оставленных ссылках со статьями об этом нет упоминания, поэтому решил не усложнять понимание.
Если считаете что это стоит упоминания в этой статье, то постараюсь добавить хороший материал на эту тему.ZloyVampir
19.06.2022 23:35На мой взгляд это все упирается в решения команды (лидов/сеньеров) - закладывают ли они возможность уделять время на изменения базовых структур архитектуры, либо архитектура изначально подразумевает динамическую структуру (возможность без особых последствий изменять уже существующий код).
Здесь все упирается в опыт архитектора. Гибкая архитектура ведет за собой с неизбежностью усложнение кода, и, обычно, минус к производительности. У архитектора должно быть чутье - в каких местах пренебречь гибкостью, а в каких - заложить задел на будущее.
Вряд ли опытный архитектор будет закладываться на будущий рефакторинг. Рефакторинг - это реальные затраты без полезного выхлопа. Заказчика интересуют бизнес-фичи, а не внутренняя красота кода.
ZloyVampir
19.06.2022 23:20то вопросов нет, интерфейсы тогда могут помочь.
Интерфейсы ВЕЗДЕ могут помочь. Интерфейсы - про сокрытие реализации, уменьшение связности кода, а не про процесс разработки.
. Если только наращивать и никогда не править, то такой код очень быстро станет перегруженным и не понятным.
Вы несколько не правильно понимаете принцип. SOLID - Это 5 принципов, а не 1. Буква S не даст вам бесконечно разрастаться. Принцип единой ответственности. А буква O - гарантирует вам, что семантика методов не поменяется. SOLID не запрещает править код. Вы можете его править, например, нашли уязвимость - поправили. Косяк нашли - поправили. Уточнили поведение в неясных случаях. Но, если вы нашли такой косяк, например, безопасности, то добро пожаловать - помечаете старый метод Deprecated и создаете новый, безопасный в том же интерфейсе.
Нельзя требовать на входе функции чтобы входящий объект имел приватное поле или чтобы его класс имел статичный метод.
Все, что разрешено в интерфейсах - можно использовать. Само собой - с умом. И понимая, ради чего та или иная фишка языка внедрялась.
ZloyVampir
19.06.2022 23:13в грамотном подходе к архитектуре проекта для большой команды, в начале создаются интерфейсы
А у вас agile? Налету требования поменялись и привет. Да, интерфейсы нужно тщательно проектировать, но 100% какие-то изменения вносить придется. Вопрос - на каком этапе. Если только во время первой итерации обнаружили неполадки - проблем нет. Поправите, доведете новый контракт до коллег, а, скорее всего, с ними и согласуете изменения. Если у вас уже устоявшийся модуль, который везде используется, возможно, проще будет какую-нибудь нашлепку сверху приделать, чем интерфейс менять. Если же изменения назрели и необходимы - выпускаете новую версию библиотеки с новым интерфейсом. И у вас параллельно существуют две версии интерфейса. Но с поддержкой обратной совместимости, постепенно будет ад нарастать.
Конечно если проект ведешь в одиночку или буквально в несколько человек, то и интерфейсами можно пренебречь и изменять можно что угодно,
Тогда рано или поздно проект дойдет до стадии, когда проще будет все выкинуть в помойку, чем дальше развивать. Правила проектирования придумывались из нужд проектов, а не из размера команды. Если проект создается в одиночку - это не повод отказываться от лучших практик в угоду экономии нескольких минут/часов на лень сделать как правильно. В будущем - оправдается.
Да реализация в интерфейсах появилась, но это соблазн в будущем эту реализацию поменять. Обычно интерфейсы как раз и используют для того, что бы реализация была разной у каждого наследника.
Методы по умолчанию - это такие методы, поведение которых очевидно из наличия тех методов, ради которых и описывается интерфейс. Например, геттеры, сеттеры, вспомогательные функции. В общем, все то, что без изменений будет тиражироваться любыми реализациями интерфейса и будет добавлять удобства к использованию. Если вы навешиваете на метод по умолчанию сложную не очевидную логику - вы сам себе злобный буратина. У вас на лицо ошибка проектирования. Впрочем, есть ситуации, когда оправдано переопределять такие методы по умолчанию. Язык разрешает вам это.
Gigatrop
19.06.2022 23:46Методы по умолчанию должны быть у предка, а не у интерфейса. Например у предка, реализующего интерфейс. Но C# не поддерживает множественное наследование, что для игр недостаток, так как например ружьё со штыком - это одновременно и ружьё и штык. Одной оси наследования мало, а агрегирование спасает не полностью, оно менее удобно чем наследование и заставляет делать прокси-свойства. В c++ множественное наследование есть. Почему в C# его не сделали, а теперь возвращают в интерфейсах и делают из них класс, не понятно. Ну что есть то есть, вы от этого довольны, а я бурчу вот, у меня идеалы другие - множественное наследование и интерфейсы-не-классы.
ZloyVampir
20.06.2022 00:26Методы по умолчанию должны быть у предка, а не у интерфейса.
Множественное наследование машет вам руками )
. Одной оси наследования мало, а агрегирование спасает не полностью, оно менее удобно чем наследование и заставляет делать прокси-свойства.
Ну множественное наследование добавляет целый класс проблем, а ружье со штыком - агрегирование, кажется, смотрится просто волшебно.
Впрочем, в свое время в разработке игр на плюсах активно использовалось множественное наследование. Но не в контексте ружья со штыком )
Почему в C# его не сделали, а теперь возвращают в интерфейсах и делают из них класс, не понятно.
Да как раз понятно. Потому что проблем от них больше профита.
И методы по умолчанию - они для того, чтобы убрать бойлерплейт, а не для наворачивания логики.
Ну что есть то есть, вы от этого довольны, а я бурчу вот, у меня идеалы другие - множественное наследование и интерфейсы-не-классы.
Я и на плюсах писал ина джаве пишу. Доволен/не доволен - не то слово ) Просто нужно понимать, что и ради чего делалось.
ZloyVampir
20.06.2022 00:36А вообще, если подумать, интерфейсы как раз прекрасно решают проблемы множественного наследования.
Что у нас было?
Нужна поддержка физики? Добавили родителя CPhysic, переопределили методы. Нужно реализовать поведение ИИ? Добавили родителя CBehaviour. Нужна поддержка в скриптовом языке? Добавили родителя CScript.
И имеется у нас куча абстрактных классов, которые поддерживают тот или иной функционал. И на выходе у нас получается объект с тысячей методов...
А как сейчас? Нужна нам физика? Реализуем интерфейс IPhysic. Нужно ИИ? Реализуем интерфейс IBehaviour. Нужна поддержка в скриптовом языке? Реализуем интерфейс IScript.
Как всю эту красоту заставить работать? А у нас есть объект PhysicManager, который работает с коллекцией IPhysic и обслуживает всю физику. Аналогично с другими слоями.
ZloyVampir
19.06.2022 21:46Для обобщённого программирования (полиморфизма) в языках со статической типизацией.
Обобщенное программирование - это generic'и и template'ы. Полиморфизм - это концепция из ООП.
Не для удобства обращения к объектам и не для большой команды, а для обобщённого программирования.
Ну не. Интерфейс создан для сокрытия реализации. Уменьшает связность модулей.
И в какой-то степени является заменой множественного наследования.
Также зачастую можно обойтись классами, так как создавать интерфейс под каждое место использования может быть накладно.
Не нужно зачастую обходиться классами. Нужно продумывать архитектуру. Выделение интерфейсов чаще всего оправдано. В чем накладность интерфейса? Это просто контракт компилятора.
То, что интерфейсы нельзя изменять, - не правда. Никакой негативной практики, как вы пишете, в этом нет.
Негативная практика есть и еще какая. В рамках монолита изменить интерфейс - ноу проблем. IDE поможет... А если вы хотите поменять интерфейс в библиотеке, например? Или в распределенной среде, когда ваш модуль может вызываться из самых интересных мест?
Нужно 10 раз подумать, прежде чем вносить изменения в контракт.
А вообще это классический пример нарушения буквы O из SOLID )
Также const в интерфейсе не может быть, потому что константы не принадлежат экземплярам
А это сильно зависит от контекста. В Java, например, вполне себе могут быть константы в интерфейсе. В этом случае они являются статическими.
Gigatrop
19.06.2022 22:55Обобщенное программирование - это generic'и и template'ы.
Вот второй абзац в Википедии про обобщённое программирование:
Обобщённое программирование рассматривается как методология программирования, основанная на разделении структур данных и алгоритмов через использование абстрактных описаний требований[3]. Абстрактные описания требований являются расширением понятия абстрактного типа данных. Вместо описания отдельного типа в обобщённом программировании применяется описание семейства типов, имеющих общий интерфейс и семантическое поведение (англ. semantic behavior). Набор требований, описывающий интерфейс и семантическое поведение, называется концепцией (англ. concept). Таким образом, написанный в обобщённом стиле алгоритм может применяться для любых типов, удовлетворяющих его своими концепциями. Такая возможность называется полиморфизмом.
Дженерики как частный случай там есть, но ими не ограничивается. Вот например в той же статье на английском (в русском вырезано):
Similarly, dynamically typed languages, especially interpreted ones, usually offer genericity by default as both passing values to functions and value assignment are type-indifferent
Тут говорится про то, что например JS имеет "обобщённость" просто потому, что не проверяет типы.
Полиморфизм - это концепция из ООП.
Ну если вы имеете в виду изначально только ООП, тогда да. А так вот из Википедии опять:
Полиморфизм в языках программирования и теории типов — способность функции обрабатывать данные разных типов.
------
Интерфейс создан для сокрытия реализации. Уменьшает связность модулей. И в какой-то степени является заменой множественного наследования.
Для сокрытия создано сокрытие (private). Интерфейс нужен для обобщённого программирования. Поясню ещё раз. Я например ожидаю, что аргумент функции будет иметь метод work. Мне без разницы на наследование, на то открыта реализация или нет, есть дженерики в языке или нет, мне нужен только метод work. Почему это обобщённое программирование? Потому что я написал код функции один раз, никогда его не меняю, а он работает с разными типами входящих данных. Почему это полиморфизм? Потому что разные объекты подходят в одно место использования.
В чем накладность интерфейса?
В том что его нужно писать. Если у вас функция только вызывает метод work у объекта, то вы пишете параметр типа IWorkable . Тогда таких интерфейсов очень много. Вот я и говорю, если не нужно обобщённое программирование, то есть если метод и так всегда работает с одним классом или с одним предком, а не с любыми объектами, то интерфейс тут не нужен, а нужен класс-предок. Это экономит силы программиста.
Нужно 10 раз подумать, прежде чем вносить изменения в контракт.
Мы говорим о разных граничных случаях, чтобы доказать точку зрения. Вот статья для мидлов на юнити. Мне кажется, что там и будет монолит :) . Но будут бояться менять интерфейсы, просто потому что так написано в интернете. И будут городить многоэтажные дополнения-потомки или новые интерфейсы вместо правки существующего кода.
Также const в интерфейсе не может быть
Ну насчёт этого я перегнул. Смысл моего выпада был в том, что интерфейс это контракт. А его используют уже как склад всего подряд. Отсюда новичёк подумает, что private в интерфейсе - это так и надо. Только потом узнает, что это совсем не тот private (не контракт). Хотя в нынешние времена, насколько я понял, новичок даже не задумается, потому что для него интерфейс - это просто штука из конкретного языка, а не воплощение контракта.
ZloyVampir
20.06.2022 01:11Для сокрытия создано сокрытие (private).
Так вы реализацию не скроете. У вас в определении типа будет торчать вся начинка. И когда вы ее будете подключать в свой проект, все зависимости private - весело прискачут к вам.
Другое дело интерфейс. Делаете вы интерфейс и описываете контракт. Все. Точка. Чтобы подключить в другой части приложения, из зависимостей тянется только интерфейс.
Пример:
public class AIService {
private List<AIObject> chars;
private AiHelper helper;
....
public void think(float duration);
}
Сравните с интерфейсом:
public interface AI {
void think(float duration);
}
Когда вы тянете AiService в любую другую часть проекта, у вас в нагрузку прилетает и AIObject и AiHelper и List. Плюс одна головная боль. А может у вас уже есть AiObject?
А может у вас там чудесным образом циклические ссылки?
А архитектурно вам реализация не нужна. Меньше знаешь - крепче спишь.
Когда вы тянете AI в любую другую часть проекта, у вас в нагрузку не летит НИЧЕГО ВООБЩЕ.
И я совершенно не спорил по поводу обобщенного программирования. Оно тоже имеет место быть, хотя там можно исхитриться и вытащить общий базовый класс, который будет решать ту же задачу.
В терминологический же спор вдаваться не буду - суть не меняется.
В том что его нужно писать.
Не серьезный аргумент. Любая IDE в два клика позволяет выделить интерфейс. Под накладными расходами обычно подразумеваются расходы компилятора по реализации фишки. У вас генерится дополнительный код, который тратит время процессора на выполнение. Так вот, накладных расходов на создание интерфейсов в этом смысле нет.
Тогда таких интерфейсов очень много.
А мозг вам для чего? Из любого правила бывают исключения. На любой чих создавать интерфейсы не нужно. Кстати, на любой чих создавать отдельные классы тоже не нужно.
Вот я и говорю, если не нужно обобщённое программирование, то есть если метод и так всегда работает с одним классом или с одним предком, а не с любыми объектами, то интерфейс тут не нужен, а нужен класс-предок. Это экономит силы программиста.
Вот это не так. У вас может быть в проекте ровно одна реализация некого сервиса. Пусть будет AIService. В этом случае все равно полезно выделить интерфейс. ДЛЯ УМЕНЬШЕНИЯ СВЯЗНОСТИ И СОКРЫТИЯ РЕАЛИЗАЦИИ.
Пример - выше. А экономить силы программиста отказываясь от архитектурно верных решений - удел джунов.
Мы говорим о разных граничных случаях, чтобы доказать точку зрения.
Единственно верной точки зрения не существует. Из любого правила бывают исключения. Даже пресловутые паттерны проектирования банды четырех - не нужно сувать везде без разбору. Нужно думать в первую очередь об архитектурных нуждах проекта.
Мне кажется, что там и будет монолит
Ай, не обязательно ) Вполне возможно там будет система плагинов, к примеру. Это не микросервисы, но разные единицы компиляции - вполне возможно. Там могут быть библиотеки. Написанные вашими же силами и повторно используемыми в нескольких проектах, которые ваша студия ведет одновременно. Т.е. вы легко залезли в реп библиотеки, поменяли интерфейс, а потом весело скачете по всем проектам и правите миллионы вхождений? Не серьезно. Более того, даже в рамках одного проекта, все может быть сильно сложнее рефакторинга средой разработки. Вы не сталкивались с косяками вижл студии? Потому что она не правильно распознала контекст вашего фильдиперстового кода? Вы хотите провести ретест стопицот фишек игры, просто потому что код изменился? Уверяю вас, промышленные масштабы - заставляют внимательно относится к наработанному опыту.
Хотя в нынешние времена, насколько я понял, новичок даже не задумается, потому что для него интерфейс - это просто штука из конкретного языка, а не воплощение контракта.
Отсюда вытекает, что новичков нужно наставлять на путь истинный получением фундаментального образования. Математика? Кватернионы? Базисы? Аффинные преобразования? Сплайны? Теория сложности? Вы о чем, кто все эти люди? Юнити за меня все сделает )
Gigatrop
20.06.2022 01:25Спасибо, посмотрел под другими углами на вопрос, раньше не видел эти нюансы. Понял про какое сокрытие вы говорите.
ZloyVampir
20.06.2022 01:25Тут говорится про то, что например JS имеет "обобщённость" просто потому, что не проверяет типы.
Если вы еще раз зайдете в вики, то подобного не увидите. И если почитать хоть английскую статью, хоть русскую - смысл один. Обобщенное программирование касается шаблонов. Т.е. вводится метатип, под который выводятся свои реализации функций (С++). Java под это определение все еще попадает, но там семантика дженериков другая (стирание типов), а JS - не подходит вообще никак.
Igor_Sib
19.06.2022 13:37>> В чём разница между ключевыми словами final, finally, finalize?
А можно услышать ответ, в контексте Unity (C#) ? )
undrAlex Автор
19.06.2022 15:11по С# такого не нашел, но вот в Java они присутствуют (оставляю для общего развития)
Скорее проще перечислить что есть у них общего… а это только корень final и то что они являются зарезервированными словами в Java.
final — модификатор, применяющийся к классам, методам, переменным. В общем своём представлении делающий объект своего действия неизменным, а если быть точнее, то после его применения:-от класса становится невозможно наследоваться;-метод невозможно переопределить;-переменную невозможно изменить(но в случае, если переменная-это ссылка на объект, то на состояние объекта final никак не влияет, если только класс сам не является final).
finally является частью конструкции try-catch-finally, в где играет роль блока, который выполняется независимо от событий происходящих в try-catch и выполняется в любом случае.
finalize() является методом класса Object.Igor_Sib
19.06.2022 16:08Я специально написал в вопросе в контексте Unity (C#). В C# вопрос должен звучать так - чем отличается readonly (и sealed) / finally / Finalize().
А еще лучше просто спросить, что такое readonly, sealed, как работает try/catch/finally (и хорошо если скажут когда не работает )), и что такое Finalize() (можно спросить в связке с Dispose()).
Lekret
19.06.2022 14:33Про ключевое слово "final" и "checked/unchecked exceptions". Подобное разве есть в C#? Это скорее вопросы для джавистов.
undrAlex Автор
19.06.2022 15:09вероятно это вопрос с подвохом и ответ возможен такой - в С# таких понятий нет, это Java синтаксис.
В свою очередь могут задать - "а что подобное есть в С#?"ZloyVampir
19.06.2022 23:30Если вам на собесе по C# задают вопрос по Java - нужно бежать из такой конторы. Это что за самодурство такое? Если человек работал со многими языками - плюс в карму. Но если он собеседуется на шарп, а вопрос получает по плюсам или по джаве - это за гранью добра и зла. Нужны разные технологии? В одном лице? Еще один повод подумать, а надо ли работать в такой конторе. Но если уж очень надо - тогда надо указывать в требованиях к вакансии.
Igor_Sib
19.06.2022 16:15Про "checked/unchecked exceptions" - наверно он имел в виду обработанные catch-ем и unhandled exceptions. Вообще мне кажется автор пару вопросов из собеса по Java случайно добавил. )
Ascar
20.06.2022 02:12Это не заменит чтение книг. Про solid в первую очередь это труды дяди боба, а не это. Например про S, что за задача? У класса несколько методов, каждый метод, очевидно, решает одну или несколько задач. Где границы какой то непонятной задачи? Тема гораздо глубже и касается причин которые могут затронут несвязанные бизнес задачи. Незнание этого приводит в запутанному коду, когда совсем разные причины затрагивают совсем несвязанные бизнес задачи соответственно. И такие блоки кода очень сложно распутывать и разделять.
Инверсия зависимости так же гораздо сложнее. Это не просто наплодил интерфейсики и внедрил реализации. Так же надо знать где определить пограничный интерфейс. И зачем в принципе тут уместно инвертировать зависимость? Чтоб бизнес логика не зависела от деталей реализации например.
Djaler
Пример с перегрузками не имеет ничего общего с полиморфизмом в контексте ООП
undrAlex Автор
Если у тебя есть более конкретный пример в 2-3 строки (и который будет понятен для новичка) то буду рад его добавить вместо моего
ZloyVampir
У тебя есть коллекция объектов, например, List<Char>; Char - базовый класс персонажа или лучше интерфейс. В любом случае, имеем некую базовую функциональность. Например, персонаж умеет реагировать на события, имеет характеристики здоровья, силы и т.д. Допустим у нас есть метод think(), который обновляет стратегию поведения и обновляет ее при развитии игровой ситуации. В базовом классе мы можем реализовать базовую версию поведения. Например, искать цель, строить маршрут и идти к ней в притык кратчайшим маршрутом, а затем бить.
Теперь предположим, что для примитивных персонажей нас такое поведение устраивает, а для сложных персонажей - нет. Тогда в классе-наследнике мы переопределяем метод think(). И теперь у нас главный босс умеет уворачиваться от пуль, строить засады и вообще ведет себя разумно как живой человек.
Теперь имеем ситуацию:
Есть базовый класс Char, в нем есть метод think()
Есть наследник Boss - в нем есть своя реализация метода think()
Что произойдет при вызове char.think()?
1) компилятор определит адрес char@Think метода think() класса Char;
2) компилятор определит адрес this объекта char.
3) компилятор сделает вызов метода по адресу Char@Think и кроме явно определенных аргументов передаст дополнительный аргумент this.
4) вм/процессор выполнят нужный метод
Что произойдет при вызое boss.think()?
Все теже 4 шага, но Boss@Think - это совершенно другой адрес и совершенно другой метод. Соответственно все еще вызовется правильный метод.
А теперь вернемся к работе с коллекциями. У нас есть объект мир, который для каждого объекта вызывает метод think. При этом мир не знает, для какой конкретно объекта вызывается метод think(). В коллекцию List<Char> мы можем положить как базовый класс, так и любого его наследника.
пусть имеем цикл for (Char c: chars) {
c.think()
}
Что произойдет при вызове c.think()?
Компилятор видит, что think() - метод класса Char и соответственно определяет адрес метода БАЗОВОГО класса, независимо от того, какого класса объект лежит в коллекции.
Итак, после обхода коллекции класс "Мир" выполнит базовое поведение для всех объектов, независимо от того, какой на самом деле объект там находился.
Но мы определенно хотели другого!
Мы хотим, чтобы при вызове метода, вызывалась та версия, которая определена в том классе, который на самом деле лежит в коллекции. Да, Boss - это тоже Char. Но при вызове Char.think() мы хотим, чтобы выывался метод из класса Boss.
Вот в этот момент и появляется полиморфизм и виртуальные функции. В Java все методы виртуальны и никак иначе. В C++/C# - метод нужно помечать оператором virtual;
Что происходит теперь? В объекте неявно добавляется таблица виртуальных методов (ТВМ).
Эта таблица содержит адрес реального метода think().
Что происходит теперь при создании объекта Char c = new Boss();?
В созданном объекте Boss заполняется таблица виртуальных методов. Для метода think() указывается адрес Boss@think. Далее в переменную c помещается адрес объекта Boss.
Что происходит теперь при вызове метода c.think?
1) Компилятор не знает на какой конкретно объект указывает ссылка/указатель Char c. Но ему этого и не надо. В ТВМ есть правильные адреса.
2) Компилятор достает правильный адрес из ТВМ и делает вызов метода.
Ура! Мы получили динамичное поведение объектов и при этом никаким образом не заботимся об определении с каким конкретно объектом мы работаем.
Кажется, я рассказал ни фига не просто, зато на уровне, как оно происходит под капотом. Кажется, джуну это знать не обязательно, а вот миддл - уже должен понимать.
undrAlex Автор
Спасибо)
0101010102
а можете чуть подробнее объяснить, я начинающ программист, но насколько я понимаю что приведенный пример подходит для общего случая определения полиморфизма
Djaler
В контексте ООП полиморфизм - это про то, как код одинаково работает с разными реализациями одного интерфейса / наследниками класса.
В этом примере функция moveToStart работает с любыми реализациями Transport, используя полиморфизм.
А то что сейчас в примере в статье - это про параметрический полиморфизм. Да, это тоже полиморфизм, но к ООП он не имеет никакого отношения.
0101010102
о
Djaler
Да, я немного ошибся. Параметрический полиморфизм - это про описание кода в общем виде для любых типов аргументов (с дженериками)
А пример из статьи с перегрузками - это как раз ad-hoc полиморфизм, действительно.
undrAlex Автор
примеры с интерфейсами сначала и решил показать, но они кхм "большие" для того что бы кратко изложить суть, а хотелось сделать картинку - подсказку достаточно компактную.
Все остальное можно подчерпнуть из статьей-ссылок, поэтому их так много, что бы охватить большую часть информации.
Djaler
Ну, компактная то она компактная, только к ООП отношения не имеет, повторюсь
undrAlex Автор
вероятнее всего ожидал ответ наподобие -
Есть несколько видов полиморфизма
Параметрический полиморфизм - дженерики.
Полиморфизм подтипов - ООП-полиморфизм, выражаемый в переопределении методов при наследовании. Same dunction - different behavior.
Ну и ad-hoc (мнимый полиморфизм) - статический полиморфизм, выражающийся в перегрузке методов.
Но нашел это в другой статье и уже добавил ссылку.
Спасибо)