Lapa Knowledge Base
Веб-архитектуры

MVC

Паттерн архитектуры Model-View-Controller

MVC — это классический архитектурный паттерн, который разделяет приложение на три основных компонента: Model (Модель), View (Представление) и Controller (Контроллер). Этот паттерн обеспечивает четкое разделение ответственности и упрощает разработку и поддержку приложений.

Что такое MVC?

MVC — это архитектурный паттерн, который:

  • Разделяет ответственность — каждый компонент имеет свою роль
  • Упрощает тестирование — компоненты можно тестировать независимо
  • Повышает переиспользование — компоненты можно использовать повторно
  • Улучшает поддерживаемость — изменения в одном компоненте не влияют на другие

Компоненты MVC

Model (Модель)

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

Ответственности:

  • Хранение и управление данными
  • Бизнес-логика и правила
  • Валидация данных
  • Взаимодействие с базой данных

Примеры:

  • Пользователь (User)
  • Продукт (Product)
  • Заказ (Order)
  • Сервисы и репозитории

View (Представление)

Представление отвечает за отображение данных пользователю.

Ответственности:

  • Отображение данных из модели
  • Пользовательский интерфейс
  • Форматирование данных
  • Обработка пользовательского ввода

Примеры:

  • HTML шаблоны
  • JSON ответы API
  • PDF отчеты
  • Email шаблоны

Controller (Контроллер)

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

Ответственности:

  • Обработка HTTP запросов
  • Валидация входных данных
  • Вызов методов модели
  • Выбор представления для отображения

Примеры:

  • UserController
  • ProductController
  • OrderController
  • API контроллеры

Схема взаимодействия

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│    View     │    │ Controller  │    │    Model    │
│             │    │             │    │             │
│ • UI        │◄───┤ • Logic     ├───►│ • Data      │
│ • Templates │    │ • Routing   │    │ • Business  │
│ • Display   │    │ • Validation│    │ • Database  │
└─────────────┘    └─────────────┘    └─────────────┘
       ▲                   │                   │
       │                   │                   │
       └───────────────────┼───────────────────┘
                    User Interaction

Примеры реализации

MVC в Express.js (Node.js)

Model

// models/User.js
class User {
    constructor(id, name, email, password) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.password = password;
        this.createdAt = new Date();
    }
    
    // Валидация данных
    validate() {
        const errors = [];
        
        if (!this.name || this.name.length < 2) {
            errors.push('Имя должно содержать минимум 2 символа');
        }
        
        if (!this.email || !this.isValidEmail(this.email)) {
            errors.push('Некорректный email');
        }
        
        if (!this.password || this.password.length < 6) {
            errors.push('Пароль должен содержать минимум 6 символов');
        }
        
        return errors;
    }
    
    isValidEmail(email) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    }
    
    // Сохранение в базу данных
    async save() {
        // Логика сохранения в БД
        console.log('Сохранение пользователя: ' + this.name);
        return this;
    }
    
    // Получение из базы данных
    static async findById(id) {
        // Логика получения из БД
        return new User(id, 'John Doe', 'john@example.com', 'hashed_password');
    }
    
    static async findAll() {
        // Логика получения всех пользователей
        return [
            new User(1, 'John Doe', 'john@example.com', 'hashed_password'),
            new User(2, 'Jane Smith', 'jane@example.com', 'hashed_password')
        ];
    }
}

module.exports = User;

View

// views/userTemplate.js
class UserTemplate {
    static renderUser(user) {
        return `
            <div class="user-card">
                <h2>Имя пользователя</h2>
                <p>Email: email@example.com</p>
                <p>Создан: 01.01.2024</p>
                <div class="actions">
                    <a href="/users/1/edit">Редактировать</a>
                    <a href="/users/1/delete">Удалить</a>
                </div>
            </div>
        `;
    }
    
    static renderUserList(users) {
        const userCards = users.map(user => this.renderUser(user)).join('');
        
        return `
            <!DOCTYPE html>
            <html>
            <head>
                <title>Список пользователей</title>
                <style>
                    .user-card { border: 1px solid #ccc; margin: 10px; padding: 15px; }
                    .actions a { margin-right: 10px; }
                </style>
            </head>
            <body>
                <h1>Пользователи</h1>
                <div class="users-container">
                    ${userCards}
                </div>
                <a href="/users/new">Добавить пользователя</a>
            </body>
            </html>
        `;
    }
    
