Для будущих студентов курса “Архитектура и шаблоны проектирования” и всех интересующихся подготовили перевод полезного материала.
Также приглашаем посетить открытый вебинар на тему “Интерпретатор”. На нем будут обсуждаться назначение и структура шаблона “Интерпретатор”, формы Бекуса-Науэра, лексический, синтаксический и семантический анализы.
Источник: документация Rails
Архитектура MVC разделяет ваш код на три (3) уровня: модели (Models), представления (Views) и контроллеры (Controllers), выполняющие различные задачи внутри программы.
Уровень модели
В Ruby on Rails этот уровень содержит модель предметной области, которая обычно представляет определенный класс объектов (например, Человек, Животное, Книги). Обычно именно здесь обрабатывается бизнес-логика, поскольку модель связана с базой данных, и данные для нее извлекаются из строк соответствующей таблицы.
Уровень представления
Обрабатывает визуальное представление ответов, предоставляемых контроллерами. Поскольку контроллер может возвращать информацию в формате HTML, XML, JSON и т. д.
Уровень контроллера
В Rails этот уровень отвечает за взаимодействие с моделью, манипулирование ее данными и предоставление соответствующих ответов на различные HTTP-запросы.
Источник: документация MDN
Поскольку JavaScript обычно не предполагает использования баз данных (хотя и может) или обработки HTTP-запросов (опять же, может), паттерн MVC придется немного подкорректировать, чтобы он соответствовал специфике языка.
Уровнем модели может быть служить даже что-то настолько простое, как массив, но зачастую это будет какой-нибудь класс. Приложение может иметь несколько моделей, и эти классы (модели) будут содержать основные данные, необходимые для работы приложения.
Возьмем, к примеру, приложение Classroom, которое отслеживает, какие классы посещает человек. В этом случае уровень модели можно разделить на классы, такие как Classroom
, Person
и модель на основе массива под названием Subjects
.
class Classroom {
constructor(id, subject = 'Homeroom') {
this.id = id;
this.persons = [];
this.subject = subject;
}
}
Модель Classroom
содержит переменные данных, которые будут содержать информацию по каждому классу. Сюда будут входить список всех людей, которые в настоящее время числятся в этом классе, предмет, связанный с этим классом, и его id
.
class Person {
constructor(id, firstN = 'John', lastN = 'Doe') {
this.id = id;
this.firstName = firstN;
this.lastName = lastN;
this.subjects = [];
this.classrooms = [];
}
}
Модель Person
содержит переменные данных, которые будут содержать информацию о каждом человеке. Сюда будут входить его имя и фамилия, предметы, которые он изучает, и классы, которые он посещает.
const subjects = [
"English",
"Math",
"Computer Science",
"Business",
"Finance",
"Home Economics"
];
Модель Subjects
будет просто массивом, поскольку для этого примера я не собираюсь разрешать манипулировать моделью дисциплин.
Контроллером будет класс, который транслирует вводимые пользователем данные в изменения данных модели.
Например, в приложении Classroom — контроллер получает данные, вводимые пользователем, от элементов представления, таких как ввод текста (text input
) или выбор из списка опций (select options
), а также нажатия кнопок, которые используются для изменения модели.
import classroomModel from "../models/classroom";
class ClassroomController {
constructor() {
this.lastID = 0;
this.classrooms = [];
this.selectedClass = null;
}
selectClassroom(classroomID) {
this.selectedClass = this.classrooms
.filter(c => c.id === parseInt(classroomID, 10))[0];
}
addClassroom(subject) {
this.classrooms.push(
new classroomModel(this.lastID, subject)
);
this.lastID += 1;
}
removeClassroom(classroomID) {
this.classrooms = this.classrooms
.filter(c => c.id !== parseInt(classroomID, 10));
}
setSubject(subject, classroomID) {
const classroom = this.classrooms
.filter(c => c.id === parseInt(classroomID, 10))[0];
classroom.subject = subject;
}
addPerson(person, classroom) {
// const classroom = this.classrooms
// .filter(c => c.id === parseInt(classroomID, 10))[0];
if (!person) return;
classroom.addPerson(person);
}
removePerson(person, classroomID) {
const classroom = this.classrooms
.filter(c => c.id === parseInt(classroomID, 10))[0];
classroom.removePerson(person);
}
}
В этом случае ClassroomController
можно рассматривать как таблицу (если сравнивать с тем, как работает Rails), и каждая строка в этой «таблице» будет представлять информацию, связанную с каждым уже созданным объектом класса.
Этот контроллер имеет три собственные переменные: «lastID
» (каждый раз, когда объект класса создается и добавляется к массиву классов, значение этой переменной инкрементируется), «classrooms
» (массив всех созданных объектов класса) и «selectedClass
».
Этот уровень обрабатывает визуальное представление данных приложения. Этот уровень содержит классы, которые позволяют пользователю видеть данные и взаимодействовать с ними.
Например, в приложении Classroom — представление будет предоставлять элементы DOM (объектной модели документа), такие как кнопки, инпуты и контейнеры (<div/>, <span/ >, <p/>… и т. д.) для отображения различных людей и классов, и связанных с ними данных.
import classroomController from "../controllers/classroom";
import subjects from "../models/subjects";
class ClassroomView {
constructor(appDiv) {
this.classroomController = new classroomController();
this.classroomSectionDiv = document.createElement('div');
this.classroomsDiv = document.createElement('div');
this.addclassBtn = document.createElement('button');
this.selectSubjectInput = document.createElement('select');
this.classroomSectionDiv.classList.add('classroom-section');
this.classroomsDiv.classList.add('classroom-container');
this.selectSubjectInput.innerHTML = subjects.map((option, index) => (
`<option key=${index} value=${option}>${option.toUpperCase()}</option>`
));
this.addclassBtn.textContent = 'New Class';
this.addclassBtn.addEventListener('click', () => this.addClassroom());
this.classroomSectionDiv.append(
this.classroomsDiv, this.selectSubjectInput,
this.addclassBtn,
);
appDiv.appendChild(this.classroomSectionDiv);
}
updateView() {
const { classroomController, classroomsDiv } = this;
const allClassrooms = classroomController.classrooms.map(
c => {
const removeBtn = document.createElement('button');
const classDiv = document.createElement('div');
classDiv.classList.add('classroom');
if (classroomController.selectedClass === c) {
classDiv.classList.add('selected');
}
classDiv.addEventListener('click', () => this.selectClassroom(classDiv.getAttribute('data-classroom-id')));
classDiv.setAttribute('data-classroom-id', c.id);
removeBtn.addEventListener('click', () => this.removeClassroom(removeBtn.getAttribute('data-classroom-id')));
removeBtn.setAttribute('data-classroom-id', c.id);
removeBtn.classList.add('remove-btn');
removeBtn.textContent= 'remove';
const allPersons = c.persons.map(p => (
`<div class="person-inline">
<span class="fname">${p.firstName}</span>
<span class="lname">${p.lastName}</span>
<span class="${p.occupation}">${p.occupation}</span>
</div>`
));
classDiv.innerHTML = `<div class="m-b">
<span class="id">${c.id}</span>
<span class="subject">${c.subject}</span></div>
<div class="all-persons">${allPersons.join('')}</div>`;
classDiv.appendChild(removeBtn);
return classDiv;
}
);
classroomsDiv.innerHTML='';
allClassrooms.map(div => classroomsDiv.append(div));
}
selectClassroom(classroomID) {
const { classroomController } = this;
classroomController.selectClassroom(classroomID);
this.updateView();
}
addClassroom() {
const {
classroomController,
selectSubjectInput,
} = this;
const subjectChosen = selectSubjectInput.value;
classroomController.addClassroom(subjectChosen);
this.updateView();
}
removeClassroom(classroomID) {
const { classroomController } = this;
classroomController.removeClassroom(classroomID);
this.updateView();
}
addPerson(person, classroomID) {
const { classroomController } = this;
classroomController.addPerson(person, classroomID);
this.updateView();
}
}
Класс ClassroomView
содержит переменную, которая связана с ClassroomController
, который создается при конструкции. Это позволяет уровню представления общаться с контроллером.
Функция updateView()
запускается после каждого изменения в результате взаимодействия с пользователем. Эта функция просто обновляет в представлении все необходимые элементы DOM соответствующими данными, полученными из связанной модели.
Все функции в представлении просто захватывают значения из UI элементов DOM и передают их как переменные функциям контроллера. Функции selectClassroom()
, addClassroom()
и removeClassroom()
добавляются к элементам DOM через функцию updateView()
как события через функцию addEventListener()
.
Теперь, поскольку для этого примера у нас есть два контроллера, ClassroomController
и PersonController
(можно найти в полном проекте), у нас также было бы два представления, и если бы мы хотели, чтобы эти два представления могли взаимодействовать друг с другом, нам пришлось бы создать единое всеобъемлющее представление. Мы могли бы назвать это представление AppView
.
import classroomView from './classroom';
import personView from './person';
class AppView {
constructor(appDiv) {
this.classroomView = new classroomView(appDiv);
this.personView = new personView(appDiv);
this.addPersonToClassBtn = document.createElement('button');
this.addPersonToClassBtn.textContent = 'Add selected Person to Selected Class';
this.addPersonToClassBtn.addEventListener('click', () => this.addPersonToClass());
appDiv.appendChild(this.addPersonToClassBtn);
}
addPersonToClass() {
const { classroomView, personView } = this;
const { classroomController } = classroomView;
const { personController } = personView;
const selectedClassroom = classroomController.selectedClass;
const selectedPerson = personController.selectedPerson;
classroomView.addPerson(selectedPerson, selectedClassroom);
personView.updateView();
}
}
Класс AppView
будет иметь собственные переменные, которые будут связываться как с ClassroomView
, так и с PersonView
. Поскольку он имеет доступ к этим двум представлениям, он также имеет доступ и к их контроллерам.
Кнопка выше создается AppView
. Оно получает значения selectedClassroom
и selectedPerson
из соответствующих контроллеров и при взаимодействии запускает функцию addPerson()
в ClassroomView
.
Чтобы полностью посмотреть приложение Classroom, переходите в CodeSandBox по этой ссылке.
Источники: Brainvire, c-sharpcorner, StackOverflow, Wikipedia
1. Разделение обязанностей
Весь код, связанный с пользовательским интерфейсом, обрабатывается представлением. Все переменные базовых данных содержатся в модели, а все данные модели изменяются с помощью контроллера.
2. Одновременная разработка
Поскольку модель MVC четко разделяет проект на три (3) уровня, становится намного проще поделить и распределить задачи между несколькими разработчиками.
3. Простота модификации
Можно легко вносить изменения на каждый уровне, не затрагивая остальные уровни.
4. Разработка через тестирование (TDD)
Благодаря четкому разделению обязанностей мы можем тестировать каждый отдельный компонент независимо.
Узнать подробнее о курсе “Архитектура и шаблоны проектирования”.
Зарегистрироваться на открытый вебинар на тему “Интерпретатор”.
Прямо сейчас в OTUS действуют максимальные новогодние скидки на все курсы. Ознакомиться с полным списком курсов вы можете по ссылке ниже. Также у всех желающих есть уникальная возможность отправить адресату подарочный сертификат на обучение в OTUS.
Кстати, о “красивой упаковке” онлайн-сертификатов мы рассказываем в этой статье.
VK объявляет о приобретении 40% компании Intickets.ru (Интикетс). Это облачный сервис для контроля и управления продажей билетов на мероприятия. Сумма…
OpenAI готовится запустить собственную поисковую систему на базе ChatGPT. Информацию об этом публикуют западные издания. Ожидается, что новый поисковик может…
Центр управления связью общего пользования (ЦМУ ССОП) Роскомнадзора рекомендовал компаниям из реестра провайдеров ограничить доступ поисковых ботов к информации на российских сайтах.…
Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…
Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…
Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…