У Вас задача поймать меня на противоречиях, или Вы плохо понимаете, что где находится?
Код валидатора лежит в модели, но создает объект этого кода вьюмодель. Пользуется валидатором (моделью) вьюмодель, но сам код валидатора (модели) отделен от вьюмодели и от последней не зависит. Выше же я Вам уже писал, что код группируется не по месту использования, а по максимизации переиспользуемости и минимизации дублирования.
Может, Вы плохо понимаете концепцию коннасценсции и/или Low coupling/High cohesion?
Давайте закончим, пожалуйста. Думаю, дальше Вы сможете во всем разобраться сами. Наша дискуссия с самого начала не очень относилась к теме статьи (а она, напомню, – способы реализации абстракции).
Очень просто же. Вьюмодель для того и нужна, чтобы соединить отображение с бизнес логикой (помните байндер или абстракцию?) и настроить преобразования данных (помните мэпперы?). Следите за руками.
вьюмодель1 для формы1 создает валидатор и настраивает его на диапазон 0..100. байндер1 связывает форму1 с только что созданным и настроенным на диапазон 0..100 валидатором.
вьюмодель2 для формы2 создает тот же самый валидатор (тот же класс, но другой объект), но настраивает его уже на диапазон 10..50. байндер2 связывает этот второй валидатор с формой2.
Вы великолепны!
Заметьте, никаких дополнительных признаков, кроме имеющейся у валидатора параметризации диапазоном не понадобилось. Ну, а диапазон – часть Вашего домена, как видно из логики Вашего приложения.
Случай с базой. База – тоже вполне себе доменная модель. База – это не только хранение данных, там вполне себе прописываются очень многие ограничения и тяжелая логика. Если Вы ковыряли любую реляционную БД, то знаете, что там ооооочень сложную логику можно прописывать... и, кстати, параметризировать тоже, как в примере выше.
Ответ на этот вопрос уже был дан. Но еще раз повторю другими словами.
Группировка функционала идет не по месту использования, а по максимизации возможности переиспользования и минимизации дублирования кода, как в примерах выше.
Но если Вы хотите помещать валидаторы во вью или вьюмодель — дело Ваше. Вы просто не сможете переиспользовать валидаторы, если нужно будет вашу логику перенести на другую платформу. Всего лишь. придется их все написать по новой для каждой новой платформы. И покрыть одинаковыми тестами. Всего лишь.
Не соглашусь с Вами. Такой логике место в модели. Она уже доменная, бизнесовая.
Проверка аналогичная. Если мы переносим вью в другие приложения в рамках одной платформы, валидация переезжает вместе с вьюхой? Конечно нет. В другом приложении будет другая логика, другие данные, другие валидации.
И наоборот. Если мы доменную логику переносим на другую визуальную платформу, она должна сохраниться? Конечно, да! Неважно, как выглядят поля ввода, валидация данных сохраняется.
Так что увы, тезис остается неизменный — во вью-модели есть только 3 ответственности: абстракция, трансформеры (или мэпперы) и байндер. Все остальное — либо вьюха, либо модель. Но ни в коем случае не пихайте все подряд во вьюмодель. Этим Вы всю концепцию убьете. Понятно, что она Вам, видимо, и не нужна (Вы не переиспользуете ни вью в разных приложениях, ни логику на разных платформах). Но тогда Вам и MVVM не нужен. А раз не нужен, зачем его извращать? Просто не используйте, всем от этого только легче будет.
Бегло глянул концепцию, которую Вы продвигаете в своих статьях. Считаю, что в комментах Вам не зря пишут. Иногда с сарказмом 🤷♂️ простите. Но не сдавайтесь, возможно, найдете сторонников. 💪🏻
View не является "фасадом" в значении шаблона Банды Четырех. Это вполне себе цельная ответственность (и соответственно логика, бывает ответственность без логики?) – поговорить с пользователем: выслушать его, отобразить ему ответ. Неважно в каком виде его выслушать и в каком вернуть ответ: консоль разработчика – это тоже View. Шлем VR – это тоже View. и т.д. Соответственно, у View тоже есть своя логика (но она ограничивается пониманием пользователя и показом ему ответов). Пример, нажатие на кнопку её подсвечивает, отпускание нажатия подсветку убирает. У Apple, как минимум, у кнопки для этой логики нет отдельной вьюмодели. Вся эта логика размещена прямо во View и это кажется логичным, нет?
ViewModel -- вот вики и картинка из неё (оригинальная) говорят, что "реализует модель данных (presentation model) слоя presentation layer и внутреннюю логику слоя presentation layer" находится где-то между View и ViewModel. Я не могу это однозначно трактовать... Оно размазано между ними двумя? Оно относится к стрелочке биндинга? Оно размазано между двумя слоями и еще и стрелочкой биндинга? Мне сложно сказать по одной лишь картине... Поэтому я учитываю коммент про View выше, который является самостоятельной ответственностью со своей логикой презентации, а также описание шаблона в Вики, которая говорит, что ответственностей у вью-модели всего 3, и среди них нет логики презентации. Еще могу сказать, что логика презентации должна быть переиспользуемой в пределах устройства, но между разными приложениями. Т.е.я могу взять кнопку, о которой говорилось выше, вставить в другое приложение и она точно так же при нажатии начнет подсвечиваться, а при отпускании подсветка будет сниматься... Если предположить, что эта логика будет не в кнопке, а во вью-модели, то получается, что из приложения в приложение мы должны переносить не только класс самой кнопки, но и класс её вьюмодели. Но!..
Тут мы переходим к тому, что Вы сказали (и Вики, и автор с вами согласны), что вьюмодель может содержать мэппер (преобразователь данных в дословном переводе). Так вот он жестко связан с тем, что он переводит (и с моделью, и с вью). Значит, что при таком переносе, я не смогу какую-то другую модель подключить к этой комбинации вью+вьюмодель. Только ту, которая ждет данные в нужном формате. Значит, вся концепция переиспользования вью убивается на корню. А MVVM должна, вроде как, её сохранять...
Отсюда следует, что логика презентации должна быть во вью, иначе вся концепция разваливается.
А картинка из Вики... Ну, я не зря в начале статьи написал, что картинка и очень проста, и одновременно безумно сложна. Так оно и есть при более глубоком рассмотрении.
Пусть будет так, спорить не собираюсь. Пусть для Вас и для остальных, кто не согласен, что это стирание типов, это будет способ, называемый адаптер объектов.
На мой взгляд Ваш последний демонстрируемый пример ближе к ассоциированным типам из статьи.
Отмечу также, что в этом примере не хранится модель, а сохраняются данные модели (замыкание, а через него имя, автора, цитату). Конечно, в этом случае невозможно определить тип модели, просто потому, что Вы её не сохранили. Это же можно сделать и в моих примерах. В обоих – и в стирающей типы обертке и в ассоциированных типах.
Вообще последний пример выглядит сильно крипово.
Использоваться это будет так, поправьте меня:
imageCell.tableViewCell(tableView)
Т.е.у вьюмодели (которая почему-то называется ячейкой) мы запрашиваем ячейку (реальную вьюху у вьюмодели) и передаем ей параметром таблицу. Все перевернуто с ног на голову.
При этом вьюмодель содержит замыкания.
Уважаемому @laptev_amниже я отмечал, что как раз замыкания использовать для автоматического обновления я не рекомендую. Как раз вот в том числе и из-за вот таких вот "перевертышей".
Закончу простым вопросом. Вам, правда, Ваш последний пример кода кажется проще любого из приведенных в статье?
Так а где Вы в моей статье увидели "скрытие ViewModel за абстракциями"?
Способ 1 и 2.1 - это тупо протоколы и ничего более. Там вьюмодель сама является протоколом, а не скрывается им.
Остальные способы не содержат "скрытие ViewModel за абстракциями". Или Вы о каких-то непонятных мне абстракциях?
Обертка, стирающая тип, сама тоже абстракцией не закрывается. Смотрите пример. Она и есть абстракция. Более того, я и Вам повторю, как уже написал выше это @house2008: статья как раз и показывает неправильный пример, где вьюмодель содержит сразу 3 абстракции! Отдельно отмечается, что так делать не нужно.
Просто для уточнения. А у Вас чем от "простого проксирования" отличается? Тем, что есть преобразование данных? Ну, я опять же в начале статьи указал, что оставим это за скобками, чтобы не скрыть суть. Но в некоторых примерах в комментах подписано, что собственно вот там и можно осуществлять преобразование, а где-то оно даже есть (из свойства с одним названием, в свойство с другим, но того же типа).
Я, в принципе, много про стирание типов разбирался прежде, чем это опубликовать. Кажется, это в чистом виде оно. Но поправьте меня, почему Вы считает, что это не оно.
Вам спасибо, за хороший вопрос и детальный пример. Это мне и нужно было, чтобы люди делились тем, как делают они. Хочется разобраться, какой же вариант самый популярный, кто пользуется непопулярными, в каким случаях, есть ли еще какие-то способы...
Вам тоже отвечу по частям, постараюсь максимально кратко.
О сложностях какого способа Вы говорите? Способ 1 очень даже простой. Там кроме протокола вообще больше ничего нет. На мой взгляд даже проще, чем тот, что описали Вы. Сложно судить по тексту без кода, что Вы описываете, но кажется, что Вы говорите как раз о том, что привел уважаемый @house2008. Если так, то это в чистом виде способ 5 – обертка, стирающая типы. В этом способе появляется 2 новых сущности (а не одна) – протокол и класс. А для класса еще и объекты надо создавать и потом управлять их жизненным циклом. В первом случае это не нужно совсем.
"Основной концепт подхода в том, что при изменении данных/стейта внутри ViewModel автоматически обновляется View." Не стану с этим спорить, но не зря я ссылку дал на Википедию же. Она утверждает, что это не главное и вообще опциональное. Это лишь третья ответственность из трех возможных, называемая биндинг. Автоматического обновления можно и без биндинга добиться, например, шаблоном "Наблюдатель"/"Observer". Или даже простым делегированием или замыканиями (в терминологии андроида, кажется, это колбеки), но я крайне не рекомендую делать ни первое, ни второе – других проблем огребете. А вот важна во вьюмодели как раз абстракция, и Вы её добиваетесь оберткой, стирающей типы. Сравните внимательно.
Я смог ответить на Ваши вопросы?
ПС: не стесняйтесь, отмечайте тогда вариант 5 в опросе. Это он!
В вашей реплике сразу несколько вопросов и неточностей. По очереди и отвечу.
Именно для того, чтоб не перегружать примеры, в начале статьи и делается сноска, что инжектирование специально остается за скобками. Иначе она спрячет от Вас суть. Но Вы можете самостоятельно убедиться, что ни один из способов не изменится, если добавить в него инжектирование. Они (в смысле инжектирование и способы) независимы друг от друга. Освоив любой из способов (или даже несколько, или даже все) Вы легко сможете использовать их с инжектированием. Отдельно в статье отмечено (ближе к концу), что ни с одним из перечисленных способов не должно быть проблем с тестированием именно потому, что сама реализация уже содержит абстракцию, а значит, может легко позволить подменить вью для модели или модель для вью.
Не во всех перечисленных примерах вьмодели скрыты протоколами. Абстракция – это не только протоколы. Статья пытается это показать. В частности, Ваш пример – это вариант 5, обертка, стирающая тип. Не самый простой вариант (есть проще), но самый популярный, как и отмечено в статье. И при этом мало известный :) Не удивительно, что он первый же в комментариях и проявился. Вам осталось проголосовать в опросе, не стесняйтесь, пожалуйста! Давайте создадим статистику!
Что касается "по самому паттерну верно делать интерфейсы для vm, но на практике это очень избыточно и даже вредно." Так статья именно это и пытается показать! Еще раз: далеко не во всех перечисленных примерах вьюмодель спрятана за протоколом. Более того, я тоже считаю, что иногда (если это не варианты 1, 2.1, где вьюмодель – это и есть протокол и ничего более, или, может быть, еще какие-то) это вредно! К тому же, в самом шаблоне нет ни слова про протокол! Там лишь написано, что вьюмодель должна быть абстракцией! А уж какой способ реализации абстракции Вы выберете - неважно, это все равно будет MVVM. Проблема будет только если а) Вы вообще забудете абстракцию; б) нагородите их слишком много.
А можете кратенько описать, что это и как? Я слегка погуглил. Получается, это возможно только в облаках сторонних провайдеров? Да и то, только потому, что они тестовые среды не предоставляют? Т.е. если я захочу такое у себя, то мне надо отобрать доступы у разработчиков и тестировщиков к тестовым средам? или как мне этого serverless добиться?
Тимофей, спасибо за хорошо проработанный материал. Возможно будет интересно. Вам или Вашим читателям. Кросс-ссылочка. Мы тоже пытались показать, как автотесты экономят именно время разработчика. Не так красиво получилось, как у Вас, зато есть конкретные цифры, не сильно отличающиеся от Ваших.
И снова соглашусь с первым комментарием. Захардкоженная навигация только у Вас. У меня вот проблем нет. И вся статья дальше тоже теряет актуальность в связи с этим.
Почему нет проблемы? Все просто. Перенесите первый же Ваш код сниппет (с экшеном кнопки) в расширение UINavigationController. Все. Ни один Ваш унаследованный контроллер не знает ни об этом экшене, ни о других контроллерах, ни об их взаимосвязи...
Попробуйте, это крайне просто. Сильно проще, чем координатор. И эстетичнее. Как команды в системном меню...
Сам тоже сейчас редактирую статью про "правильную готовку" MVVM. Думал уже не публиковать, но, видимо, теперь придется :)
В статье Вики про MVVM указано, что вьюмодель содержит всего 3 ответственности:
абстракцию для разделения вьюхи и модели (слабая связность).
преобразование данных из вида модели к виду отображения (и возможно, но необязательно, обратно).
биндинг - вариация абстрагирования и/или преобразования данных.
Вот, в принципе и все, что имеет право быть во вьюомдели. Все остальное, что вы перечислили, это уже либо модель, либо представление. Без вариантов! Например, навигация – ответственность представления. Если Вы меняете систему представления (например, с iOS переезжаете на мак, часы или телевизор), то в Вашем приложении не нужно бы менять ни вьюмодель, ни модель... а если Ваша модель зависит от роутера или координатора, то вам этого не избежать... а вот если роутер или координатор дергаются напрямую из вью, то остальные два слоя вашего приложения изменений не требуют – достаточно поменять только представление...
Unidirectional Data Flow – это пункт 2 из обязательных ответственностей. Преобразование данных. В одну и в другую сторону. Они в 142% случаев всегда независимы друг от друга, да. Поэтому реализовать их в двух разных классах не представляет сложности никогда. Но при этом оба класса все же остаются внутри слоя вьюмодели. Это слой. Он не обязан состоять из одного класса... В вашем примере Вы просто лишь одну часть этой ответственности осуществляете через биндинг. Но можно и обе, проблем не будет, это все еще будет UDF, если вью будет писать в одно свойство, а модель в другое (в одном или разных объектах – уже неважно)...
Но в принципе биндинг для преобразования данных не обязателен, как и для абстракции. Но с его помощью можно реализовать и то, и другое. А можно и не реализовывать. Биндинг для MVVM не обязателен, но тогда оно выродится в MVP или MVC с пассивной моделью.
В остальном, Вы молодец. Копайте глубже и проблем у Вас не будет ни с какой архитектурой. Еще и на лету их сможете менять, если ответственности представления (типа навигации) не будете опускать ниже.
ПС: неплохо Вы статьи генерите – по одной в день – ИИ помогает?
Соглашусь с первым комментарием по поводу состояния.
"Состояние"("State") – это прям отдельный шаблон из книжки Банды Четырех, чтобы с ним работа была комфортна, он должен реализовываться через набор классов с одним и тем же интерфейсом. Иначе, рано или поздно столкнетесь с проблемами. Например, enum'ы не расширяемые. Добавить состояние без нарушения OCP у вас не выйдет. В мелких проектах это и не важно, но в крупных...
Ну, и Вы тут много говорите о том, что не нужно смешивать ответственности, а "Состояние" (с переходами между ними, которые довольно часто не тривиальные! и не все вообще разрешены!) – вполне себе цельная отдельная ответственность...
Попробуйте её инкапсулировать по шаблону Банды Четырех. Не с первого раза, но получится всю прелесть прочувствовать...
У Вас задача поймать меня на противоречиях, или Вы плохо понимаете, что где находится?
Код валидатора лежит в модели, но создает объект этого кода вьюмодель. Пользуется валидатором (моделью) вьюмодель, но сам код валидатора (модели) отделен от вьюмодели и от последней не зависит. Выше же я Вам уже писал, что код группируется не по месту использования, а по максимизации переиспользуемости и минимизации дублирования.
Может, Вы плохо понимаете концепцию коннасценсции и/или Low coupling/High cohesion?
Давайте закончим, пожалуйста. Думаю, дальше Вы сможете во всем разобраться сами. Наша дискуссия с самого начала не очень относилась к теме статьи (а она, напомню, – способы реализации абстракции).
Очень просто же. Вьюмодель для того и нужна, чтобы соединить отображение с бизнес логикой (помните байндер или абстракцию?) и настроить преобразования данных (помните мэпперы?). Следите за руками.
вьюмодель1 для формы1 создает валидатор и настраивает его на диапазон 0..100. байндер1 связывает форму1 с только что созданным и настроенным на диапазон 0..100 валидатором.
вьюмодель2 для формы2 создает тот же самый валидатор (тот же класс, но другой объект), но настраивает его уже на диапазон 10..50. байндер2 связывает этот второй валидатор с формой2.
Вы великолепны!
Заметьте, никаких дополнительных признаков, кроме имеющейся у валидатора параметризации диапазоном не понадобилось. Ну, а диапазон – часть Вашего домена, как видно из логики Вашего приложения.
Случай с базой. База – тоже вполне себе доменная модель. База – это не только хранение данных, там вполне себе прописываются очень многие ограничения и тяжелая логика. Если Вы ковыряли любую реляционную БД, то знаете, что там ооооочень сложную логику можно прописывать... и, кстати, параметризировать тоже, как в примере выше.
Ответ на этот вопрос уже был дан. Но еще раз повторю другими словами.
Группировка функционала идет не по месту использования, а по максимизации возможности переиспользования и минимизации дублирования кода, как в примерах выше.
Но если Вы хотите помещать валидаторы во вью или вьюмодель — дело Ваше. Вы просто не сможете переиспользовать валидаторы, если нужно будет вашу логику перенести на другую платформу. Всего лишь. придется их все написать по новой для каждой новой платформы. И покрыть одинаковыми тестами. Всего лишь.
Не соглашусь с Вами. Такой логике место в модели. Она уже доменная, бизнесовая.
Проверка аналогичная. Если мы переносим вью в другие приложения в рамках одной платформы, валидация переезжает вместе с вьюхой? Конечно нет. В другом приложении будет другая логика, другие данные, другие валидации.
И наоборот. Если мы доменную логику переносим на другую визуальную платформу, она должна сохраниться? Конечно, да! Неважно, как выглядят поля ввода, валидация данных сохраняется.
Так что увы, тезис остается неизменный — во вью-модели есть только 3 ответственности: абстракция, трансформеры (или мэпперы) и байндер. Все остальное — либо вьюха, либо модель. Но ни в коем случае не пихайте все подряд во вьюмодель. Этим Вы всю концепцию убьете. Понятно, что она Вам, видимо, и не нужна (Вы не переиспользуете ни вью в разных приложениях, ни логику на разных платформах). Но тогда Вам и MVVM не нужен. А раз не нужен, зачем его извращать? Просто не используйте, всем от этого только легче будет.
Бегло глянул концепцию, которую Вы продвигаете в своих статьях. Считаю, что в комментах Вам не зря пишут. Иногда с сарказмом 🤷♂️ простите. Но не сдавайтесь, возможно, найдете сторонников. 💪🏻
Добрый день. Спасибо за вопрос.
Автор видит как-то так. Уж простите. :)
View не является "фасадом" в значении шаблона Банды Четырех. Это вполне себе цельная ответственность (и соответственно логика, бывает ответственность без логики?) – поговорить с пользователем: выслушать его, отобразить ему ответ. Неважно в каком виде его выслушать и в каком вернуть ответ: консоль разработчика – это тоже View. Шлем VR – это тоже View. и т.д. Соответственно, у View тоже есть своя логика (но она ограничивается пониманием пользователя и показом ему ответов). Пример, нажатие на кнопку её подсвечивает, отпускание нажатия подсветку убирает. У Apple, как минимум, у кнопки для этой логики нет отдельной вьюмодели. Вся эта логика размещена прямо во View и это кажется логичным, нет?
ViewModel -- вот вики и картинка из неё (оригинальная) говорят, что "реализует модель данных (presentation model) слоя presentation layer и внутреннюю логику слоя presentation layer" находится где-то между View и ViewModel. Я не могу это однозначно трактовать... Оно размазано между ними двумя? Оно относится к стрелочке биндинга? Оно размазано между двумя слоями и еще и стрелочкой биндинга? Мне сложно сказать по одной лишь картине... Поэтому я учитываю коммент про View выше, который является самостоятельной ответственностью со своей логикой презентации, а также описание шаблона в Вики, которая говорит, что ответственностей у вью-модели всего 3, и среди них нет логики презентации. Еще могу сказать, что логика презентации должна быть переиспользуемой в пределах устройства, но между разными приложениями. Т.е.я могу взять кнопку, о которой говорилось выше, вставить в другое приложение и она точно так же при нажатии начнет подсвечиваться, а при отпускании подсветка будет сниматься... Если предположить, что эта логика будет не в кнопке, а во вью-модели, то получается, что из приложения в приложение мы должны переносить не только класс самой кнопки, но и класс её вьюмодели. Но!..
Тут мы переходим к тому, что Вы сказали (и Вики, и автор с вами согласны), что вьюмодель может содержать мэппер (преобразователь данных в дословном переводе). Так вот он жестко связан с тем, что он переводит (и с моделью, и с вью). Значит, что при таком переносе, я не смогу какую-то другую модель подключить к этой комбинации вью+вьюмодель. Только ту, которая ждет данные в нужном формате. Значит, вся концепция переиспользования вью убивается на корню. А MVVM должна, вроде как, её сохранять...
Отсюда следует, что логика презентации должна быть во вью, иначе вся концепция разваливается.
А картинка из Вики... Ну, я не зря в начале статьи написал, что картинка и очень проста, и одновременно безумно сложна. Так оно и есть при более глубоком рассмотрении.
Что скажете по этому поводу?
И Вам спасибо. Еще поизучаю type erasure, попробую понять Вашу мысль.
Пусть будет так, спорить не собираюсь. Пусть для Вас и для остальных, кто не согласен, что это стирание типов, это будет способ, называемый адаптер объектов.
На мой взгляд Ваш последний демонстрируемый пример ближе к ассоциированным типам из статьи.
Отмечу также, что в этом примере не хранится модель, а сохраняются данные модели (замыкание, а через него имя, автора, цитату). Конечно, в этом случае невозможно определить тип модели, просто потому, что Вы её не сохранили. Это же можно сделать и в моих примерах. В обоих – и в стирающей типы обертке и в ассоциированных типах.
Вообще последний пример выглядит сильно крипово.
Использоваться это будет так, поправьте меня:
Т.е.у вьюмодели (которая почему-то называется ячейкой) мы запрашиваем ячейку (реальную вьюху у вьюмодели) и передаем ей параметром таблицу. Все перевернуто с ног на голову.
При этом вьюмодель содержит замыкания.
Уважаемому @laptev_amниже я отмечал, что как раз замыкания использовать для автоматического обновления я не рекомендую. Как раз вот в том числе и из-за вот таких вот "перевертышей".
Закончу простым вопросом. Вам, правда, Ваш последний пример кода кажется проще любого из приведенных в статье?
Так а где Вы в моей статье увидели "скрытие ViewModel за абстракциями"?
Способ 1 и 2.1 - это тупо протоколы и ничего более. Там вьюмодель сама является протоколом, а не скрывается им.
Остальные способы не содержат "скрытие ViewModel за абстракциями". Или Вы о каких-то непонятных мне абстракциях?
Обертка, стирающая тип, сама тоже абстракцией не закрывается. Смотрите пример. Она и есть абстракция. Более того, я и Вам повторю, как уже написал выше это @house2008: статья как раз и показывает неправильный пример, где вьюмодель содержит сразу 3 абстракции! Отдельно отмечается, что так делать не нужно.
:)
Спасибо, понятно.
Оба случая — обертка, стирающая типы.
В вашем случае обертка — это протокол. В моем — класс. Но в обоих случаях их цель, скрыть детали реализации и интерфейс от потребителя.
Спросил Гугл, он именно так и сказал:
«Manual Wrapping: Traditionally, a wrapper struct or class (e.g., AnyMyProtocol) is created to wrap the concrete type and forward methods.»
Как бы Вы этот способ назвали, если им постоянно пользуетесь и не считаете это оберткой, стирающей типы?
Ну, кроме «адаптера объекта, т.к.это тоже оно самое в чистом виде.
Просто для уточнения. А у Вас чем от "простого проксирования" отличается? Тем, что есть преобразование данных? Ну, я опять же в начале статьи указал, что оставим это за скобками, чтобы не скрыть суть. Но в некоторых примерах в комментах подписано, что собственно вот там и можно осуществлять преобразование, а где-то оно даже есть (из свойства с одним названием, в свойство с другим, но того же типа).
Я, в принципе, много про стирание типов разбирался прежде, чем это опубликовать. Кажется, это в чистом виде оно. Но поправьте меня, почему Вы считает, что это не оно.
Вам спасибо, за хороший вопрос и детальный пример. Это мне и нужно было, чтобы люди делились тем, как делают они. Хочется разобраться, какой же вариант самый популярный, кто пользуется непопулярными, в каким случаях, есть ли еще какие-то способы...
Вам тоже отвечу по частям, постараюсь максимально кратко.
О сложностях какого способа Вы говорите? Способ 1 очень даже простой. Там кроме протокола вообще больше ничего нет. На мой взгляд даже проще, чем тот, что описали Вы. Сложно судить по тексту без кода, что Вы описываете, но кажется, что Вы говорите как раз о том, что привел уважаемый @house2008. Если так, то это в чистом виде способ 5 – обертка, стирающая типы. В этом способе появляется 2 новых сущности (а не одна) – протокол и класс. А для класса еще и объекты надо создавать и потом управлять их жизненным циклом. В первом случае это не нужно совсем.
"Основной концепт подхода в том, что при изменении данных/стейта внутри ViewModel автоматически обновляется View." Не стану с этим спорить, но не зря я ссылку дал на Википедию же. Она утверждает, что это не главное и вообще опциональное. Это лишь третья ответственность из трех возможных, называемая биндинг. Автоматического обновления можно и без биндинга добиться, например, шаблоном "Наблюдатель"/"Observer". Или даже простым делегированием или замыканиями (в терминологии андроида, кажется, это колбеки), но я крайне не рекомендую делать ни первое, ни второе – других проблем огребете.
А вот важна во вьюмодели как раз абстракция, и Вы её добиваетесь оберткой, стирающей типы. Сравните внимательно.
Я смог ответить на Ваши вопросы?
ПС: не стесняйтесь, отмечайте тогда вариант 5 в опросе. Это он!
В вашей реплике сразу несколько вопросов и неточностей. По очереди и отвечу.
Именно для того, чтоб не перегружать примеры, в начале статьи и делается сноска, что инжектирование специально остается за скобками. Иначе она спрячет от Вас суть. Но Вы можете самостоятельно убедиться, что ни один из способов не изменится, если добавить в него инжектирование. Они (в смысле инжектирование и способы) независимы друг от друга. Освоив любой из способов (или даже несколько, или даже все) Вы легко сможете использовать их с инжектированием. Отдельно в статье отмечено (ближе к концу), что ни с одним из перечисленных способов не должно быть проблем с тестированием именно потому, что сама реализация уже содержит абстракцию, а значит, может легко позволить подменить вью для модели или модель для вью.
Не во всех перечисленных примерах вьмодели скрыты протоколами. Абстракция – это не только протоколы. Статья пытается это показать. В частности, Ваш пример – это вариант 5, обертка, стирающая тип. Не самый простой вариант (есть проще), но самый популярный, как и отмечено в статье. И при этом мало известный :) Не удивительно, что он первый же в комментариях и проявился. Вам осталось проголосовать в опросе, не стесняйтесь, пожалуйста! Давайте создадим статистику!
Что касается "по самому паттерну верно делать интерфейсы для vm, но на практике это очень избыточно и даже вредно." Так статья именно это и пытается показать! Еще раз: далеко не во всех перечисленных примерах вьюмодель спрятана за протоколом. Более того, я тоже считаю, что иногда (если это не варианты 1, 2.1, где вьюмодель – это и есть протокол и ничего более, или, может быть, еще какие-то) это вредно! К тому же, в самом шаблоне нет ни слова про протокол! Там лишь написано, что вьюмодель должна быть абстракцией! А уж какой способ реализации абстракции Вы выберете - неважно, это все равно будет MVVM. Проблема будет только если а) Вы вообще забудете абстракцию; б) нагородите их слишком много.
Я смог ответить на Ваши вопросы?
Звучит крайне интересно.
А можете кратенько описать, что это и как? Я слегка погуглил. Получается, это возможно только в облаках сторонних провайдеров? Да и то, только потому, что они тестовые среды не предоставляют? Т.е. если я захочу такое у себя, то мне надо отобрать доступы у разработчиков и тестировщиков к тестовым средам? или как мне этого serverless добиться?
Не по теме: воспитательную работу с товарищем, напрямую оперирующим слоями, провели? :)
Кросс-ссылка на схожий материал о пользе автотестов.
Тимофей, спасибо за хорошо проработанный материал.
Возможно будет интересно. Вам или Вашим читателям. Кросс-ссылочка. Мы тоже пытались показать, как автотесты экономят именно время разработчика. Не так красиво получилось, как у Вас, зато есть конкретные цифры, не сильно отличающиеся от Ваших.
И снова соглашусь с первым комментарием. Захардкоженная навигация только у Вас. У меня вот проблем нет. И вся статья дальше тоже теряет актуальность в связи с этим.
Почему нет проблемы? Все просто. Перенесите первый же Ваш код сниппет (с экшеном кнопки) в расширение UINavigationController. Все. Ни один Ваш унаследованный контроллер не знает ни об этом экшене, ни о других контроллерах, ни об их взаимосвязи...
Попробуйте, это крайне просто. Сильно проще, чем координатор. И эстетичнее. Как команды в системном меню...
Сам тоже сейчас редактирую статью про "правильную готовку" MVVM. Думал уже не публиковать, но, видимо, теперь придется :)
В статье Вики про MVVM указано, что вьюмодель содержит всего 3 ответственности:
абстракцию для разделения вьюхи и модели (слабая связность).
преобразование данных из вида модели к виду отображения (и возможно, но необязательно, обратно).
биндинг - вариация абстрагирования и/или преобразования данных.
Вот, в принципе и все, что имеет право быть во вьюомдели. Все остальное, что вы перечислили, это уже либо модель, либо представление. Без вариантов! Например, навигация – ответственность представления. Если Вы меняете систему представления (например, с iOS переезжаете на мак, часы или телевизор), то в Вашем приложении не нужно бы менять ни вьюмодель, ни модель... а если Ваша модель зависит от роутера или координатора, то вам этого не избежать... а вот если роутер или координатор дергаются напрямую из вью, то остальные два слоя вашего приложения изменений не требуют – достаточно поменять только представление...
Unidirectional Data Flow – это пункт 2 из обязательных ответственностей. Преобразование данных. В одну и в другую сторону. Они в 142% случаев всегда независимы друг от друга, да. Поэтому реализовать их в двух разных классах не представляет сложности никогда. Но при этом оба класса все же остаются внутри слоя вьюмодели. Это слой. Он не обязан состоять из одного класса... В вашем примере Вы просто лишь одну часть этой ответственности осуществляете через биндинг. Но можно и обе, проблем не будет, это все еще будет UDF, если вью будет писать в одно свойство, а модель в другое (в одном или разных объектах – уже неважно)...
Но в принципе биндинг для преобразования данных не обязателен, как и для абстракции. Но с его помощью можно реализовать и то, и другое. А можно и не реализовывать. Биндинг для MVVM не обязателен, но тогда оно выродится в MVP или MVC с пассивной моделью.
В остальном, Вы молодец. Копайте глубже и проблем у Вас не будет ни с какой архитектурой. Еще и на лету их сможете менять, если ответственности представления (типа навигации) не будете опускать ниже.
ПС: неплохо Вы статьи генерите – по одной в день – ИИ помогает?
Соглашусь с первым комментарием по поводу состояния.
"Состояние"("State") – это прям отдельный шаблон из книжки Банды Четырех, чтобы с ним работа была комфортна, он должен реализовываться через набор классов с одним и тем же интерфейсом. Иначе, рано или поздно столкнетесь с проблемами. Например, enum'ы не расширяемые. Добавить состояние без нарушения OCP у вас не выйдет. В мелких проектах это и не важно, но в крупных...
Ну, и Вы тут много говорите о том, что не нужно смешивать ответственности, а "Состояние" (с переходами между ними, которые довольно часто не тривиальные! и не все вообще разрешены!) – вполне себе цельная отдельная ответственность...
Попробуйте её инкапсулировать по шаблону Банды Четырех. Не с первого раза, но получится всю прелесть прочувствовать...
Даже художники и писатели "струячат" свои шедевры, как на конвейере, чтобы выжить. Стиг Ларсон, Ю Несбё и прочие...
Есть ли где-то не конвейер в этой жизни? :) наверное, только у бомжа в зимнем московском трамвае - вот где истинная свобода...