    static renderUserForm(user = null) {
        const isEdit = user !== null;
        const action = isEdit ? '/users/1' : '/users';
        const method = isEdit ? 'PUT' : 'POST';
        
        return `
            <!DOCTYPE html>
            <html>
            <head>
                <title>${isEdit ? 'Редактирование' : 'Создание'} пользователя</title>
            </head>
            <body>
                <h1>${isEdit ? 'Редактирование' : 'Создание'} пользователя</h1>
                <form action="${action}" method="${method}">
                    <div>
                        <label for="name">Имя:</label>
                        <input type="text" id="name" name="name" value="${user?.name || ''}" required>
                    </div>
                    <div>
                        <label for="email">Email:</label>
                        <input type="email" id="email" name="email" value="${user?.email || ''}" required>
                    </div>
                    <div>
                        <label for="password">Пароль:</label>
                        <input type="password" id="password" name="password" ${isEdit ? '' : 'required'}>
                    </div>
                    <button type="submit">${isEdit ? 'Обновить' : 'Создать'}</button>
                </form>
            </body>
            </html>
        `;
    }
}

module.exports = UserTemplate;

Controller

// controllers/UserController.js
const User = require('../models/User');
const UserTemplate = require('../views/userTemplate');

class UserController {
    // Показать всех пользователей
    async index(req, res) {
        try {
            const users = await User.findAll();
            const html = UserTemplate.renderUserList(users);
            res.send(html);
        } catch (error) {
            res.status(500).send('Ошибка при получении пользователей');
        }
    }
    
    // Показать конкретного пользователя
    async show(req, res) {
        try {
            const user = await User.findById(req.params.id);
            if (!user) {
                return res.status(404).send('Пользователь не найден');
            }
            
            const html = UserTemplate.renderUser(user);
            res.send(html);
        } catch (error) {
            res.status(500).send('Ошибка при получении пользователя');
        }
    }
    
    // Показать форму создания пользователя
    new(req, res) {
        const html = UserTemplate.renderUserForm();
        res.send(html);
    }
    
    // Создать нового пользователя
    async create(req, res) {
        try {
            const { name, email, password } = req.body;
            const user = new User(null, name, email, password);
            
            // Валидация
            const errors = user.validate();
            if (errors.length > 0) {
                return res.status(400).json({ errors });
            }
            
            await user.save();
            res.redirect('/users');
        } catch (error) {
            res.status(500).send('Ошибка при создании пользователя');
        }
    }
    
    // Показать форму редактирования
    async edit(req, res) {
        try {
            const user = await User.findById(req.params.id);
            if (!user) {
                return res.status(404).send('Пользователь не найден');
            }
            
            const html = UserTemplate.renderUserForm(user);
            res.send(html);
        } catch (error) {
            res.status(500).send('Ошибка при получении пользователя');
        }
    }
    
    // Обновить пользователя
    async update(req, res) {
        try {
            const { name, email, password } = req.body;
            const user = await User.findById(req.params.id);
            
            if (!user) {
                return res.status(404).send('Пользователь не найден');
            }
            
            // Обновление данных
            user.name = name;
            user.email = email;
            if (password) {
                user.password = password;
            }
            
            // Валидация
            const errors = user.validate();
            if (errors.length > 0) {
                return res.status(400).json({ errors });
            }
            
            await user.save();
            res.redirect('/users');
        } catch (error) {
            res.status(500).send('Ошибка при обновлении пользователя');
        }
    }
    
    // Удалить пользователя
    async destroy(req, res) {
        try {
            const user = await User.findById(req.params.id);
            if (!user) {
                return res.status(404).send('Пользователь не найден');
            }
            
            // Логика удаления
            console.log('Удаление пользователя: ' + user.name);
            res.redirect('/users');
        } catch (error) {
            res.status(500).send('Ошибка при удалении пользователя');
        }
    }
}

module.exports = UserController;

Маршруты

// routes/userRoutes.js
const express = require('express');
const UserController = require('../controllers/UserController');

const router = express.Router();
const userController = new UserController();

