<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 20:13:38 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="FLNFcoTJCNNHIlwfP6DEPD7lbjTlo3OiMc4yUABW8Qv7Yo5Fdrels_FheIczrzRL_uxDnu2UjQCMLqgEUlEWZQ";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Уровневое проектирование | PHP: Абстракция с помощью данных</title>
<meta name="description" content="Уровневое проектирование / PHP: Абстракция с помощью данных: Учимся видеть барьеры абстракции и выделять слои">
<link rel="canonical" href="https://ru.hexlet.io/courses/php-data-abstraction/lessons/levels/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Уровневое проектирование">
<meta property="og:title" content="PHP: Абстракция с помощью данных">
<meta property="og:description" content="Уровневое проектирование / PHP: Абстракция с помощью данных: Учимся видеть барьеры абстракции и выделять слои">
<meta property="og:url" content="https://ru.hexlet.io/courses/php-data-abstraction/lessons/levels/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="0otz5Q58z37jJb3R7k3j5oGeiNEHzR4-VAxqxBhDJ409WrjS_AJiHlVmmUniQhORQZelew_64Jzp7PCQSkTA4w" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk5MiwicHVyIjoiYmxvYl9pZCJ9fQ==--e9d0f30948ea766a7e6bc3e3d56c192344d45fb8/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programming-cuate%20(1).png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc1MSwicHVyIjoiYmxvYl9pZCJ9fQ==--e5793a1818ff43d73135cc7ed88c1998d7650470/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Developer%20activity-bro.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T20:13:38.734Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"l3oMAAvqYrA0JkXRRDPU_-ILBl52eMTci47Dwge5bPV4q8c3-ZTP0IJlYUlIPCSIIgIr9H5POn42blmWVb6Lmw","topics":[{"id":43137,"title":"Здравствуйте! Мое решение https://ru.hexlet.io/code_reviews/270077.\nВ общем, я по своему понял условие задачи в рамках создания прямоугольника. В итоге прошел все проверки, но решение совершенно другое. Уважаемый ментор, если не затруднит разобраться в моем монстре, прокомментируйте решение, пожалуйста.\nPS. function containsOrigin мог решить через array_reduce, каюсь.","plain_title":"Здравствуйте! Мое решение https://ru.hexlet.io/code_reviews/270077. В общем, я по своему понял условие задачи в рамках создания прямоугольника. В итоге прошел все проверки, но решение совершенно другое. Уважаемый ментор, если не затруднит разобраться в моем монстре, прокомментируйте решение, пожалуйста. ","creator":{"public_name":"Илья Идущий","id":283545,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":93845,"body":"Приветствую, Илья!\n\nВ данной реализации выполняется много лишних действий. Обратите внимание, что изначально при создании прямоугольника в функцию передаются готовые данные: точка, длина, высота. А далее в функциях getStartPoint, getWidth, getHeight вам приходится заново вычислять эти данные. Кроме того, функция getStartPoint создаёт точку основываясь на том, что это массив в котором первый элемент координата x, а второй - координата y. Это и есть нарушение уровня абстракции Если реализация точки изменится, данная функция перестанет работать. Также обратите внимание, что для реализации функции containsOrigin нет необходимости проверять все 4 точки, а достаточно проверить только 2. Поэтому тут также можно было поступить проще. Порекомендую вам изучить и разобраться как работает решение учителя.","topic_id":43137},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":93943,"body":"Приветствую, Илья!\n\n> Соответственно, исходя из моего решения логично было применение именно моей реализации функции getStartPoint.\n\nЕсли опираться на вашу структуру прямоугольника, то в методе getStartPoint достаточно просто вернуть $rectangle[0], а не заново создавать точку.\n\n> За исключением того, что я использовал индексированный массив, а не ассоциативный. Вы это имели ввиду, когда указали \"Это и есть нарушение уровня абстракции\"?\n\nНет. Тут изначально проблема в том, что вы используете массив. А если структура точки в будущем изменится, и будет возвращаться вообще не массив, тогда весь код сломается. Поэтому с абстракциями нужно работать используя интерфейсные методы, которые они предоставляют. В данном случае, если необходимо создать точку, нужно использовать функцию конструктор makeDecartPoint (по аналогии как вы поступили в методе getNextPoint). Таким образом, какие изменения бы не произошли во внутренней структуре \"точки\", код который её использует, будет и далее работать корректно.","topic_id":43137},{"creator":{"public_name":"Илья Идущий","id":283545,"is_tutor":false},"id":93979,"body":"**Станислав**, благодарю!","topic_id":43137},{"creator":{"public_name":"Илья Идущий","id":283545,"is_tutor":false},"id":93901,"body":"**Станислав Дзисяк**, да, я понимаю, что оптимальней использовать только три параметра, а не создавать массив из четырех координат. Соответственно, исходя из моего решения логично было применение именно моей реализации функции getStartPoint. За исключением того, что я использовал индексированный массив, а не ассоциативный. Вы это имели ввиду, когда указали \"Это и есть нарушение уровня абстракции\"? Ну а по поводу containsOrigin - в конечном решении это был вроде третий вариант))). Жаль, что не додумался до диагональной проверки(. В решении учителя разобрался сразу, но так как у меня изначально получился несколько иной подход, поэтому и попросил разбор полета).","topic_id":43137}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Уровневое проектирование","entity_url":null,"active":true}},{"id":47993,"title":"Думаю имеет смысл написать, что квадранты нумеруются с плоскости х > 0 и y > 0 против часовой стрелки. Так как нельзя посмотреть что именно выдает getQuadrant, сделал без нее. \nhttps://ru.hexlet.io/code_reviews/322743\n\nИ получается решение учителя опровергает задание (проверка только на 2 квадранта). \n<Чтобы в этом убедиться, достаточно проверить, что все вершины прямоугольника лежат в разных квадрантах (их можно высчитать в момент проверки).>","plain_title":"Думаю имеет смысл написать, что квадранты нумеруются с плоскости х > 0 и y > 0 против часовой стрелки. Так как нельзя посмотреть что именно выдает getQuadrant, сделал без нее. https://ru.hexlet.io/code_reviews/322743 ","creator":{"public_name":"Алексей","id":299779,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":103136,"body":"Это простая оптимизация. Решение учителя в том числе позволяет под другим углом взглянуть на свою реализацию и улучшить её.","topic_id":47993},{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":103119,"body":"Приветствую, Алексей! Здорово, что вы справились с задачей.\n\n> Так как нельзя посмотреть что именно выдает getQuadrant\n\nПочему же? Определение этой функции находится в файле _src/Points.php_, исходя из него можно понять как нумеруются квадранты.\n\n> И получается решение учителя опровергает задание (проверка только на 2 квадранта).\n\nЭтой проверки достаточно. Ведь если диагональные вершины прямоугольника лежат в квадрантах 2 и 4 соответственно, то другие две вершины будут находиться в квадрантах 1 и 3.","topic_id":47993},{"creator":{"public_name":"Алексей","id":299779,"is_tutor":false},"id":103134,"body":"Просто в задании написано, что все вершины должны находится в разных квадратах. А по факту проверяем только две)","topic_id":47993}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Уровневое проектирование","entity_url":null,"active":true}},{"id":24801,"title":" Здравствуйте! Извините что лезу со своими пятью копейками. Я могу ошибаться, но мне кажется что тесты не полны. Если мы рассматриваем декартову систему координат на плоскости, то ничего не мешает нам задавать в качестве ширины и высоты отрицательные значения, как это делается во всех графических редакторах. В задании условия об обязательности задавать положительные значения я вроде бы не нашел. А если задавать отрицательные - предложенное решение не работает.\nТогда придется делать нечто вроде этого \n\nhttps://ru.hexlet.io/code_reviews/93202\n\nОно жутко кривое, но как сделать лучше я пока не придумал.\nК существующим тестам тогда добавится что-то наподобие\n```\n $p3 = makeDecartPoint(4, 3);\n $rectangle7 = makeRectangle($p3, -5, 6);\n $this->assertTrue(isContainsTheOrigin($rectangle7));\n\n $rectangle8 = makeRectangle($p3, 5, 6);\n $this->assertFalse(isContainsTheOrigin($rectangle8));\n\n $rectangle9 = makeRectangle($p3, 5, -6);\n $this->assertFalse(isContainsTheOrigin($rectangle9));\n\n $rectangle10 = makeRectangle($p3, -4, 6);\n $this->assertFalse(isContainsTheOrigin($rectangle10));\n\n $rectangle11 = makeRectangle($p3, 2, 4);\n $this->assertFalse(isContainsTheOrigin($rectangle11));\n\n $p4 = makeDecartPoint(-5, -4);\n $rectangle12 = makeRectangle($p4, 6, -6);\n $this->assertTrue(isContainsTheOrigin($rectangle12));\n\n $rectangle13 = makeRectangle($p4, 2, -3);\n $this->assertFalse(isContainsTheOrigin($rectangle13));\n\n $rectangle14 = makeRectangle($p4, 6, -4);\n $this->assertFalse(isContainsTheOrigin($rectangle14));\n\n $rectangle15 = makeRectangle($p4, -5, 7);\n $this->assertFalse(isContainsTheOrigin($rectangle15));\n\n $rectangle16 = makeRectangle($p4, 7, 7);\n $this->assertFalse(isContainsTheOrigin($rectangle16));\n\n $p5 = makeDecartPoint(-3, 2);\n $rectangle17 = makeRectangle($p5, 4, 3);\n $this->assertTrue(isContainsTheOrigin($rectangle17));\n\n $rectangle18 = makeRectangle($p5, 4, 3);\n $this->assertTrue(isContainsTheOrigin($rectangle18));\n \n $rectangle19 = makeRectangle($p5, -4, 3);\n $this->assertFalse(isContainsTheOrigin($rectangle19));\n\n $rectangle20 = makeRectangle($p5, 4, 1);\n $this->assertFalse(isContainsTheOrigin($rectangle20));\n\n $rectangle21 = makeRectangle($p5, -10, -10);\n $this->assertFalse(isContainsTheOrigin($rectangle21));\n\n $p6 = makeDecartPoint(7, -3);\n $rectangle22 = makeRectangle($p6, 8, 4);\n $this->assertFalse(isContainsTheOrigin($rectangle22));\n\n $rectangle23 = makeRectangle($p6, -9, -5);\n $this->assertTrue(isContainsTheOrigin($rectangle23));\n\n $rectangle24 = makeRectangle($p6, -9, 4);\n $this->assertFalse(isContainsTheOrigin($rectangle24));\n\n $rectangle25 = makeRectangle($p6, 8, -6);\n $this->assertFalse(isContainsTheOrigin($rectangle25));\n\n $rectangle26 = makeRectangle($p6, -13, -3);\n $this->assertFalse(isContainsTheOrigin($rectangle26));\n```\n\nИ еще линтер хочет странного\n```\nFILE: /usr/src/app/src/Points.php\n----------------------------------------------------------------------\nFOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE\n----------------------------------------------------------------------\n 1 | WARNING | A file should declare new symbols (classes, functions,\n | | constants, etc.) and cause no other side effects, or\n | | it should execute logic with side effects, but should\n | | not do both. The first symbol is defined on line 5 and\n | | the first side effect is on line 39.\n | | (PSR1.Files.SideEffects.FoundWithSymbols)\n----------------------------------------------------------------------\n\nTime: 309ms; Memory: 6Mb\n\n```\nНесколькими минутами ранее докапывался будто бы перед закрывающей скобкой функции ('}') есть пустая строка, но ее не было. После нескольких запусков проверки ошибка с пустой строкой исчезла сама по себе.\n\nВпрочем, если заморочиться на общем решении, это может переусложнить задачу на пустом месте и нарушить ход учебного процесса с педагогической точки зрения. Так что, наверное, проще все же добавить в задание условие, \"ширина и высота - неотрицательные числа\".","plain_title":" Здравствуйте! Извините что лезу со своими пятью копейками. Я могу ошибаться, но мне кажется что тесты не полны. Если мы рассматриваем декартову систему координат на плоскости, то ничего не мешает нам задавать в качестве ширины и высоты отрицательные значения, как это делается во всех графических редакторах. В задании условия об обязательности задавать положительные значения я вроде бы не нашел. А если задавать отрицательные - предложенное решение не работает. Тогда придется делать нечто вроде этого https://ru.hexlet.io/code_reviews/93202 Оно жутко кривое, но как сделать лучше я пока не придумал. К существующим тестам тогда добавится что-то наподобие ``` $p3 = makeDecartPoint(4, 3); $rectangle7 = makeRectangle($p3, -5, 6); $this->assertTrue(isContainsTheOrigin($rectangle7)); $rectangle8 = makeRectangle($p3, 5, 6); $this->assertFalse(isContainsTheOrigin($rectangle8)); $rectangle9 = makeRectangle($p3, 5, -6); $this->assertFalse(isContainsTheOrigin($rectangle9)); $rectangle10 = makeRectangle($p3, -4, 6); $this->assertFalse(isContainsTheOrigin($rectangle10)); $rectangle11 = makeRectangle($p3, 2, 4); $this->assertFalse(isContainsTheOrigin($rectangle11)); $p4 = makeDecartPoint(-5, -4); $rectangle12 = makeRectangle($p4, 6, -6); $this->assertTrue(isContainsTheOrigin($rectangle12)); $rectangle13 = makeRectangle($p4, 2, -3); $this->assertFalse(isContainsTheOrigin($rectangle13)); $rectangle14 = makeRectangle($p4, 6, -4); $this->assertFalse(isContainsTheOrigin($rectangle14)); $rectangle15 = makeRectangle($p4, -5, 7); $this->assertFalse(isContainsTheOrigin($rectangle15)); $rectangle16 = makeRectangle($p4, 7, 7); $this->assertFalse(isContainsTheOrigin($rectangle16)); $p5 = makeDecartPoint(-3, 2); $rectangle17 = makeRectangle($p5, 4, 3); $this->assertTrue(isContainsTheOrigin($rectangle17)); $rectangle18 = makeRectangle($p5, 4, 3); $this->assertTrue(isContainsTheOrigin($rectangle18)); $rectangle19 = makeRectangle($p5, -4, 3); $this->assertFalse(isContainsTheOrigin($rectangle19)); $rectangle20 = makeRectangle($p5, 4, 1); $this->assertFalse(isContainsTheOrigin($rectangle20)); $rectangle21 = makeRectangle($p5, -10, -10); $this->assertFalse(isContainsTheOrigin($rectangle21)); $p6 = makeDecartPoint(7, -3); $rectangle22 = makeRectangle($p6, 8, 4); $this->assertFalse(isContainsTheOrigin($rectangle22)); $rectangle23 = makeRectangle($p6, -9, -5); $this->assertTrue(isContainsTheOrigin($rectangle23)); $rectangle24 = makeRectangle($p6, -9, 4); $this->assertFalse(isContainsTheOrigin($rectangle24)); $rectangle25 = makeRectangle($p6, 8, -6); $this->assertFalse(isContainsTheOrigin($rectangle25)); $rectangle26 = makeRectangle($p6, -13, -3); $this->assertFalse(isContainsTheOrigin($rectangle26)); И еще линтер хочет странного FILE: /usr/src/app/src/Points.php FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE 1 | WARNING | A file should declare new symbols (classes, functions, | | constants, etc.) and cause no other side effects, or | | it should execute logic with side effects, but should | | not do both. The first symbol is defined on line 5 and | | the first side effect is on line 39. | | (PSR1.Files.SideEffects.FoundWithSymbols) Time: 309ms; Memory: 6Mb Несколькими минутами ранее докапывался будто бы перед закрывающей скобкой функции ('}') есть пустая строка, но ее не было. После нескольких запусков проверки ошибка с пустой строкой исчезла сама по себе. Впрочем, если заморочиться на общем решении, это может переусложнить задачу на пустом месте и нарушить ход учебного процесса с педагогической точки зрения. Так что, наверное, проще все же добавить в задание условие, \"ширина и высота - неотрицательные числа\". ","creator":{"public_name":"Maxim Korobov","id":177966,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":53006,"body":"> Впрочем, если заморочиться на общем решении, это может переусложнить задачу на пустом месте и нарушить ход учебного процесса с педагогической точки зрения. \n\nВот да), тут важнее баланс с точки зрения целей обучения. А так спасибо за раскладку, это очень ценное добавление.","topic_id":24801}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Уровневое проектирование","entity_url":null,"active":true}},{"id":43115,"title":"Я, конечно, здорово накрутил лишнего и ненужного, но тем не менее в процессе появился вопрос.\n\nДопустим, у меня есть массив четырёх точек (да, я уже понял, что это излишне).\n\n`$points = [$point1, $point2, $point3, $point4];` \nИ я пробую сделать на этот массив map, чтобы появился массив с номерами квадрантов.\n\nЕсли я делаю `$quads = array_map(getQuadrant, $points);`, то так не работает, несмотря на то, что использование функции заявлено в начале файла.\n\nА если я сделаю \"не по-функциональному\", а тупо циклом: \n`foreach ($points as $point) {\n $quads[] = getQuadrant($point);\n }` \nто всё работает.\n\nВ чём разница, почему не работает map и работает цикл? И как использовать функцию из неймспейса через map?\n","plain_title":"Я, конечно, здорово накрутил лишнего и ненужного, но тем не менее в процессе появился вопрос. Допустим, у меня есть массив четырёх точек (да, я уже понял, что это излишне). $points = [$point1, $point2, $point3, $point4]; И я пробую сделать на этот массив map, чтобы появился массив с номерами квадрантов. Если я делаю $quads = array_map(getQuadrant, $points);, то так не работает, несмотря на то, что использование функции заявлено в начале файла. А если я сделаю \"не по-функциональному\", а тупо циклом: foreach ($points as $point) { $quads[] = getQuadrant($point); } то всё работает. В чём разница, почему не работает map и работает цикл? И как использовать функцию из неймспейса через map? ","creator":{"public_name":"Denis Skvortsov","id":185650,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":93830,"body":"Приветствую, Денис!\n\nТут можно поступить как рекомендует Николай, или можно воспользоваться, например стрелочной функцией `$quads = array_map(fn($point) => getQuadrant($point), $points);`","topic_id":43115},{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":93749,"body":"Привет, если не ошибаюсь, использование именованной функции требует прокидывание полного неймспейса до функции.\nТ.е. надо `array_map('foo/bar/baz', $arr)`","topic_id":43115}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Уровневое проектирование","entity_url":null,"active":true}},{"id":32393,"title":" <?php\n\n function ($rational1, $rational2)\n {\n\nТак и должно быть, без имени ?","plain_title":"<?php function ($rational1, $rational2) { Так и должно быть, без имени ? ","creator":{"public_name":"Zsuravljov Georgij","id":183433,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":70382,"body":"Добавил имя функции. Спасибо)","topic_id":32393}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Уровневое проектирование","entity_url":null,"active":true}},{"id":16128,"title":"В Ридми упражнения есть пара ошибок в примере вызова:\n\n```php\n$p = makeDecartPoint(0, 1);\n$rectangle = makeRectangle(p, 4, 5); // В переменной нужен доллар: `$p`\n\nisContainsTheOrigin($rectangle); // false\n\n$rectangle2 = makeRectangle(makeDecartPoint(-4, 3), 5, 4);\ncontainsTheOrigin($rectangle2); // true\n// ↑ Имя функции должно быть `isContainsTheOrigin`\n```\n","plain_title":"В Ридми упражнения есть пара ошибок в примере вызова: $p = makeDecartPoint(0, 1); $rectangle = makeRectangle(p, 4, 5); // В переменной нужен доллар: `$p` isContainsTheOrigin($rectangle); // false $rectangle2 = makeRectangle(makeDecartPoint(-4, 3), 5, 4); containsTheOrigin($rectangle2); // true // ↑ Имя функции должно быть `isContainsTheOrigin` ","creator":{"public_name":"Андрей Миськов","id":103643,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":34172,"body":"Поправил, спасибо!","topic_id":16128},{"creator":{"public_name":"Антон Шведов","id":284871,"is_tutor":false},"id":98105,"body":"нет, как была containsOrigin так и осталась. is нету...","topic_id":16128}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Уровневое проектирование","entity_url":null,"active":true}},{"id":44409,"title":"Добрый день. Подскажите пожалуйста: \n1) Являются ли в моем решении избыточными дополнительные переменные ($leftUpQuadrant, $rightDownQuadrant) в функции \"containsOrigin\"\n2) Какое условие проверки отработает быстрее - две операции сравнения (как у учителя) , или одна операция вычитания (как у меня) ?\nhttps://ru.hexlet.io/code_reviews/281969","plain_title":"Добрый день. Подскажите пожалуйста: 1) Являются ли в моем решении избыточными дополнительные переменные ($leftUpQuadrant, $rightDownQuadrant) в функции \"containsOrigin\" 2) Какое условие проверки отработает быстрее - две операции сравнения (как у учителя) , или одна операция вычитания (как у меня) ? https://ru.hexlet.io/code_reviews/281969 ","creator":{"public_name":"Артем","id":168956,"is_tutor":false},"comments":[{"creator":{"public_name":"Артем","id":168956,"is_tutor":false},"id":96085,"body":"Спасибо!","topic_id":44409},{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":96084,"body":"Думаю, все это приходит с опытом. Нужно больше решать задач, анализировать решение учителя, рафакторить свой код. Со временем придет понимание, где эта грань. Вы правы, в данном примере все понятно и без промежуточной переменной\n>getQuadrant($point1)","topic_id":44409},{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":96037,"body":"Добрый день. На мой взгляд, не очевидна связь между условием:\n>все вершины прямоугольника лежат в разных квадрантах\n\nи вашей проверкой с вычитанием. Вы проверяете уже следствие того, что одна точка лежит во втором квадранте, а другая в четвертом. Это заставляет задуматься, что же вы все-таки проверяете, почему важна именно разность квадрантов. В решении учителя же, напротив, все очевидно. Разница в производительности, если и есть, то ничтожная и о ней вообще нет смысла рассуждать в данной ситуации. На эту тему есть [гайд по оптимизации](http://optimization.guide)\n\nПромежуточные переменные можно не создавать, они же используются только один раз","topic_id":44409},{"creator":{"public_name":"Артем","id":168956,"is_tutor":false},"id":96080,"body":"Максим, спасибо, все понятно. Но где же все таки та тонкая грань, когда для читаемости кода (ведь код, который заставляет думать это плохой код) мы вводим промежуточные переменные с наименованием описывающим суть, и когда мы от них отказываемся ?\nВ данном примере считается, что и так все понятно по имени вызываемой функции ?","topic_id":44409}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Уровневое проектирование","entity_url":null,"active":true}},{"id":43783,"title":"\n\nПривет! Поясните, пожалуйста, что называется \"базовым уровнем\"? По каким причинам построение круга по радиусу отнесли к \"базовому уровню\"?","plain_title":"enter image description here http://prntscr.com/sxb5cg Привет! Поясните, пожалуйста, что называется \"базовым уровнем\"? По каким причинма построение круга по радиусу отнесли к \"базовому уровню\"? ","creator":{"public_name":"Robert Kuznetsov","id":138381,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":95216,"body":"Роберт, приветствую!\n\n> Поясните, пожалуйста, что называется \"базовым уровнем\"? По каким причинам построение круга по радиусу отнесли к \"базовому уровню\"?\n\nБазовый уровень это минимально необходимый набор функций для работы с конкретной абстракцией. Что бы построить круг достаточно знать его радиус, и соответственно имея круг мы должны возможность получить исходные данные которые были использованы для построения, то есть функцию получения радиуса.","topic_id":43783},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":95274,"body":"Роберт, приветствую!\n\n> Можете пояснить, по каким причинам операции вычисления (площади, длины окружности) не относятся к базовыму уровню для работы с этой абстракцией?\n\nТут есть ключевой момент, что это минимально необходимый набор функций. Для того чтобы вычислить площадь круга или длину окружности, нужно знать радиус, и для этого используется селектор с помощью которого мы получаем радиус.\n\n> Т.е. в этом случае, к базовому уровню можно отнести вычислительные операции и извлечение радиуса, правильно понимаю?\n\nВот об этом как раз и идёт речь в уроке, а именно об уровнях. Есть уровень представления абстракции круг - это конструктор и селектор. Далее уже идёт уровень на котором вычисляются площадь и длина окружности, который опирается на уровень с конструктором и селектором. Все эти функции принадлежат одной абстракции, но находятся на разных её уровнях.","topic_id":43783},{"creator":{"public_name":"Robert Kuznetsov","id":138381,"is_tutor":false},"id":95273,"body":"Хорошо, спасибо за пояснения :)\n\nМожете пояснить, по каким причинам операции вычисления (площади, длины окружности) не относятся к базовыму уровню для работы с этой абстракцией?\n\nДопустим ко мне пришел заказчик и у него основной запрос в том, чтобы с кругом произвести вычислительные операции, но ему не интересна визуализация и построение круга. Т.е. в этом случае, к базовому уровню можно отнести вычислительные операции и извлечение радиуса, правильно понимаю? ","topic_id":43783},{"creator":{"public_name":"Robert Kuznetsov","id":138381,"is_tutor":false},"id":95303,"body":"Спасибо за разъяснения)))","topic_id":43783}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Уровневое проектирование","entity_url":null,"active":true}},{"id":19999,"title":"Вот [это решение](https://ru.hexlet.io/code_reviews/68123) проходит тесты, а не должно. Надо добавить случай, например, когда весь прямоугольник лежит во втором квадранте. ","plain_title":"Вот это решение (https://ru.hexlet.io/code_reviews/68123) проходит тесты, а не должно. Надо добавить случай, например, когда весь прямоугольник лежит во втором квадранте. ","creator":{"public_name":"Юрий Чернышев","id":182794,"is_tutor":false},"comments":[{"creator":{"public_name":"Юрий Чернышев","id":182794,"is_tutor":false},"id":42509,"body":"Думаю надо так:\n```\n\n $rectangle3 = makeRectangle(makeDecartPoint(-4, 3), 2, 2);\n $this->assertFalse(isContainsTheOrigin($rectangle3));\n\n $rectangle4 = makeRectangle(makeDecartPoint(-4, 3), 5, 2);\n $this->assertFalse(isContainsTheOrigin($rectangle4));\n\n $rectangle5 = makeRectangle(makeDecartPoint(-4, 3), 2, 4);\n $this->assertFalse(isContainsTheOrigin($rectangle5)); \n \n $rectangle6 = makeRectangle(makeDecartPoint(-4, 3), 4, 3);\n $this->assertFalse(isContainsTheOrigin($rectangle6)); \n```","topic_id":19999},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":42283,"body":"Приветствую!\n\n> Надо добавить случай, например, когда весь прямоугольник лежит во втором квадранте.\n\nНапишешь сюда код нужного теста? Я обновлю им тесты практики","topic_id":19999},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":43742,"body":"Да, спасибо, Юрий, обновил тесты практики, добавив твои кейсы!","topic_id":19999}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Уровневое проектирование","entity_url":null,"active":true}},{"id":18280,"title":"`// removed`\n\nВот это часть кода учителя мне не понятна. Объясните пожалуйста.","plain_title":"// removed Вот это часть кода учителя мне не понятна. Объясните пожалуйста. ","creator":{"public_name":"Сергей Демиденко","id":164961,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей Демиденко","id":164961,"is_tutor":false},"id":38553,"body":"Разве прямоугльник не может быть в одной квадранте ? Это же в полне реально.","topic_id":18280},{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":38563,"body":"Может быть. Но здесь ты проверяешь его две точки. Верхнюю левую и нижнюю правую.\nПопробуй их нарисовать на бумаге соответственно квадрантам и дорисуй две оставшиеся, чтобы сделать прямоугольник.","topic_id":18280},{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":38548,"body":"Чуть ниже мне объяснил Кирилл\n```Центр координат всегда попадает в прямоугольник, стороны которого паралелльны осям и его левая верхняя точка во втором квадранте, а правая нижняя в четвертом.```\n","topic_id":18280}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Уровневое проектирование","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":723,"slug":"php_data_abstraction_levels_exercise","name":null,"state":"active","kind":"exercise","language":"php","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"## src/Rectangle.php\n\nРеализуйте набор функций для работы с прямоугольниками, стороны которого всегда параллельны осям. Прямоугольник может располагаться в любом месте координатной плоскости.\n\nПри такой постановке, достаточно знать только три параметра для однозначного задания прямоугольника на плоскости: координаты левой верхней точки, ширину и высоту. Зная их, мы всегда можем построить прямоугольник одним единственным способом.\n\n |\n 4 | точка ширина\n | *-------------\n 3 | | |\n | | | высота\n 2 | | |\n | --------------\n 1 |\n |\n ------|---------------------------\n 0 | 1 2 3 4 5 6 7\n |\n |\n |\n\nОсновной интерфейс:\n\n* `makeRectangle()` (конструктор) – создает прямоугольник. Принимает параметры: левую верхнюю точку, ширину и высоту. Ширина и высота – положительные числа.\n* Селекторы `getStartPoint()`, `getWidth()` и `getHeight()`\n\n* `containsOrigin()` – проверяет, принадлежит ли центр координат прямоугольнику (не лежит на границе прямоугольника, а находится внутри). Чтобы в этом убедиться, достаточно проверить, что все вершины прямоугольника лежат в разных квадрантах (их можно высчитать в момент проверки).\n\n```php\n<?php\n\n// Создание прямоугольника:\n// p - левая верхняя точка\n// 4 - ширина\n// 5 - высота\n//\n// p 4\n// -----------\n// | |\n// | | 5\n// | |\n// -----------\n\n$p = makeDecartPoint(0, 1);\n$rectangle = makeRectangle($p, 4, 5);\n\ncontainsOrigin($rectangle); // false\n\n$rectangle2 = makeRectangle(makeDecartPoint(-4, 3), 5, 4);\ncontainsOrigin($rectangle2); // true\n```\n\n### Подсказки\n* Квадрант плоскости — любая из 4 областей (углов), на которые плоскость делится двумя взаимно перпендикулярными прямыми, принятыми в качестве осей координат.\n","prepared_readme":"## src/Rectangle.php\n\nРеализуйте набор функций для работы с прямоугольниками, стороны которого всегда параллельны осям. Прямоугольник может располагаться в любом месте координатной плоскости.\n\nПри такой постановке, достаточно знать только три параметра для однозначного задания прямоугольника на плоскости: координаты левой верхней точки, ширину и высоту. Зная их, мы всегда можем построить прямоугольник одним единственным способом.\n\n |\n 4 | точка ширина\n | *-------------\n 3 | | |\n | | | высота\n 2 | | |\n | --------------\n 1 |\n |\n ------|---------------------------\n 0 | 1 2 3 4 5 6 7\n |\n |\n |\n\nОсновной интерфейс:\n\n* `makeRectangle()` (конструктор) – создает прямоугольник. Принимает параметры: левую верхнюю точку, ширину и высоту. Ширина и высота – положительные числа.\n* Селекторы `getStartPoint()`, `getWidth()` и `getHeight()`\n\n* `containsOrigin()` – проверяет, принадлежит ли центр координат прямоугольнику (не лежит на границе прямоугольника, а находится внутри). Чтобы в этом убедиться, достаточно проверить, что все вершины прямоугольника лежат в разных квадрантах (их можно высчитать в момент проверки).\n\n```php\n<?php\n\n// Создание прямоугольника:\n// p - левая верхняя точка\n// 4 - ширина\n// 5 - высота\n//\n// p 4\n// -----------\n// | |\n// | | 5\n// | |\n// -----------\n\n$p = makeDecartPoint(0, 1);\n$rectangle = makeRectangle($p, 4, 5);\n\ncontainsOrigin($rectangle); // false\n\n$rectangle2 = makeRectangle(makeDecartPoint(-4, 3), 5, 4);\ncontainsOrigin($rectangle2); // true\n```\n\n### Подсказки\n* Квадрант плоскости — любая из 4 областей (углов), на которые плоскость делится двумя взаимно перпендикулярными прямыми, принятыми в качестве осей координат.\n","has_solution":true,"entity_name":"Уровневое проектирование"},"units":[{"id":2883,"name":"theory","url":"/courses/php-data-abstraction/lessons/levels/theory_unit"},{"id":2884,"name":"quiz","url":"/courses/php-data-abstraction/lessons/levels/quiz_unit"},{"id":2891,"name":"exercise","url":"/courses/php-data-abstraction/lessons/levels/exercise_unit"}],"links":[],"ordered_units":[{"id":2883,"name":"theory","url":"/courses/php-data-abstraction/lessons/levels/theory_unit"},{"id":2884,"name":"quiz","url":"/courses/php-data-abstraction/lessons/levels/quiz_unit"},{"id":2891,"name":"exercise","url":"/courses/php-data-abstraction/lessons/levels/exercise_unit"}],"id":1353,"slug":"levels","state":"approved","name":"Уровневое проектирование","course_order":450,"goal":"Учимся видеть барьеры абстракции и выделять слои","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Рассмотрим ещё одну простую систему — рациональные числа и операции над ними. Напомню, что рациональным называют число, которое может быть представлено в виде дроби `a/b`, где `a` — это числитель дроби, `b` — знаменатель дроби. Причём `b` не должно быть нулём, поскольку деление на ноль не допускается.\n\nРациональные числа в PHP не поддерживаются, поэтому абстракцию для них создадим самостоятельно. Как обычно, нам понадобятся конструктор и селекторы:\n\n```php\n<?php\n\n$num = makeRational(1, 2); // создали рациональное число \"одна вторая\"\n$numer = getNumer($num); // 1\n$denom = getDenom($num); // 2\n```\n\nС помощью трёх функций мы определили рациональное число. Одна функция (конструктор) собирает его из частей, другие (селекторы) позволяют каждую часть извлечь. Чем при этом, с точки зрения языка, является `$num` — абсолютно не важно. Хоть функцией (и такое возможно), хоть массивом (индексированным или ассоциативным). Во внутренней реализации можно даже использовать строки:\n\n```php\n<?php\n\nfunction makeRational($numer, $denom)\n{\n return \"{$numer}/{$denom}\";\n}\n\nfunction getNumer($rational)\n{\n return explode('/', $rational)[0];\n}\n\nfunction getDenom($rational)\n{\n return explode('/', $rational)[1];\n}\n\nprint_r(makeRational(10, 3)); // => 10/3\n```\n\nНесмотря на то, что мы научились представлять рациональные числа, эта абстракция сама по себе малоприменима. Абстракция становится полезна тогда, когда появляется возможность оперировать ей. Для рациональных чисел базовыми операциями можно считать арифметические, например, сложение, вычитание или умножение. Умножение рациональных чисел — самая простая операция. Для её выполнения нужно перемножить числители и знаменатели:\n\n```\n3/4 * 4/5 = (3 * 4)/(4 * 5) = 12/20\n```\n\nСамое интересное начинается в процессе реализации. Если предположить, что реальная структура рационального числа выглядит так: `['numer' => 2, 'denom' => 3]`, то, чисто технически, решение может быть таким:\n\n```php\n<?php\n\nfunction mul($rational1, $rational2)\n{\n return [\n 'numer' => $rational1['numer'] * $rational2['numer'],\n 'denom' => $rational1['denom'] * $rational2['denom']\n ];\n}\n```\n\nС точки зрения вызывающего кода всё нормально, абстракция сохранена. На вход в `mul` подаются рациональные числа, на выходе — рациональное число. А вот внутри никакой абстракции нет, обращение с рациональными числами строится на основе знания их устройства. Любое изменение внутренней реализации рациональных чисел потребует переписывания всех операций, работающих с рациональными числами напрямую — то есть без селекторов или конструктора. Данный код нарушает принцип одного уровня абстракции (single layer abstraction).\n\nПри разработке сложных систем используется подход — _уровневое проектирование_. Он заключается в том, что системе придаётся структура при помощи последовательных уровней. Каждый из уровней строится путём комбинации частей, которые на данном уровне рассматриваются как элементарные. Части, которые строятся на каждом уровне, работают как элементарные на следующем уровне.\n\n> Уровневое проектирование пронизывает всю технику построения сложных систем. Например, при проектировании компьютеров резисторы и транзисторы сочетаются (и описываются при помощи языка аналоговых схем), и из них строятся и-, или- элементы и им подобные, служащие основой языка цифровых схем. Из этих элементов строятся процессоры, шины и системы памяти, которые в свою очередь служат элементами в построении компьютеров при помощи языков, подходящих для описания компьютерной архитектуры. Компьютеры, сочетаясь, дают распределённые системы, которые описываются при помощи языков описания сетевых взаимодействий, и так далее. (c) SICP\n\n```php\n<?php\n\nfunction mul($rational1, $rational2)\n{\n return makeRational(\n getNumer($rational1) * getNumer($rational2),\n getDenom($rational1) * getDenom($rational2)\n );\n}\n```\n\nВ нашем примере базовым уровнем являются типы, встроенные в сам язык: числа и массивы. На их основе сформирован уровень для представления рациональных чисел: `makeRational`, `getDenom`, `getNumer`. Затем — уровень, на котором реализованы арифметические операции над рациональными числами: сложение, вычитание, умножение и так далее.\n\nПодчеркну, что речь идёт про реализацию самих уровней. Например, операция сложения полностью опирается на конструктор и селекторы, но ничего не знает и не может знать про внутреннее устройство самих рациональных чисел. С другой стороны, это не значит, что в одном месте не могут появиться функции из разных уровней. Могут, и это нормально во многих случаях. Например:\n\n```php\n<?php\n\nfunction printSum($rational1, $rational2)\n{\n $rational3 = sum($rational1, $rational2);\n $denom = getDenom($rational3);\n $numer = getNumer($rational3);\n echo \"Denom: {$denom}\";\n echo \"Numer: {$numer}\";\n}\n```\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":2875,"name":"theory","url":"/courses/php-data-abstraction/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":2875,"name":"theory","url":"/courses/php-data-abstraction/lessons/intro/theory_unit"}],"id":1349,"slug":"intro","state":"approved","name":"Введение","course_order":10,"goal":"Знакомимся с курсом и его целями","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Абстракция – основной способ борьбы со сложностью в программировании. Она позволяет уйти от деталей реализации и сосредоточиться на главном. Хороший пример абстракции – функция сортировки массива. Не важно как она устроена, важно что она приводит к результату, который нас интересует.\n\nДругой пример – функции высшего порядка, такие как `map`, `filter` и `reduce`, позволяют обрабатывать коллекции без знания их внутреннего устройства. Причём коллекция не обязательно должна быть плоской, подобные функции можно написать для сколь угодно сложных структур, например, для деревьев. Абстракция с помощью функций помогает сосредоточиться на самой обработке, а не на процессе обхода данных.\n\nС другой стороны, сами данные нередко имеют сложную структуру. Представление пользователя в нетривиальной системе может потребовать описания десятков и сотен различных параметров и данных связанных с ними. В этой ситуации полезно спрятать эту структуру за набором функций. Эти функции скроют внутреннюю сложность и упростят поддержку кода. Это называется абстракция с помощью данных.\n\nВ этом курсе мы познакомимся с некоторыми базовыми принципами проектирования программ. С тем, как моделировать и представлять в программе объекты реального (и воображаемого) мира. Примером для проектирования послужит создание библиотеки для работы с графическими примитивами, такими как точки, отрезки, фигуры. Эта библиотека, с одной стороны, достаточно понятна для всех (в том числе визуально), с другой — очень просто представляется в коде.\n\nОсновные темы этого курса:\n\n* Предметная область (Domain Model)\n* Онтология\n* Уровни проектирования (Барьеры абстракции)\n* Инварианты\n"},"id":177,"slug":"php-data-abstraction","challenges_count":3,"name":"PHP: Абстракция с помощью данных","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы изучите построение абстракций в PHP. Вы познакомитесь с такими понятиями, как интерфейс, онтология и инварианты. Также научитесь выделять сущности предметной области, устанавливать правильные взаимоотношения между ними и грамотно проектировать интерфейсы. Знания из этого курса помогут скрывать ненужные детали с помощью абстракции и фокусировать внимание на интересующих вас понятиях.","kind":"advanced","updated_at":"2026-01-20T11:46:14.432Z","language":"php","duration_cache":30120,"skills":["Выделять сущности предметной области и устанавливать правильные взаимоотношения между ними","Подбирать правильную структуру данных для хранения сущностей","Грамотно проектировать интерфейсы абстракций","Определять инварианты и следовать им","Правильно использовать индексированный и ассоциативный массивы"],"keywords":["ER-модель","DDD","интерфейс"],"lessons_count":8,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NzQ4MCwicHVyIjoiYmxvYl9pZCJ9fQ==--e2fdd867c192da24cc3847ce1e5b90d4348ea4ce/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":2,"slug":"php","title":"PHP-разработчик","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":60,"duration_in_months":10},"id":1,"slug":"php","title":"РНР-разработчик","subtitle":"Изучите PHP и Laravel для разработки и проектирования REST API","subtitle_for_lists":"Изучите PHP и Laravel для разработки и проектирования REST API","locale":"ru","current":true,"duration_in_months_text":"10 месяцев","stack_slug":"php","price_text":"от 5 650 ₽","duration_text":"10 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk5MiwicHVyIjoiYmxvYl9pZCJ9fQ==--e9d0f30948ea766a7e6bc3e3d56c192344d45fb8/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programming-cuate%20(1).png"},{"stack":{"id":25,"slug":"php-oop","title":"ООП В PHP","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":4300,"duration_in_months":2},"id":38,"slug":"php-oop","title":"ООП В PHP","subtitle":"Навык глубокого понимания архитектуры и написания чистого кода, позволяющий решать сложные задачи","subtitle_for_lists":"Изучите архитектуру и чистый код на PHP","locale":"ru","current":true,"duration_in_months_text":"2 месяца","stack_slug":"php-oop","price_text":"от 3 900 ₽","duration_text":"2 месяца","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc1MSwicHVyIjoiYmxvYl9pZCJ9fQ==--e5793a1818ff43d73135cc7ed88c1998d7650470/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Developer%20activity-bro.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/php-data-abstraction/lessons/levels/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">PHP: Абстракция с помощью данных</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Уровневое проектирование</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Уровневое проектирование","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"PHP: Абстракция с помощью данных"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Рассмотрим ещё одну простую систему — рациональные числа и операции над ними. Напомню, что рациональным называют число, которое может быть представлено в виде дроби <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">a/b</code>, где <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">a</code> — это числитель дроби, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">b</code> — знаменатель дроби. Причём <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">b</code> не должно быть нулём, поскольку деление на ноль не допускается.</p>
<p>Рациональные числа в PHP не поддерживаются, поэтому абстракцию для них создадим самостоятельно. Как обычно, нам понадобятся конструктор и селекторы:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
$num = makeRational(1, 2); // создали рациональное число "одна вторая"
$numer = getNumer($num); // 1
$denom = getDenom($num); // 2</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>С помощью трёх функций мы определили рациональное число. Одна функция (конструктор) собирает его из частей, другие (селекторы) позволяют каждую часть извлечь. Чем при этом, с точки зрения языка, является <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">$num</code> — абсолютно не важно. Хоть функцией (и такое возможно), хоть массивом (индексированным или ассоциативным). Во внутренней реализации можно даже использовать строки:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
function makeRational($numer, $denom)
{
return "{$numer}/{$denom}";
}
function getNumer($rational)
{
return explode('/', $rational)[0];
}
function getDenom($rational)
{
return explode('/', $rational)[1];
}
print_r(makeRational(10, 3)); // => 10/3</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Несмотря на то, что мы научились представлять рациональные числа, эта абстракция сама по себе малоприменима. Абстракция становится полезна тогда, когда появляется возможность оперировать ей. Для рациональных чисел базовыми операциями можно считать арифметические, например, сложение, вычитание или умножение. Умножение рациональных чисел — самая простая операция. Для её выполнения нужно перемножить числители и знаменатели:</p>
<code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">3/4 * 4/5 = (3 * 4)/(4 * 5) = 12/20</code>
<p>Самое интересное начинается в процессе реализации. Если предположить, что реальная структура рационального числа выглядит так: <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">['numer' => 2, 'denom' => 3]</code>, то, чисто технически, решение может быть таким:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
function mul($rational1, $rational2)
{
return [
'numer' => $rational1['numer'] * $rational2['numer'],
'denom' => $rational1['denom'] * $rational2['denom']
];
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>С точки зрения вызывающего кода всё нормально, абстракция сохранена. На вход в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">mul</code> подаются рациональные числа, на выходе — рациональное число. А вот внутри никакой абстракции нет, обращение с рациональными числами строится на основе знания их устройства. Любое изменение внутренней реализации рациональных чисел потребует переписывания всех операций, работающих с рациональными числами напрямую — то есть без селекторов или конструктора. Данный код нарушает принцип одного уровня абстракции (single layer abstraction).</p>
<p>При разработке сложных систем используется подход — <em>уровневое проектирование</em>. Он заключается в том, что системе придаётся структура при помощи последовательных уровней. Каждый из уровней строится путём комбинации частей, которые на данном уровне рассматриваются как элементарные. Части, которые строятся на каждом уровне, работают как элементарные на следующем уровне.</p>
<blockquote>
<p>Уровневое проектирование пронизывает всю технику построения сложных систем. Например, при проектировании компьютеров резисторы и транзисторы сочетаются (и описываются при помощи языка аналоговых схем), и из них строятся и-, или- элементы и им подобные, служащие основой языка цифровых схем. Из этих элементов строятся процессоры, шины и системы памяти, которые в свою очередь служат элементами в построении компьютеров при помощи языков, подходящих для описания компьютерной архитектуры. Компьютеры, сочетаясь, дают распределённые системы, которые описываются при помощи языков описания сетевых взаимодействий, и так далее. (c) SICP</p>
</blockquote>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
function mul($rational1, $rational2)
{
return makeRational(
getNumer($rational1) * getNumer($rational2),
getDenom($rational1) * getDenom($rational2)
);
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В нашем примере базовым уровнем являются типы, встроенные в сам язык: числа и массивы. На их основе сформирован уровень для представления рациональных чисел: <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">makeRational</code>, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">getDenom</code>, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">getNumer</code>. Затем — уровень, на котором реализованы арифметические операции над рациональными числами: сложение, вычитание, умножение и так далее.</p>
<p>Подчеркну, что речь идёт про реализацию самих уровней. Например, операция сложения полностью опирается на конструктор и селекторы, но ничего не знает и не может знать про внутреннее устройство самих рациональных чисел. С другой стороны, это не значит, что в одном месте не могут появиться функции из разных уровней. Могут, и это нормально во многих случаях. Например:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
function printSum($rational1, $rational2)
{
$rational3 = sum($rational1, $rational2);
$denom = getDenom($rational3);
$numer = getNumer($rational3);
echo "Denom: {$denom}";
echo "Numer: {$numer}";
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/php?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">10 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">РНР-разработчик</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите PHP и Laravel для разработки и проектирования REST API</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk5MiwicHVyIjoiYmxvYl9pZCJ9fQ==--e9d0f30948ea766a7e6bc3e3d56c192344d45fb8/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programming-cuate%20(1).png" alt="РНР-разработчик" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 5 650 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/php-oop?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">2 месяца</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">ООП В PHP</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите архитектуру и чистый код на PHP</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc1MSwicHVyIjoiYmxvYl9pZCJ9fQ==--e5793a1818ff43d73135cc7ed88c1998d7650470/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Developer%20activity-bro.png" alt="ООП В PHP" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/php-data-abstraction/lessons/levels/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 8</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/php-data-abstraction/lessons/levels/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>