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

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

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

Model-View

Модель знает как себя по разному представлять.

MV
MV

Пример

class User { // Model
	
	_id: bigint
	_nickname: string
	
	toString() { // View
		return 'user=' + this._id
	}
	
	toJSON() { // View
		return {
			id: String( this._id ),
			name: this._nickname,
		}
	}
	
}

Особенности

✅ Удобно из модели получать любые отображения.

❌ Добавление нового отображения требует изменения модели.

❌ Отображение полностью определяется одной основной моделью.

❌ Загрузка модели вытягивает по зависимостям и все её отображения.

❌ Двух слоёв слишком мало на больших масштабах.

View-Model

Код работы с моделями пишется прямо в отображении.

VM
VM

Пример

// View
function Task_list() {
	return <ul>{
		Task.list.map( task =>
			<li><Task_row {task} /></li>
		)
	}</ul>
}

// Model
class Task {
	static list = [] as Task[]
}

Особенности

✅ Отображение может использовать произвольные модели.

✅ Легко добавлять новые отображения, не меняя модели.

❌ Для отображения разных моделей необходимо дублировать код отображения.

❌ Изменение интерфейса модели требует обновления всех использующих её отображений.

❌ Двух слоёв слишком мало на больших масштабах.

Model-View-ViewModel

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

MVVM

Пример

// View
<li class="User_card" model="User_card_model">
	<img src={ image } />
	<p>{ message }</p>
</li>

// ViewModel
class User_card_model {
	user = User.current
	get image() {
		return this.user.avatar
	}
	get message() {
		return this.user.nickname
	}
}

// Model
class User {
	avatar: string
	nickname: string
	static current = new User
}

Особенности

✅ Отображение может использовать произвольные вьюмодели.

✅ Легко добавлять новые отображения, не меняя ни модели, ни вьюмодели.

✅ Изменение интерфейса модели или отображения требует изменения только лишь вьюмодели.

✅ Одну и ту же вьюмодель можно шарить между несколькими отображениями.

❌ Для отображения разных моделей необходимо дублировать код отображения и вьюмодели.

❌ Трёх слоёв слишком мало на больших масштабах.

Model-View-Controller

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

MVC
MVC

Пример

// Controller
class Users_resource {
	GET() {
		return User.all.map( user_brief )
	}
}

// View
function user_brief( user: User ) {
	return {
		id: user.guid,
		name: user.passport.name_full,
	}
}

// Model
class User {
	
	static all = [] as User[]
	
	guid: GUID
	passports: Passport[]
	resumes: Resume[]
	
	get passport() {
		return this.passports[0]
	}
	
}

Особенности

✅ Отображение может использовать произвольные модели с тем же интерфейсом.

✅ Легко добавлять новые отображения, не меняя модели. И наоборот.

❌ Для отображения разных типов моделей необходимо дублировать код отображения.

❌ Изменение интерфейса модели требует обновления всех использующих её отображений и контроллеров.

❌ Трёх слоёв слишком мало на больших масштабах.

Model-View-Presenter

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

MVP
MVP

Пример

// Presenter
class User_preview {
	user: User
	card = new Card({
		image: ()=> this.user.avatar,
		message: ()=> this.user.nickname,
		color: ()=> this.user.skin.color,
		click: ()=> this.skin_change(),
	})
	skin_change() {
		this.user.skin = Skin.random()
	}
}

// View
<div class="Card" onclick={click} style={{ background: color }}>
	<img src={ image } />
	<p>{ message }</p>
</div>

// Model
class User extends Model {
	avatar: string
	nickname: string
	skin: Skin
}

Особенности

✅ Легко добавлять новые отображения, не меняя модели. И наоборот.

✅ Изменение интерфесов модели или отображения требует изменения только лишь презентеров.

❌ Трёх слоёв слишком мало на больших масштабах.

❌ Для использования состояния одного презентера из другого необходимо искусственное вынесение его в модели.

