Decomposition of Software Components
Tags: javascript, programming, analytics
Published: 2024-04-01 07:10:00

Hello, my name is Dmitriy Karlovskiy and I... love power. When I take hold of the keyboard, every bit begins to dance to my tune. But when there are really a lot of these bits, it becomes difficult to keep track of them all. So let's compare popular design patterns that allow you to divide a large application into components so you can control them as efficiently and independently as possible.

Since the same application entity occurs in an application in many places, in different contexts, and must have different representations, the basic decomposition consists of selecting a *model* of the subject area, which is the source of truth for all places where it is *viewed*. And here the nuances begin...

💡 Please note that further arrows do not show the movement of data, as they are often drawn, but the presence of knowledge of one component of the system how to work with another. In the limit, this knowledge is expressed in complete control of the life cycle: from creation to destruction. The lack of knowledge gives independence from a specific implementation, and therefore the ability to work with different implementations.

# Model-View

The model knows how to present herself in different ways.

![](https://i.imgur.com/BlmsxgX.png)

## Example

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

## Features

✅ It is convenient to receive any displays from the model.
❌ Adding a new display requires changing the model.
❌ The display is completely determined by one main model.
❌ Loading the model pulls out all its displays according to dependencies.
❌ Two layers are too few on a large scale.

# View-Model

The code for working with models is written directly in the display.

![](https://i.imgur.com/sCAFqra.png)

## Example

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

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

## Features

✅ Display can use arbitrary models.
✅ Easily add new displays without changing models.
❌ To display different models, you need to duplicate the display code.
❌ Changing the model interface requires updating all displays that use it.
❌ Two layers are too few on a large scale.

# Model-View-ViewModel

Mappings work with models through intermediaries that transform domain abstractions into mapping abstractions and back. The ViewModel also acts as a store of non-domain view state.

![](https://i.imgur.com/wPLceQw.png)

## Example

```ts
// 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
}
```

## Features

✅ Display can use arbitrary ViewModels.
✅ Easily add new displays without changing either the model or the ViewModel.
✅ Changing the model interface or display requires changing only the ViewModel.
✅ The same ViewModel can be shared between several displays.
❌ To display different models, you need to duplicate the display code and ViewModel.
❌ Three layers are too few on a large scale.

# Model-View-Controller

The controller creates the display and tells it which model to work with. He also processes all commands from the user and manages his charges.

![](https://i.imgur.com/q2tzCRa.png)

## Example

```ts
// 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]
	}
	
}
```

## Features

✅ Display can use arbitrary models with the same interface.
✅ Easily add new displays without changing models. And vice versa.
❌ To display different types of models, you need to duplicate the display code.
❌ Changing the model interface requires updating all views and controllers that use it.
❌ Three layers are too few on a large scale.

# Model-View-Presenter

Models and views are passive and do not know about each other - they are controlled by the presenter, which also acts as an intermediary between them.

![](https://i.imgur.com/zIWYzOa.png)

## Example

```ts
// 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
}
```

## Features

✅ Easily add new displays without changing models. And vice versa.
✅ Changing the interfaces of the model or display requires changing only the presenters.
❌ Three layers are too few on a large scale.
❌ To use the state of one presenter from another, it is necessary to artificially transfer it into the model.

# ModelView Fractal

Each ModelView acts as a model/controller for slave ModelViews and as a view for the owning ModelView. Part of the logic can be transferred to both pure Model and pure View, which are only degenerate cases of ModelView.

![](https://i.imgur.com/uOoDifS.png)

## Example

```ruby
$my_user_list $my_view
	- \Owner ModelView
	users? /$my_user
	kids /
		<= Row*0 $my_user_row
			user <= 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? \
```

## Features

✅ Each ModelView fully controls internal ModelViews and knows nothing about external ones.
✅ Any ModelView can navigate between different other ModelViews at any composition level.
✅ Changing the ModelView interface requires changing only its owners.
✅ Fractal structure easily scales to applications of any size.

# Conclusions

[`$mol_view`](https://mol.hyoo.ru/#!section=docs/=vv2nig_s5zr0f) is built on the ideas of MVF, since this is the simplest decomposition pattern that easily scales as needed and well separates different levels of abstraction.