Decomposition of Software Components

Decomposition of Software Components

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.

Example

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.

Example

// 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.

Example

// 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.

Example

// 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.

Example

// 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.

Example

$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 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.

Leave a Reply

Your email address will not be published. Required fields are marked *