ModelView Fractal

Каждый ModelView выступает в роли модели/контроллера для ведомых ModelView и в качестве отображения для владеющего ModelView. Часть логики может выноситься как в чистые Model, так и в чистые View, которые являются лишь вырожденными случаями ModelView.

MVF
MVF

Пример

$my_user_list $my_view
	- \Owner ModelView
	users? /$my_user
	kids /
		<= Row*0 $my_user_row
			user <= user* $my_user

$my_user_row $my_card
	- \Having ModevView
	user $my_user
		avatar => image
		nickname => message

$my_card $my_view
	- \View not Model
	kids /
		<= Image $my_image
			uri <= image \about:blank
		<= Message $my_text
			text <= message \
	
$my_user $my_model
	- \Model not View
	avatar? \
	nickname? \

Особенности

✅ Каждый ModelView полностью контролирует внутренние ModelView и ничего не знает про внешние.

✅ Любой ModelView может шариться между разными другими ModelView на любом уровне композиции.

✅ Изменение интерфейса ModelView требует изменения только лишь его владельцев.

✅ Фрактальная структура легко масштабируется на приложения любого размера.

Выводы

$mol_view построен на идеях MVF, так как это наиболее простой паттерн декомпозиции, который легко масштабируется по мере потребности и хорошо разделяет разные уровни абстракции.

Ссылочки

Другие анализы
Следите за новостями
Обсуждайте наболевшее
Донатьте на печеньки


Актуальный оригинал на $hyoo_page.