// RESTful маршруты
router.get('/', userController.index.bind(userController));           // GET /users
router.get('/new', userController.new.bind(userController));           // GET /users/new
router.get('/:id', userController.show.bind(userController));          // GET /users/:id
router.post('/', userController.create.bind(userController));         // POST /users
router.get('/:id/edit', userController.edit.bind(userController));    // GET /users/:id/edit
router.put('/:id', userController.update.bind(userController));       // PUT /users/:id
router.delete('/:id', userController.destroy.bind(userController));   // DELETE /users/:id

module.exports = router;

MVC в Laravel (PHP)

Model

<?php
// app/Models/User.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $fillable = ['name', 'email', 'password'];
    
    protected $hidden = ['password'];
    
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
    
    // Валидация
    public static function rules()
    {
        return [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6',
        ];
    }
    
    // Отношения
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

View

<!-- resources/views/users/index.blade.php -->
@extends('layouts.app')

@section('content')
<div class="container">
    <h1>Пользователи</h1>
    
    <div class="row">
        @foreach($users as $user)
        <div class="col-md-4 mb-3">
            <div class="card">
                <div class="card-body">
                    <h5 class="card-title">{{ $user->name }}</h5>
                    <p class="card-text">{{ $user->email }}</p>
                    <a href="{{ route('users.show', $user) }}" class="btn btn-primary">Просмотр</a>
                    <a href="{{ route('users.edit', $user) }}" class="btn btn-secondary">Редактировать</a>
                </div>
            </div>
        </div>
        @endforeach
    </div>
    
    <a href="{{ route('users.create') }}" class="btn btn-success">Добавить пользователя</a>
</div>
@endsection

Controller

<?php
// app/Http/Controllers/UserController.php
namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function index()
    {
        $users = User::paginate(10);
        return view('users.index', compact('users'));
    }
    
    public function show(User $user)
    {
        return view('users.show', compact('user'));
    }
    
    public function create()
    {
        return view('users.create');
    }
    
    public function store(Request $request)
    {
        $validated = $request->validate(User::rules());
        
        $user = User::create($validated);
        
        return redirect()->route('users.show', $user)
                        ->with('success', 'Пользователь создан успешно');
    }
    
    public function edit(User $user)
    {
        return view('users.edit', compact('user'));
    }
    
    public function update(Request $request, User $user)
    {
        $rules = User::rules();
        $rules['email'] = 'required|string|email|max:255|unique:users,email,' . $user->id;
        
        $validated = $request->validate($rules);
        
        $user->update($validated);
        
        return redirect()->route('users.show', $user)
                        ->with('success', 'Пользователь обновлен успешно');
    }
    
    public function destroy(User $user)
    {
        $user->delete();
        
        return redirect()->route('users.index')
                        ->with('success', 'Пользователь удален успешно');
    }
}

Преимущества MVC

Плюсы:

  • Разделение ответственности — четкое разделение логики
  • Переиспользование — компоненты можно использовать повторно
  • Тестируемость — каждый компонент можно тестировать отдельно
  • Масштабируемость — легко добавлять новые функции
  • Поддерживаемость — изменения в одном компоненте не влияют на другие

Минусы:

  • Сложность — может быть избыточным для простых приложений
  • Производительность — дополнительные слои абстракции
  • Кривая обучения — требует понимания паттерна
  • Связанность — компоненты все еще связаны между собой

Когда использовать MVC

Подходит для:

  • Веб-приложения — традиционные веб-сайты
  • CRUD приложения — системы управления данными
  • Корпоративные системы — большие бизнес-приложения
  • API серверы — RESTful веб-сервисы

Не подходит для:

  • Простые статические сайты — избыточная сложность
  • Игры — другие паттерны более подходящие
  • Системы реального времени — нужны другие подходы

Современные альтернативы

MVVM

  • Двусторонняя привязка данных
  • Лучше для SPA приложений
  • Примеры: Angular, Vue.js

MVP

  • Более тестируемый
  • Четкое разделение логики
  • Примеры: Android разработка

Микросервисы

  • Распределенная архитектура
  • Независимое развертывание
  • Масштабируемость

MVC остается одним из самых популярных и проверенных архитектурных паттернов в веб-разработке!

Copyright © 2026