Комментарии (18)


  1. rukhi7
    07.04.2024 07:22

    стрелочки, квадратики, ...

    фигня какая-то. На заборе тоже очень многозначительно написано, но там просто дрова лежат за забором.


  1. SuperCat911
    07.04.2024 07:22

    Спасибо за публикацию. Интересно.

    Последний пример привели бы на ts с jsx, чтобы было понятно во что ваш tree превращается.

    Я часто использую MVC. Посмотрел на картинку. Все ли там корректно? У меня на практике код для View, код Model изолированы друг от друга. Взаимодействие отражается только в секции Controller.

    Схема движения данных такая: View <--> Controller и Model <--> Controller. И нет связи между View и Model.

    Кстати, если говорить о том, кто кого знает, то только Controller знает о View и Model. Они же в свою очередь не знают о Controller и друг друге


    1. nin-jin Автор
      07.04.2024 07:22

      Во чо-то такое, но в данном случае это не особо важно.

      Если View не связана с Model, то это, очевидно, не MVC, а MVP.


      1. SuperCat911
        07.04.2024 07:22

        Блин, точно. Думал, что у меня MVC, а оно MVP ))


  1. murkin-kot
    07.04.2024 07:22
    +11

    $mol_view построен на идеях MVF

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

    Вообще, подход автора непонятен. Сначала он расписывает преимущества и недостатки разных подходов. Уже на этом этапе он совершает непонятное - зачем-то в каждом случае собирает отдельный набор критериев оценки. Далее же просто заявляет - вот в этом произвольном наборе критериев выставлены все галки, и видимо поэтому он использован в моей библиотеке.

    Автор, что такое сравнимость результатов? Это когда попугаев сравнивают с попугаями, а не с мандаринами. Во всяком случае мне (и не только мне) так казалось до сих пор. Но автор предложил альтернативу - мерить слона в попугаях. А вдруг взлетит?

    Мне кажется, что взлетит оно только в детских сказках. А в серьёзных задачах стоит вернуться к классике - измерять слона хотя бы в метрах, а сравнивать попарно всё же попугая с попугаем, а не со слоном.


    1. nin-jin Автор
      07.04.2024 07:22

      ...когда нечего сказать, но очень хочется. Покажите же нам всем пример, как правильно сравнивать эти паттерны.


      1. murkin-kot
        07.04.2024 07:22
        +9

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

        Ну и по сравнению.

        Во первых, не указан контекст. То есть в каких-то ситуациях один паттерн лучше, а в других, соответственно, другой лучше. И это зависит именно от контекста. Вы в своей библиотеке выбрали один, но из-за отсутствия контекста при сравнении не можете даже для себя лично представить, что вы потеряли, оставив за бортом все остальные паттерны.

        Во вторых, собственно паттерны, это чаще всего штука очень примитивная, содержащая минимум идей. Но вы, вместо анализа этих идей (а они лежат на поверхности), смело прицепили к каждому своё видение, мол вот этот паттерн плох здесь и хорош только здесь. То есть даже не попробовали как-то обобщить опыт использования различных идей при создании UI. Соответственно, вы потеряли то главное, что вполне могли бы увидеть, проведя обобщение. А остальным предложили как-нибудь самим со всем этим разобраться. Но нужна ли им ваша статья, если они разберутся сами?

        Ну и в третьих. Следует двигаться от задачи. То есть от построения UI. И вот тогда вы могли бы предложить что-то ценное, а потом уже указать, что найденное работает хуже/лучше с такими-то паттернами. То есть тогда была бы основа для сравнения. У вас же никакой основы нет. Я даже удивлён - ожидал от вас большего проникновения в глубины пиления фронта, но обнаружил очень поверхностный подход.


        1. nin-jin Автор
          07.04.2024 07:22

          Пока что вы показываете нам лишь пример неистового словоблудия. Так, ребята, кто пустил эту нейронку в интеренет?


  1. kipar
    07.04.2024 07:22
    +7

    А можно пример последнего паттерна на обычном ts? Потому иначе напрашивается красный крестик "требует значительного бойлерплейта или использования специализированных библиотек".


    1. nin-jin Автор
      07.04.2024 07:22

      Нельзя. Не ленитесь учиться.


      1. kipar
        07.04.2024 07:22
        +8

        Да мне и typescript не особо интересен, не то что фреймворк для него. Просто если для остальных паттернов по примерам в целом понятно как на любом языке реализовать, то для последнего получается что без сложной библиотеки его даже псевдокодом не описать. И это несомненный минус.


        1. nin-jin Автор
          07.04.2024 07:22

          Мне тайпскрипт не интересен, поэтому дайте мне код на тайпскрипте, иначе я поставлю вам ❌и ➖.


          1. kipar
            07.04.2024 07:22
            +6

            можно и не на тайпскрипте. На питоне, шарпе, на чем угодно что можно понять не изучая $mol сложных библиотек.


  1. francyfox
    07.04.2024 07:22
    +10

    Дмитрий без обид, но это классический пост про $mol. Напоминает рекламу из боковых баннеров: "У меня теперь стоит как у быка, ведь я принимаю ..." Хотите узнать больше?
    В этом есть какая-то выгода, помимо саморазрушения? Какой план? Наступать на одни и те же грабли?


    1. nin-jin Автор
      07.04.2024 07:22

      Что мертво, умереть не может.


      1. doctorw
        07.04.2024 07:22

        Жить не может тоже.


  1. Janycz
    07.04.2024 07:22

    Я немного извиняюсь за тупость, но у меня вот такой вопрос: если у меня в MVVM классы Model и ViewModel сливаются в один класс, то что это? С одной стороны, это не View-Model, поскольку код работы с моделями не пишется прямо в отображении. С другой стороны, это не Model-View, поскольку пользователь не взаимодействует с моделью напрямую, да и модель не знает как себя представлять. В коде это выглядит как-то так: https://pastebin.com/0Xst5chU

    И кроме того, почему в голосовании можно выбирать лишь одну опцию? Что делать, если в приложении одновременно используются разные паттерны?


    1. nin-jin Автор
      07.04.2024 07:22

      Я бы сказал, что это MVVM с микромоделями содержащими одно значение.