<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
  <channel>
    <language>ru</language>
    <title>Объектно-ориентированное программирование</title>
    <description/>
    <link>https://tproger.ru/tag/oop</link>
    <atom:link href="https://tproger.ru/tag/oop/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Tue, 29 Sep 2026 05:26:50 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>Объектно-ориентированное программирование</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>Метаклассы в Python: как работает фабрика классов</title>
      <link>https://tproger.ru/articles/metaklassy-v-python-kak-rabotaet-fabrika-klassov</link>
      <comments>https://tproger.ru/articles/metaklassy-v-python-kak-rabotaet-fabrika-klassov?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/metaklassy-v-python-kak-rabotaet-fabrika-klassov</guid>
      <description><![CDATA[<p>Разбираем, что такое метаклассы в Python, как type создаёт классы и когда стоит писать свой метакласс. Примеры кода и практические рекомендации.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/metaklassy-v-python-kak-rabotaet-fabrika-klassov">Метаклассы в Python: как работает фабрика классов</a>»</p>]]></description>
      <category><![CDATA[Лучшая практика]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Архитектура ПО]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Практика]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 09 Jul 2026 12:12:32 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если класс в Python — это шаблон, по которому создаются объекты, то метакласс — это шаблон, по которому создаются сами классы. Идея звучит рекурсивно, но именно в ней скрывается ответ на вопрос, почему в Python всё — объект: даже определение класса можно собрать, изменить или зарегистрировать программно.</p><p>В этой статье разберём, как внутри устроены метаклассы, зачем нужен встроенный type с тремя аргументами и когда стоит писать свой метакласс, а когда — обойтись наследованием или декоратором.</p><h2>Класс — тоже объект</h2><p>В Python 3 любой класс является экземпляром метакласса. По умолчанию этим метаклассом выступает type. Поэтому запись class Foo: pass интерпретатор превращает примерно в такой вызов:</p><p>С точки зрения языка разницы почти нет: и в том, и в другом случае получается объект класса Foo, у которого есть атрибуты, методы и собственный тип. Проверить это можно прямо в интерпретаторе:</p><p>type сам является экземпляром себя — это одна из тех особенностей Python, которые сначала удивляют, а потом помогают понять, что классы и объекты в языке живут по одним правилам.</p><p>Метакласс — это класс, экземплярами которого являются другие классы.</p><p>В Python 3 по умолчанию метаклассом любого класса является type.</p><p>Вызов type(name, bases, dict) создаёт класс динамически.</p><p>Собственный метакласс наследуется от type и переопределяет __new__ или __init__.</p><p>Чаще всего задачу решают наследованием или декоратором класса — метакласс нужен редко.</p><h2>type — не только функция проверки типа</h2><p>Всем знаком вызов type(x), который возвращает тип объекта. Но если передать type три аргумента, он превращается в фабрику классов:</p><ul><li>первый аргумент — имя класса (становится __name__);</li><li>второй — кортеж базовых классов (становится __bases__);</li><li>третий — словарь пространства имён (становится __dict__).</li></ul><p>Этот механизм лежит в основе любого определения класса. Когда Python видит ключевое слово class, он сначала собирает тело класса в словарь, а затем вызывает метакласс, чтобы создать сам класс.</p><p>Пример — класс с методом, собранный вручную:</p><p>Такой код редко пишут в production, но он наглядно показывает, что класс — это всего лишь объект, созданный вызовом фабрики. А значит, эту фабрику можно заменить на свою.</p><h2>Как написать свой метакласс</h2><p>Чтобы изменить процесс создания класса, наследуемся от type и переопределяем __new__. Этот метод отвечает за создание самого класса, поэтому в нём можно добавить общие атрибуты, проверить имя или зарегистрировать класс в каталоге.</p><p>Здесь AutoAttrMeta автоматически добавляет атрибут version каждому классу, который её использует. То же самое можно сделать через наследование, но метакласс действует на этапе создания класса, а не при вызове методов.</p><p>Помимо __new__, часто переопределяют __init__ метакласса: он получает уже созданный класс и может его донастроить. А __call__ контролирует создание экземпляров класса — именно он вызывает __new__ и __init__ объекта.</p><h2>А точно нужен метакласс?</h2><p>Тим Петерс, автор «Дзена Python», как-то сказал, что метаклассы — это магия, которую 99% разработчиков никогда не понадобится. Если вы сомневаетесь, нужны ли они вам, — скорее всего, не нужны.</p><blockquote>Метаклассы — это более глубокая магия, чем та, о которой 99% пользователей должны беспокоиться. Если вы сомневаетесь, нужны ли они вам, значит, не нужны.</blockquote><p>Для сравнения — три способа дать классам общий атрибут:</p><h3>Наследование</h3><h3>Декоратор класса</h3><h3>Метакласс</h3><p>Наследование и декоратор читаются проще и не лезут в механику создания класса. Метакласс выигрывает, когда поведение должно быть неизбежным для всех наследников или когда логика завязана на сам процесс построения класса.</p><p><b>Когда метакласс действительно уместен:</b><br />— автоматическая регистрация подклассов в плагиновой системе;<br />— валидация объявлений полей (например, ORM проверяют типы атрибутов);<br />— принудительный единый API для большого семейства классов;<br />— реализация паттернов вроде Singleton на уровне класса.</p><h2>Практический пример: автоматическая регистрация плагинов</h2><p>Представьте, что вы пишете расширяемую систему: каждый новый адаптер должен попадать в общий реестр. Метакласс может добавлять класс в реестр сразу при определении, без ручного вызова регистрации.</p><p>Такой подход удобен, потому что автор плагина просто объявляет класс, а реестр обновляется сам. В реальных фреймворках — например, в старых версиях Django — похожая идея используется для построения моделей данных.</p><h2>Выводы</h2><p>Метаклассы — не ежедневный инструмент, но понимание их работы делает код Python прозрачнее. Вы начинаете видеть, что class — это тоже объект, а type — всего лишь фабрика, которую при желании можно заменить.</p><p>Главное правило: если задачу решает наследование или декоратор класса, не тащите метакласс. А если без контроля за созданием класса никуда — смело берите type и наследуйтесь от него.</p><p>Источник и дополнительное чтение: <a href="https://realpython.com/python-metaclasses/">Python Metaclasses</a> на Real Python.</p>]]></content:encoded>
    </item>
    <item>
      <title>Хорошо ли вы знаете принципы SOLID — тест</title>
      <link>https://tproger.ru/quiz/horowo-li-vy-znaete-principy-solid---test</link>
      <comments>https://tproger.ru/quiz/horowo-li-vy-znaete-principy-solid---test?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дух айтишной эмо школы]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/quiz/horowo-li-vy-znaete-principy-solid---test</guid>
      <description><![CDATA[<p>SOLID — это ключевые принципы в объектно-ориентированном программировании. Проверьте свои знания SOLID, ответив на 10 вопросов. </p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/quiz/horowo-li-vy-znaete-principy-solid---test">Хорошо ли вы знаете принципы SOLID — тест</a>»</p>]]></description>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Викторины]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 30 Jan 2024 10:03:21 GMT</pubDate>
      <content:encoded><![CDATA[<p>SOLID — это ключевые принципы в объектно-ориентированном программировании. Они помогают разработчикам создавать более устойчивый, гибкий и адаптируемый к изменениям системы код.</p><p>В этом тесте вы сможете проверить свои знания SOLID, ответив на ряд вопросов. На каждый вопрос можно дать только один верный ответ.</p><p>Удачи!</p>]]></content:encoded>
    </item>
    <item>
      <title>Как автоматически проверить задание на знание ООП на примере Stepik</title>
      <link>https://tproger.ru/articles/kak-proverit-zadanie-po-programmirovaniyu-v-stile-oop-avtomaticheski-na-primere-stepik-2-chast</link>
      <comments>https://tproger.ru/articles/kak-proverit-zadanie-po-programmirovaniyu-v-stile-oop-avtomaticheski-na-primere-stepik-2-chast?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Роман Агренин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-proverit-zadanie-po-programmirovaniyu-v-stile-oop-avtomaticheski-na-primere-stepik-2-chast</guid>
      <description><![CDATA[<p>Рассказали, как построить автоматизированную систему для проверки задач на знание ООП согласно требованиям Stepik.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-proverit-zadanie-po-programmirovaniyu-v-stile-oop-avtomaticheski-na-primere-stepik-2-chast">Как автоматически проверить задание на знание ООП на примере Stepik</a>»</p>]]></description>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Автоматизирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 29 Nov 2023 08:03:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Обучение объектно-ориентированному программированию (ООП), как правило, строится либо на излишне тривиальных примерах вроде животных, либо на абстракциях. Это трудно для понимания, так как никак не пересекается со встреченными до этого момента задачами и проблемами программирования.</p><p>В прошлой <a href="https://tproger.ru/articles/obuchenie-oop-na-primere-realizacii-klassa-kucha-v-python-1-chast">заметке</a> на примере плана урока по реализации класса структуры типа «куча» мы показали, как построить пошаговую подачу материала с постепенным усложнением практических задач. Теперь их необходимо реализовать.</p><p>Образовательные платформы реализуют разные подходы к проверке заданий по программированию. Мы рассмотрим Stepik, как одну из наиболее доступных. Она позволяет любому желающему создать свой курс и наполнить его задачами по программированию бесплатно.</p><p>Stepik — прекрасная платформа онлайн-образования, позволяющая проходить онлайн-курсы по различным дисциплинам. Однако она имеет серьёзный недостаток, так как тестирование жёстко завязано на проверку сравнения потока вывода и некоего эталона, а не самих классов, экземпляров, атрибутов и методов.</p><p>Начнём с системы проверки заданий по программированию: как она устроена, и как можно её улучшить для целей проверки более сложных задач?</p><h2>Описание стандартной схемы проверки</h2><p>Механизм проверки заданий на программирование на платформе Stepik состоит из двух частей:</p><ol><li>«Языки и шаблоны»;</li><li>«Расширенный редактор».</li></ol><p>В расширенном редакторе автор задания должен реализовать три функции:</p><ol><li>generate();</li><li>check(reply, clue);</li><li>solve(dataset).</li></ol><p>generate возвращает список строк.</p><p>Каждая строка — это один тест, который проверяет код учащегося независимо от других. Текст строки попадает в поток ввода в начале теста.</p><p>solve(dataset) — функция, реализующая эталонное решение. В качестве аргумента dataset она получает строку теста целиком, с символами переноса строк. В качестве ответа функция должна предоставить строку эталонного ответа.</p><p>check(reply, clue) — функция, осуществляющая сравнение эталонного ответа и ответа учащегося. На вход принимает две строки. Возвращает логическое значение (True или False) в зависимости от результата сравнения. При получении на вход в качестве обоих параметров эталонного ответа должна возвращать True, иначе задача считается сломанной (то есть некорректно оценит ответ учащегося).</p><p>В простейшем варианте check фактически сравнивает поток вывода в каждом тестовом случае у учащегося и эталонной функции solve, однако, не для всех задач это приемлемо.</p><p>Например, если в задаче требуется найти площадь правильного треугольника со стороной 5, то некорректно в качестве эталонного значения использовать строку «10.825317547305483» — в зависимости от округления и порядка операций точность вычислений может быть разной. Более корректно будет привести полученный от учащегося и от эталонной функции ответ к типу float, после чего сравнить абсолютную разницу между этими числами с пороговым значением (например, 0,0000000001).</p><p>В блоке «Языки и шаблоны» фактически формируется код учащегося с помощью четырёх блоков в следующем порядке:</p><p>В данном примере python3 и kotlin — языки программирования, разрешённые к использованию учащимся. Здесь могут быть перечислены несколько языков, каждый с новой строки.</p><figure><img src="https://media.tproger.ru/user-uploads/80428/2023-11-22/9793ef71-a28e-4123-8adf-6a6e2104a3bc.jpg" alt="" /></figure><p>Такая реализация позволяет довольно легко создавать задания, где учащийся сам должен считать что-то из потока ввода, а ответ выводить в поток вывода (есть даже упрощённый редактор во вкладке «Тестовые данные», где задаются непосредственно строки ввода и вывода).</p><h3>Какие у этого сложности</h3><p>Такая схема не проверяет, как был получен ответ.</p><p>Чтобы обойти это, необходимо:</p><ul><li>сгенерировать уникальные тесты,</li><li>считать их в блоке ::header до кода студента,</li><li>вывести в блоке ::footer код, который в зависимости от состояния теста, полученного ранее, проведёт только определённые проверки.</li></ul><p>Например, для Python: сначала проверить есть ли в пространстве local объект с именем класса, чтобы узнать, реализовал ли учащийся такой класс. После чего узнать тип этого объекта, чтобы удостовериться, что это именно класс, а не функция. И, наконец, вызвать конструктор этого класса, чтобы удостовериться, что он работает.</p><p>И только если весь этот код отработает без ошибок, можно вывести какое-то сообщение, например, «Базовая проверка пройдена».</p><p>Поскольку весь код, написанный в блоке ::header, уже выполнился к моменту начала работы кода учащегося, он может узнать обо всех проверках, которые мы там производим. Следовательно, никаких сложных проверок там делать не стоит.</p><p>Также это значит, что необходимо предусмотреть непубличные тесты, где поток ввода будет влиять непосредственно на поток вывода, а не вызывать заранее заготовленные сообщения.</p><p>Однако ООП довольно тяжело даётся многим студентам, и новичкам часто сложно понять природу проблемы по ошибкам и исключениям. Поэтому на начальных этапах необходимо добавить как можно больше простых проверок, которые в случае проблем будут выводить в поток вывода сообщения, описывающие проблему.</p><p>Рассмотрим этот процесс на примере грейдера задачи, проверяющей реализацию на Python простого класса Heap, хранящего внутри экземпляров всего один атрибут data с типом список.</p><h2>Система проверки задачи</h2><h3>generate</h3><p>Создадим шесть простых сообщений, которые будут публичными тестами.</p><p>Остальные тесты будут приватными (для начала добавим один). Для определённости и простоты отладки поместим внутрь тестовых сообщений копию кода, который будем выполнять в тестовом сценарии.</p><h3>check</h3><p>Как уже было описано ранее, сравнение с эталоном в данном уроке можно проводить простым сравнением строк:</p><h3>solve</h3><p>Внутрь функции solve поместим эталонный класс Heap, который должен проходить все проверки.</p><p>В дальнейшем весь код мы переместим в раздел ::footer шаблона с двумя изменениями:</p><ol><li>Шаблон не будет содержать эталонной реализации Heap, её должен написать учащийся.</li><li>Вместо return мы будем использовать print, так как по жизненному циклу код учащегося не возвращает строку, а именно выводить её в поток вывода.</li></ol><h2>Система проверки цепочек задач с постепенно усложняющимся условием</h2><p>Очевидно, это необходимо для того, чтобы путём декомпозиции задачи позволить учащемуся сперва решить более простую задачу. Например, реализовать класс-заглушку, как в примере выше, и только после этого добавить в него методы, соответствующие реальной структуре данных (в нашем примере, очевидно, «куче»).</p><p>Система проверки всех задач строится по описанному выше принципу, однако наследует все тесты предыдущих задач, так как мы модифицируем и расширяем функционал одних и тех же классов.</p><p>Поскольку платформа Stepik не позволяет скрыть первые тесты, мы будем помещать эти тесты в конце, делая их приватными. Это может быть спорным решением в плане дизайна, так как получив ошибку в неизвестном приватном тесте учащийся обычно не знает, как её исправлять. Однако, в описании урока мы явно обозначим этот факт, напомнив в тесте, что код решения текущей задачи должен успешно проходить и все предыдущие.</p><p>Так же необходимо заблокировать возможность использования модулей стандартной библиотеки Python, реализующих функциональность «кучи», чтобы учащийся самостоятельно реализовал их. Покажем это на примере блокировки модуля heapq:</p><p>Код помещается в начале шаблона, (в блоке ::header) и выполняется до кода учащегося.</p><h2>Что дальше?</h2><p>Примеры описанного подхода можно посмотреть в следующих уроках:</p><ol><li><a href="https://stepik.org/lesson/1024973/">на примере класса «Кучи»</a>;</li><li><a href="https://stepik.org/lesson/68721/step/12">на примере животных</a> (кстати, в этом курсе такой же подход во многих уроках используется для задач на написание функций).</li></ol><p>А всем, кто планирует реализацию своих курсов с задачами по программированию на Stepik мы рекомендуем сперва реализовать шаблонизатор, позволяющий генерировать заготовки заданий из готовых функций или классов и наборов тест-кейсов. Это позволит сделать действительно полезные задачи прямо на Stepik, без интеграции с отдельной проверяющей системой.</p>]]></content:encoded>
    </item>
    <item>
      <title>Обучение ООП на примере реализации класса «Куча» в Python (1 часть)</title>
      <link>https://tproger.ru/articles/obuchenie-oop-na-primere-realizacii-klassa-kucha-v-python-1-chast</link>
      <comments>https://tproger.ru/articles/obuchenie-oop-na-primere-realizacii-klassa-kucha-v-python-1-chast?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Роман Агренин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/obuchenie-oop-na-primere-realizacii-klassa-kucha-v-python-1-chast</guid>
      <description><![CDATA[<p>Составили пошаговый план урока по обучению реализации класса «Куча» в Python. Теория, визуализация, просеивание, очередь и не только.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/obuchenie-oop-na-primere-realizacii-klassa-kucha-v-python-1-chast">Обучение ООП на примере реализации класса «Куча» в Python (1 часть)</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 31 Oct 2023 10:06:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Всем привет! Меня зовут Задойный Алексей, я ведущий архитектор в Группе «Иннотех» Холдинга Т1.</p><p>Для многих учащихся концепции объектно-ориентированного программирования (ООП) кажутся неестественными, непригодными к практическому использованию. На них нужно много накладных расходов для реализации, при первом освоении языка и знакомстве с основами синтаксиса и процедурным подходом. Однако промышленное программирование практически никогда не обходится без использования ООП, поэтому концепцию нужно изучить максимально широкому кругу учащихся, включая тех, кто не претендует на должность программиста, но планирует использовать в своей работе и личной жизни программирование.</p><p>Перспективнее всего обучаться на практике. То есть предоставлять учащемуся минимальные базовые концепций, позволяющие реализовать классы, методы, экземпляры и наследование, после чего проходить эти концепции на практических примерах для создания структуры, которую нельзя или довольно сложно реализовать другим способом.</p><h2>Обратный педагогический дизайн</h2><p>Не будем детально вдаваться в концепцию обратного педагогического дизайна, но сразу определим задачи обучения: что должен уметь учащийся в итоге?</p><ul><li>Создавать классы.</li><li>Создавать экземпляры объектов с использованием классов.</li><li>Создавать методы внутри классов и использовать их в экземплярах.</li><li>Реализовывать наследование классов.</li></ul><p>Обратите внимание, что мы ставим ограниченный набор задач, в частности, не рассматриваем вопрос интерфейсов, абстрактных классов (и их методов) и тому подобное.</p><p>Это делается сознательно. Во-первых, невозможно охватить все темы единоразово. Во-вторых, конструктивнее будет создать отдельный урок, чтобы охватить какие-то из этих тем и повторить ранее изученные. Это концепция интервального повторения, позволяющая учащемуся на практике закрепить знания и умения.</p><p>После определения целей, определимся и с методами проверки полученных навыков. Наиболее распространённый современный метод: тестирование. Но оно не позволяет полноценно определить полноту владения навыком, только знания (да и то, учащийся может случайно угадать ответ). Более продуктивным будет использование практической задачи. Но как её проверить? Не станешь же вручную смотреть задачу каждого ученика в онлайн-курсе, где их могут быть сотни и тысячи. Очевидно, этот процесс можно автоматизировать, мы рассмотрим это во второй части материала.</p><p>После определения метода проверки следует перейти к тому, как учащийся освоит навык. Очевидно, через непосредственную реализацию (привет «концепции конструктивизма» в обучении). Этот этап можно совместить с онлайн-проверкой заданий, если не вводить штрафных санкций, не ограничивать попытки и давать развёрнутую обратную связь.</p><p>И наконец, мы определяем, как и какие знания мы дадим учащемуся, чтобы он справился с этапом освоения навыка. Это могут быть текстовые или видеолекции. Важнее иное. Не следует загружать учащегося информацией, которая ему не пригодится. На каждом этапе мы даём ему ровно тот объём, который необходим для освоения следующего, чтобы он не утонул в потоке информации.</p><p>Увы, когда человек только осваивает программирование, ему бесполезно читать большие и умные книжки. Оставьте это для следующего этапа, когда он усвоит основные концепции и у него появятся вопросы.</p><p>Обратите внимание, что этапы проектирования урока для учащегося идут в обратном порядке:</p><ol><li>Определение результатов обучения (навыков, которые освоит учащийся).</li><li>Определение методов проверки освоения результатов.</li><li>Определение метода выработки навыков.</li><li>Определение, какой материал необходимо дать, чтобы сформировать необходимые знания.</li></ol><p>Поэтому метод и называется обратным дизайном.</p><p>Предположим, все шаги пройдены. Но на чём мы будем вырабатывать навыки?</p><p>Классический подход обучения ООП — аналогии с объектами реального мира, в частности, примеры с животными. Не будем повторяться и выберем что-то более интересное и экзотичное. Это не значит, что на практике животных из ООП следует исключить, скорее лучше рассмотреть несколько разных примеров в зависимости от уровня подготовки учащихся.</p><p>Всё-таки, животные могут жить и в переменных… Попробуем задать нечто, где без классов и объектов будет тяжело.</p><p>Для этого рассмотрим структуру данных «куча», как наиболее показательную и достаточно простую в реализации.</p><p>Также заметим, что представленный план урока написан для Python для конкретизации, однако, относительно легко может быть адаптирован под иные языки программирования.</p><h2>1. Введение</h2><p>В большинстве задач урока не требуется считывать данные или выводить что-то на печать самостоятельно, так как это должна делать проверяющая система. Учащийся лишь должен корректно реализовать все классы с их методами и атрибутами, согласно условиям задачи.</p><p>Поток вывода также сообщает учащемуся о наступлении какой-то ошибки, если проверяющей системе это удаётся отследить (подробнее опишем в разделе про задачу на реализацию базового класса).</p><h2>2. Задача на реализацию Heap</h2><p>Примерами уроков, знакомящих с синтаксисом ООП, можно считать любой из следующих:</p><ul><li><a href="https://stepik.org/lesson/68721/">Stepik</a>,</li><li><a href="https://www.codecademy.com/learn/learn-object-oriented-programming-with-python">Codecademy</a>.</li></ul><p>Первая задача подготовительная на закрепление и разминку.</p><p>Необходимо реализовать класс Heap, поддерживающий всего 1 метод — __init__. Он в свою очередь должен поддерживать необязательный аргумент data.</p><p>При вызове метода у экземпляра класса должен появляться атрибут data.</p><ul><li>Если метод был вызван без атрибута, то с пустым списком внутри.</li><li>Если в метод был передан список, то этот список должен быть передан в атрибут data.</li></ul><p>Класс должен иметь оформленную документацию (docstring). Для реализации функционала нужно обратиться к стандарту PEP 257.</p><p>Мы даём это в примечании, а не раскрываем теорию отдельным шагом. Это нужно, чтобы студент изучил стандарт PEP самостоятельно, и чтобы побудить их читать и писать помогающие учиться комментарии (если это предусмотрено платформой).</p><p>Также задача проверяет, когда учащийся не задал в качестве значения атрибута data по умолчанию пустой список.</p><h2>3. Теория — что такое «куча»</h2><p>Понятие «кучи» демонстрируется на массиве из тестовых данных предыдущей задачи, который образует троичную «кучу».</p><figure><img src="https://media.tproger.ru/user-uploads/75379/2023-10-30/9ea397ff-160f-4e79-9fab-d61998f39042.jpeg" alt="" /></figure><p>Выводим количество элементов полностью заполненной троичной «кучи» с k уровнями через сумму членов геометрической прогрессии.</p><p>Если ввести понятие «кучи» с d детьми у каждого родителя, то для неё также будет выполнено это свойство, и можно найти число элементов в полностью заполненной d-«куче».</p><p>На основе этой формулы вводится формула высоты d-«кучи» с n-элементами.</p><p>В конце урока повторяем ряд выводов и полезных формул, включая формулу для определения количества элементов на определённом уровне.</p><h2>4. Задача на визуализацию «кучи»</h2><p>Для закрепления материала (и чтобы учащийся в дальнейшем мог упростить процесс отладки своего кода) введём промежуточную задачу, где необходимо представить объект «кучи» в виде псевдографики:</p><figure><img src="https://media.tproger.ru/user-uploads/75379/2023-10-30/07d91d4d-e9ee-4c32-abb7-fc9bc4f18075.jpeg" alt="" /></figure><p>Необходимо:</p><ol><li>Модифицировать метод __init__, добавив ещё один необязательный атрибут — child_count.</li><li>Создать служебный метод определения глубины дерева __get_depth__.</li><li>Создать метод __str__ (его назначение покажем на примере использования функции print для экземпляров класса).</li></ol><p>После решения задачи учащийся получает доступ к форуму решений, где есть код, позволяющий построить более сложную и красивую псевдографику:</p><figure><img src="https://media.tproger.ru/user-uploads/75379/2023-10-30/6b29e840-2af3-4cdf-b4d9-91c8ecfaf231.jpeg" alt="" /></figure><p>Следует учитывать особенности платформы, ведь поток вывода может отображаться не моноширинным шрифтом в отличие от Jupyter Notebook или консоли, поэтому ширина символов «┬», «┐», «├», «─» и «│» может отличаться от ширины пробела или цифры, что не позволит использовать данное решение на выбранной платформе обучения.</p><p>Однако публикация такого решения на форуме и анонс его в теле задачи мотивируют учащихся чаще изучать форум решений, решения других учащихся и черпать новые идеи.</p><h2>5. Мин-«куча», макс-«куча»</h2><p>Уточняем определение «кучи» из предыдущего теоретического раздела. Если до сих пор «куча» была макс-«кучей», то теперь вводится понятие мин-«кучи».</p><p>Ссылки на видеолекции о кучах из курсов по алгоритмам:</p><ul><li><a href="https://stepik.org/lesson/13240/">Очереди с приоритетами</a>.</li><li><a href="https://stepik.org/lesson/41235/">Алгоритмы: теория и практика. Структуры данных</a>.</li></ul><p>Вводим понятие просеивания вверх и просеивания вниз.</p><p>Для иллюстрации процессов используем пошаговые иллюстрации, на каждой из которых меняемые местами элементы «кучи» выделяются цветом:</p><figure><img src="https://media.tproger.ru/user-uploads/75379/2023-10-30/2e52e8d3-386c-44b2-a251-45c598e03b3c.jpeg" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/75379/2023-10-30/0dda870d-cdf8-453d-a060-3b4551c01f3f.jpeg" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/75379/2023-10-30/2b120b93-eca9-4c36-834c-89261e0c676c.jpeg" alt="" /></figure><p>Предлагаем использовать просеивание вверх для пошагового наполнения «кучи» из пустого состояния, чтобы гарантировать её свойства.</p><p>А также использовать получение вершины «кучи», замену этого элемента на заведомо «тонущий» (например, на float(“-inf”) для макс-кучи или float(“inf”) для мин-«кучи») и последующее просеивание вниз, если необходимо извлечь элемент из «кучи».</p><p>Удаление такого элемента после просеивания может быть опасно, так как может нарушить свойства «кучи».</p><figure><img src="https://media.tproger.ru/user-uploads/75379/2023-10-30/c2b6dbf9-9b54-4a09-be77-d6d7965affd2.jpeg" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/75379/2023-10-30/52a09254-8940-404b-8e2b-94f6cbc9d4f3.jpeg" alt="" /></figure><p>В конце урока вводятся формулы для определения индекса родителя по индексу ребёнка и наоборот.</p><h2>6. Задача — просеивание вверх</h2><p>Познакомимся с двумя служебными атрибутами:</p><ul><li>__name__,</li><li>__class__.</li></ul><p>Рекомендуется использовать эти служебные атрибуты, чтобы модифицировать метод __str__ класса Heap. Это позволит при наследовании классов HeapMax и HeapMin не переписывать этот метод.</p><p>Суть задачи — реализовать два класса HeapMax и HeapMin, наследовав их от Heap. И каждому добавить по два метода: sift_up(i) и insert(n).</p><p>Начиная с этой задачи грейдер блокирует использование модуля heapq, чтобы учащийся самостоятельно реализовывал методы кучи.</p><h2>7. Задача — просеивание вниз</h2><p>В задаче необходимо реализовать ещё два метода: sift_down(i) и pop().</p><p>При использовании метода pop вершина заменяется на «бесконечность», просеивается вниз не удаляется (даже если это можно сделать безопасно).</p><h2>8. Очередь с приоритетами</h2><p>Классическая задача на реализацию очереди с приоритетами.</p><p>Задача не проверяет наличие ООП-классов, наоборот для разнообразия следует потребовать работать с потоками ввода и вывода.</p><p>Вероятно, что учащийся, только что реализовавший «кучу», воспользуется своей реализацией, просто добавив вызовы конструктора класса, а также методов.</p><p>Эта задача закрепляет материал, показывает его практическую пользу, а также позволяет потренироваться не только в написании классов, но и в использовании уже готовых.</p><h2>9. и 10. Сортировка «кучей»</h2><p>Даём краткую иллюстрацию практического применения «кучи» для объединения и сортировки нескольких предварительно отсортированных массивов, а после этого отрабатываем этот навык.</p><h2>11. и 12. Починка дерева</h2><p>Учащийся знакомится с методом починки дерева, если оно не было построено с нуля с помощью просеивания вверх, а потом отрабатывает навык.</p><h2>13. Изменение приоритета</h2><p>Задача на изменение элемента и последующее его просеивание для восстановления свойств «кучи». Фактически задача на закрепление материала.</p><h2>Что дальше?</h2><p>А дальше самое интересное — необходимо взять учебную онлайн платформу и реализовать на ней проверку всех задач. =)</p><p>Но этим мы займёмся уже во 2 части…</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы ООП: полиморфизм в программировании</title>
      <link>https://tproger.ru/articles/osnovnye-principy-oop-polimorfizm-v-programmirovanii</link>
      <comments>https://tproger.ru/articles/osnovnye-principy-oop-polimorfizm-v-programmirovanii?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Марина Александровна]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/osnovnye-principy-oop-polimorfizm-v-programmirovanii</guid>
      <description><![CDATA[<p>Полиморфизм — один из основных принципов ООП: что это такое, в каких случаях используется, а также наглядный пример полиморфизма в ООП.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/osnovnye-principy-oop-polimorfizm-v-programmirovanii">Основные принципы ООП: полиморфизм в программировании</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 24 Aug 2023 12:26:20 GMT</pubDate>
      <content:encoded><![CDATA[<p>Одним из ключевых принципов ООП является полиморфизм — концепция, позволяющая создавать более гибкие, расширяемые и понимаемые программы. В этой статье мы рассмотрим суть полиморфизма, его типы и приведем примеры кода для лучшего понимания данного принципа ООП.</p><h2>Для чего нужен полиморфизм в программировании?</h2><p>Полиморфизм в контексте ООП означает, что разные объекты могут реагировать на один и тот же запрос, проявляя разное поведение в зависимости от своего типа. Это позволяет сократить дублирование кода, улучшить читаемость и облегчить расширение программы.</p><h2>Преимущества принципа полиморфизма</h2><ol><li>Гибкость и расширяемость. Полиморфизм позволяет добавлять новые типы объектов и операций без изменения существующего кода. Новые классы, реализующие общий интерфейс, могут быть легко интегрированы в существующую систему.</li><li>Упрощение кода. Полиморфизм способствует уменьшению дублирования кода. Общий интерфейс или абстрактный базовый класс позволяют описать общее поведение, и каждый конкретный класс реализует только свою специфичную логику.</li><li>Читаемость кода. Полиморфизм делает код более интуитивно понимаемым, так как работа с различными объектами происходит через общий интерфейс. Это упрощает восприятие кода другими разработчиками и способствует поддержке программы.</li><li>Расширение функциональности. Добавление новых функций или операций для существующих классов становится проще. Достаточно реализовать необходимые методы в новых классах, которые наследуют общий интерфейс.</li><li>Повторное использование кода. Полиморфизм позволяет использовать одни и те же методы для разных типов данных. Это устраняет необходимость создания аналогичных функций для разных классов.</li><li>Улучшение тестирования. Тестирование становится более удобным, так как можно создать общие тестовые сценарии для всех классов, реализующих один интерфейс. Это способствует повышению качества и надежности программы.</li><li><a href="https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii">Абстракция</a> и <a href="https://tproger.ru/articles/osnovnye-principy-oop-inkapsulyaciya-v-programmirovanii/">инкапсуляция</a>. Полиморфизм позволяет абстрагироваться от конкретных реализаций и сосредоточиться на общем поведении объектов. Также он способствует инкапсуляции, разделяя интерфейс от деталей реализации.</li><li>Облегчение командной разработки. Когда разработчики работают над разными частями программы, полиморфизм позволяет им взаимодействовать через общие интерфейсы без необходимости глубокого понимания внутренней реализации друг друга.</li></ol><h2>Виды полиморфизма в объектно-ориентированном программировании</h2><p>Разные виды полиморфизма в объектно-ориентированном программировании обеспечивают гибкость и расширяемость кода. Они позволяют обращаться с разными типами данных единообразно, что делает программы более понятными и удобными для разработки и обслуживания программного кода.</p><h3>1. Полиморфизм подтипов (наследования)</h3><p>Этот вид полиморфизма основан на <a href="https://tproger.ru/articles/osnovnye-principy-oop-nasledovanie-v-programmirovanii">наследовании</a> и позволяет объектам дочерних классов использоваться как объекты родительского класса. Это делает код более гибким и облегчает добавление новых типов.</p><h3>2. Параметрический полиморфизм (обобщённое программирование)</h3><p>Параметрический полиморфизм позволяет создавать обобщенные функции и классы, которые могут работать с разными типами данных без знания их конкретной природы.</p><h3>3. Полиморфизм в интерфейсах</h3><p>Интерфейсный полиморфизм позволяет объектам разных классов реализовывать общий интерфейс и предоставлять схожее поведение без явного наследования.</p><p>Полиморфизм — это суть объектно-ориентированного программирования, позволяющая создавать гибкие и расширяемые программы. Благодаря различным видам полиморфизма, разработчики могут писать более чистый, читаемый и эффективный код. Овладение этим принципом существенно обогатит навыки любого программиста и сделает его программы более элегантными и функциональными.</p>]]></content:encoded>
    </item>
    <item>
      <title>ООП в JavaScript на примерах с Фредди Меркьюри</title>
      <link>https://tproger.ru/articles/oop-i-js</link>
      <comments>https://tproger.ru/articles/oop-i-js?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Фетюхин про IT]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/oop-i-js</guid>
      <description><![CDATA[<p>Объяснили объектно-ориентированное программирование или ООП в JavaScript для начинающих программистов на примерах с Фредди Меркьюри.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/oop-i-js">ООП в JavaScript на примерах с Фредди Меркьюри</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 15 Aug 2023 07:31:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Меня зовут Артур Памбухчян, я — frontend-разработчик в IT-компании Intelsy.</p><p>В Intelsy мы помогаем бизнесу запускать интернет-магазины, делать продукты для финтеха, корпоративные сайты и личные кабинеты, и также автоматизировать бизнес-процессы.</p><p>Команда растет, поэтому стараемся поддерживать нашу внутреннюю базу знаний, чтобы онбординг новых сотрудников, а также менторство и наставничество, проходили наименее трудо- и времязатратно.</p><p>Решил поделиться небольшой статьей на тему объектно-ориентированного программирования — ввести в курс дела тех, кто только начинает работать с ООП.</p><h2>Что такое ООП</h2><p>По сути, ООП или Объектно-ориентированное программирование — это способ написания кода, позволяющий создавать одни объекты с помощью других.</p><p><i>В изучении данного метода нам поможет Фредди Меркьюри, так будет легче запомнить. Каким образом он осуществит свою помощь, поймете буквально в следующих абзацах.</i></p><p>Общий объект обычно называется планом, проектом или схемой (blueprint), а создаваемые с его помощью объекты — экземплярами (instances).</p><figure><img src="https://media.tproger.ru/uploads/2023/08/ef02d47c-daf9-4f66-acea-cea78da42ba0.jpg" alt="" /></figure><p>Мы можем заметить что класс Human имеет только два свойства, и в constructor мы присваиваем начальные значения для human-instance.</p><p>Мы можем опустить constructor, если нам не требуется присваивать начальные значения.</p><figure><img src="https://media.tproger.ru/uploads/2023/08/d6cf9362-c0a2-432e-9e1a-52668b41c19e.jpg" alt="" /></figure><p>Экземпляры также создаются с помощью ключевого слова «new».</p><p>Мы создали instance класса Human и передали начальные значения в конструктор.</p><h2>Четыре принципа ООП</h2><ul><li>Наследование.</li><li>Полиморфизм.</li><li>Инкапсуляция.</li><li>Абстракция.</li></ul><p>Наверняка вы не раз слышали эти слова, давайте их разберем.</p><h3>Наследование</h3><p>Это механизм базирования объекта, class на другом объекте (наследование на основе прототипа) или class (наследование на основе класса).</p><p>Мы избегаем необходимости переписывать один и тот же код, а также экономим пространство памяти, используя общие методы.</p><figure><img src="https://media.tproger.ru/uploads/2023/08/a7f91120-83f6-45c2-ad81-df3f910a23e8.jpg" alt="" /></figure><p>Мы создали новый класс Singer, который расширяется классом Human.</p><p>Нам не нужно заново создавать firstName и lastName в Singer, так как это уже было сделано в классе Human. Вы также могли заметить вызов super(firstName, lastName) — это вызов родительского конструктора.</p><p>Обратите внимание, что мы наследуем Singer от Human, а не наоборот. В Human мы постарались записать все свойства, которые есть у человека.</p><p>В Singer будут свойства только певца: например, мы инициализируем переменную bandName (название музыкальной группы).</p><p>В Singer было объявлено новое свойство bandName, ведь не у всех Human оно есть.</p><p>Все певцы — люди, но не все люди — певцы.</p><h3>Полиморфизм</h3><p>Само слово означает «много форм».</p><p>Существует много толкований сути явления, но основная его идея в ООП заключается в способности вызывать один и тот же метод для разных объектов, и при этом каждый объект будет реагировать по-своему.</p><p>Изменим наши классы следующим образом:</p><figure><img src="https://media.tproger.ru/uploads/2023/08/302c5c18-ff1a-40eb-ae30-0f1e4acc80d6.jpg" alt="" /></figure><p>Мы добавили новый метод sayHi(), где человек должен представиться, назвав свое имя. Но что же происходит в классе Singer? Человек называет свое имя, а Singer сообщает, что он певец.</p><p>Мы переопределили метод sayHi(), и теперь он работает по-разному для Human и Singer.</p><h3>Инкапсуляция</h3><p>Инкапсуляция включает в себя идею о том, что данные объекта не должны быть напрямую доступны. Нужно вызывать методы вместо прямого доступа к данным.</p><p>Инкапсуляция позволяет нам скрывать/показывать свойства функций.</p><figure><img src="https://media.tproger.ru/uploads/2023/08/de66ea65-44fc-4fb2-9f6c-01ee776f7396.jpg" alt="" /></figure><p>Рассмотрим знакомый нам класс Human().</p><figure><img src="https://media.tproger.ru/uploads/2023/08/80ea7986-256b-4739-8534-54b7eb36328f.jpg" alt="" /></figure><p>Мы решили, что поле lastName должно быть приватным, поскольку в JS нельзя объявлять приватные поля — существует условность, что перед приватным свойством/методом мы ставим “_”;</p><p>Таким образом, мы не можем получать/менять свойства, обращаясь к ним напрямую, для этого существуют геттеры и сеттеры, которые мы добавили в класс<b> </b>Human.</p><figure><img src="https://media.tproger.ru/uploads/2023/08/a00146e3-1894-4d1d-aa15-bfe72bfb1e75.jpg" alt="" /></figure><p>Теперь мы можем обращаться к методу класса как к свойству, без его вызова.</p><p>Как было сказано ранее, в JS мы не можем объявлять приватные поля, но можем сделать это в TypeScript.</p><h2>Четыре типа доступа к свойствам класса</h2><p>Существует четыре типа доступа к свойствам класса:</p><ul><li>public,</li><li>protected,</li><li>private,</li><li>readonly.</li></ul><h3>Public</h3><p>Если к свойствам и функциям классов не применяется модификатор, то такие свойства и функции расцениваются как определенные модификатором public.</p><p>В таком случае свойство/метод будут доступны при обращении извне данного класса.</p><h3>Private</h3><p>Если же к свойствам и методам применяется модификатор private, то к ним нельзя будет обратиться извне при создании объекта данного класса.</p><figure><img src="https://media.tproger.ru/uploads/2023/08/c859d430-e5c4-4cf5-bb48-4181bf3b7712.jpg" alt="" /></figure><p>В уже знакомом нам классе Human мы объявили, что поле firstName теперь — приватное поле, и при попытке обратиться к нему извне класса мы получаем ожидаемую ошибку.</p><h3>Protected</h3><p>Модификатор protected действует аналогично private, за исключением того, что члены, объявленные protected, могут быть доступны в подклассах.</p><figure><img src="https://media.tproger.ru/uploads/2023/08/cbecd55e-6f14-4bfa-91de-8367978534ef.jpg" alt="" /></figure><p>Теперь мы объявили firstName как protected, мы также не можем использовать это свойство извне класса Human, но мы можем использовать его в классах-наследниках.</p><h3>Readonly</h3><p>Мы можем делать свойства, доступными только для чтения, с помощью ключевого слова readonly. Такие свойства должны быть инициализированы при их объявлении или в конструкторе.</p><figure><img src="https://media.tproger.ru/uploads/2023/08/1d80a10e-9f82-4fdd-8206-5c67e5311a3d.jpg" alt="" /></figure><p>Мы объявили свойство birthPlace<b> </b>(место рождения) как readonly, вряд ли оно когда-либо изменится. И при попытке изменить это свойство мы получим ошибку.</p><p>Пожалуй, всё, что мы разобрали в этой статье, и есть база ООП.</p><h2>Подведем итоги вышесказанного</h2><ul><li>Объектно-ориентированное программирование подходит для решения множества задач.</li><li>Основной единицей ООП является объект, на нем все строится.</li><li>ООП основано на четырех принципах: абстракция, инкапсуляция, наследование и полиморфизм.</li><li>В JS есть свои особенности, например, при работе с приватными полями — решается через TypeScript.</li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы ООП: наследование в программировании</title>
      <link>https://tproger.ru/articles/osnovnye-principy-oop-nasledovanie-v-programmirovanii</link>
      <comments>https://tproger.ru/articles/osnovnye-principy-oop-nasledovanie-v-programmirovanii?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Марина Александровна]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/osnovnye-principy-oop-nasledovanie-v-programmirovanii</guid>
      <description><![CDATA[<p>О принципе наследования в ООП простыми словами. Объясняем механизм наследования ООП и преимущества метода на примере Java-кода.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/osnovnye-principy-oop-nasledovanie-v-programmirovanii">Основные принципы ООП: наследование в программировании</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 27 Jul 2023 06:34:13 GMT</pubDate>
      <content:encoded><![CDATA[<p>Принцип программирования наследование является одним из ключевых понятий в ООП. Он позволяет создавать иерархии классов, где один класс (подкласс) наследует свойства и методы другого класса (суперкласса). Это позволяет сокращать дублирование кода, упрощать структуру программы и создавать более логичные иерархии объектов.</p><h2>Преимущества принципа наследования</h2><p>Принцип наследования является одним из фундаментальных понятий объектно-ориентированного программирования и предоставляет несколько преимуществ, которые делают его важным и полезным инструментом при проектировании программных систем:</p><ol><li>Повторное использование кода. Наследование позволяет создавать иерархии классов, где общая функциональность реализуется в родительском классе, и все подклассы автоматически наследуют этот код. Это способствует повторному использованию кода, что уменьшает дублирование и облегчает его поддержку.</li><li>Расширяемость. Принцип наследования позволяет создавать новые классы, расширяющие функциональность существующих классов. Подклассы могут добавлять новые свойства и методы, а также переопределять поведение унаследованных методов. Это делает код более гибким и позволяет легко вносить изменения.</li><li>Упрощение кода. Использование наследования позволяет разбивать большие и сложные классы на более мелкие и управляемые части. Каждый подкласс специализируется на определенном аспекте функциональности, что упрощает понимание и поддержку кода.</li><li>Полиморфизм. Наследование поддерживает концепцию полиморфизма, которая позволяет обращаться к объектам подклассов через ссылки на родительские классы. Это облегчает обработку групп объектов с различными типами, что упрощает написание общего и универсального кода.</li><li><a href="https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii">Абстракция</a>. Наследование позволяет выделить общие характеристики объектов и создать абстрактные классы, которые определяют интерфейс для группы связанных классов. Абстрактные классы предоставляют общую сущность без необходимости определения всех деталей реализации.</li><li>Структурирование кода. Наследование помогает упорядочить классы в логические иерархии, что улучшает структуру программы. Каждый класс наследует функциональность от одного или нескольких родительских классов, что улучшает организацию кода и делает его более понятным и легко поддерживаемым.</li></ol><p>В целом, принцип наследования позволяет создавать более гибкие, модульные и расширяемые программы, что упрощает разработку и сопровождение сложных проектов. Он способствует повторному использованию кода и помогает соблюдать принципы DRY (Don’t Repeat Yourself) и <a href="https://tproger.ru/articles/principy-solid-python/">SOLID</a>, что в свою очередь способствует созданию качественного и эффективного кода.</p><h2>Пример наследования в ООП</h2><p>Рассмотрим пример кода на Java:</p><p>В этом примере у нас есть родительский класс Animal, который содержит общие свойства и методы для всех животных. Затем есть два подкласса Dog и Cat, которые наследуют свойства и методы от класса Animal. Каждый подкласс также имеет свои собственные уникальные свойства и методы. Обратите внимание на использование ключевого слова extends при объявлении подклассов.</p><p>Принцип наследования позволяет нам использовать общие характеристики и функциональность из родительского класса и при этом иметь возможность расширять или переопределять их в подклассах для создания более специфичных типов объектов.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы ООП: инкапсуляция в программировании</title>
      <link>https://tproger.ru/articles/osnovnye-principy-oop-inkapsulyaciya-v-programmirovanii</link>
      <comments>https://tproger.ru/articles/osnovnye-principy-oop-inkapsulyaciya-v-programmirovanii?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Марина Александровна]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/osnovnye-principy-oop-inkapsulyaciya-v-programmirovanii</guid>
      <description><![CDATA[<p>Основные принципы ООП включают в себя инкапсуляцию. Рассмотрим главные преимущества принципа и пример инкапсуляции данных.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/osnovnye-principy-oop-inkapsulyaciya-v-programmirovanii">Основные принципы ООП: инкапсуляция в программировании</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 23 Jul 2023 11:22:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Принцип инкапсуляции является одним из основных принципов объектно-ориентированного программирования и подразумевает скрытие внутренней реализации объекта от внешнего мира. Это означает, что данные и методы, которые оперируют с этими данными, объединяются в единое целое, называемое классом.</p><p>Внешний код может взаимодействовать с объектом только через определенные интерфейсы, предоставленные классом, не имея прямого доступа к его внутренним данным.</p><h2>Преимущества принципа инкапсуляции</h2><p>Инкапсуляция данных имеет множество практических применений в разработке программного обеспечения. Вот несколько примеров использования <a href="https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D0%BA%D0%B0%D0%BF%D1%81%D1%83%D0%BB%D1%8F%D1%86%D0%B8%D1%8F_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)">инкапсуляции</a> на практике:</p><ol><li>Безопасность данных. Инкапсуляция позволяет защитить данные объекта от некорректного доступа и изменения извне. Класс может предоставлять только определенные методы (геттеры и сеттеры) для доступа к данным, которые проверяют правильность операций и обеспечивают безопасность данных.</li><li>Сокрытие реализации. Когда класс инкапсулирует свою реализацию, то изменения внутри класса не отражаются на внешнем коде. Это позволяет менять реализацию объекта, не нарушая функциональность клиентского кода, что облегчает поддержку и эволюцию программы.</li><li>Упрощение интерфейса. Инкапсуляция позволяет предоставить простой и понятный интерфейс для работы с объектами. Клиентский код взаимодействует только с публичными методами класса, не требуя знания деталей его внутренней реализации.</li><li>Модульность. Инкапсуляция помогает создавать модульные системы, где каждый класс представляет собой отдельный модуль со своими данными и методами. Модули могут взаимодействовать друг с другом через публичные интерфейсы, что способствует повышению читаемости и понимаемости кода.</li><li>Принцип единственной ответственности. Инкапсуляция способствует соблюдению принципа единственной ответственности (Single Responsibility Principle). Класс, инкапсулирующий определенные данные и операции с ними, должен отвечать только за эти данные и их обработку.</li><li>Контроль доступа. Инкапсуляция ООП позволяет устанавливать уровни доступа к данным и методам класса. Таким образом, некоторые данные и функциональность могут быть скрыты от других классов или пакетов, что способствует защите и контролю кода.</li></ol><p>Применение процесса инкапсуляции в практике программирования помогает создавать более структурированный, безопасный и расширяемый код. Она способствует лучшему управлению сложностью программы и облегчает сотрудничество между разработчиками при разработке больших проектов.</p><h2>Пример инкапсуляции в ООП</h2><p>Рассмотрим инкапсуляцию на простом примере:</p><p>В приведенном примере класс BankAccount инкапсулирует данные о банковском счете (accountNumber и balance) и предоставляет интерфейс для работы с ними. Поля accountNumber и balance объявлены как private, что делает их доступными только внутри класса.</p><p>Для доступа к данным счета извне класса используются публичные методы (геттеры и сеттеры). В данном случае, методы getAccountNumber() и getBalance() позволяют получить номер счета и баланс соответственно. А методы deposit() и withdraw() предоставляют возможность внести или снять деньги со счета.</p><p>Таким образом, внешний код может использовать объект BankAccount, зная только публичные методы, а не внутренние детали реализации класса. Это позволяет безопасно изменять внутреннюю реализацию класса BankAccount, не затрагивая код, который с ним взаимодействует.</p><p>Читайте также о <a href="https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii/">принципе абстракции в ООП</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы ООП: абстракция в программировании</title>
      <link>https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii</link>
      <comments>https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Марина Александровна]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii</guid>
      <description><![CDATA[<p>Основные принципы ООП включают в себя абстракцию: что это такое, когда и для чего используется, а также наглядный пример абстракции в ООП.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii">Основные принципы ООП: абстракция в программировании</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 19 May 2023 08:15:05 GMT</pubDate>
      <content:encoded><![CDATA[<p>Абстракция — один из принципов ООП в программировании. По своей сути это процесс выделения общих характеристик и функциональности объектов или системы, игнорируя детали реализации.</p><h2>Для чего нужна абстракция в программировании?</h2><p>Абстракция позволяет разрабатывать программы на различных языках программирования, скрывая сложность и детали нижележащего кода. Это делается для упрощения сложных систем и концепций, чтобы разработчики могли фокусироваться на основных аспектах проблемы и легче понимали код.</p><h2>Преимущества абстракции в ООП</h2><p>В объектно-ориентированном программировании абстракция играет важную роль. Она позволяет создавать абстрактные классы и интерфейсы, которые определяют общие свойства и методы, не зависящие от конкретной реализации. Преимущества абстракции ООП включают:</p><ol><li>Упрощение сложности: абстракция в программировании позволяет скрыть детали реализации и сосредоточиться на ключевых аспектах системы. Это помогает упростить понимание и поддержку кода.</li><li>Модульность: возможность разбить систему на модули или классы, которые могут работать независимо друг от друга. Это способствует повторному использованию кода и улучшает масштабируемость проекта.</li><li>Повышение безопасности: абстракция позволяет скрыть некоторые детали реализации, что делает код более безопасным и защищенным. Внешние компоненты не имеют прямого доступа к внутренним деталям объекта или системы.</li></ol><h2>Пример абстракции в ООП</h2><p>В качестве примера реализуем абстрактный класс «Фигура» и его наследников на языке Java:</p><p>В этом примере абстрактный класс Shape содержит общие свойства и методы для всех фигур. У него есть абстрактные методы getArea() и getPerimeter(), которые должны быть реализованы в наследниках. Классы Circle и Rectangle наследуют абстрактный класс Shape и реализуют абстрактные методы в соответствии с логикой для каждой фигуры.</p><p>В методе main() создаются объекты Circle и Rectangle, которые вызывают метод printInfo(), чтобы вывести информацию о каждой фигуре, включая цвет, площадь и периметр.</p><p>Пример показывает, как абстракция в ООП позволяет определить общий интерфейс (абстрактный класс) и реализовать его в конкретных классах, обеспечивая гибкость и повторное использование кода.</p><p>Также держите полезную <a href="https://tproger.ru/translations/oop-principles-cheatsheet/">шпаргалку по принципам ООП</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Объектно-ориентированное программирование: инструмент, требующий опыта</title>
      <link>https://tproger.ru/articles/obektno-orientirovannoe-programmirovanie</link>
      <comments>https://tproger.ru/articles/obektno-orientirovannoe-programmirovanie?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Анна Джанибекова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/obektno-orientirovannoe-programmirovanie</guid>
      <description><![CDATA[<p>В наши дни инженеры-программисты глубоко осведомлены о применении ООП. Но гораздо реже встречается ответ на вопрос «зачем».</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/obektno-orientirovannoe-programmirovanie">Объектно-ориентированное программирование: инструмент, требующий опыта</a>»</p>]]></description>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Гостевая публикация]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 08 Apr 2022 13:13:43 GMT</pubDate>
      <content:encoded><![CDATA[<p>В наши дни практически все инженеры-программисты достаточно глубоко осведомлены о принципах и подходах к применению объектно-ориентированного программирования (ООП). Абстракция, инкапсуляция, наследование, полиморфизм, S.O.L.I.D. для них — не заклинание, вызывающее дождь, а скорее основа повседневной деятельности. Эти методологические термины многократно и подробно объяснены в Сети. Без них сегодня не обходится практически ни одно собеседование. Разбуди девелопера среди ночи и спроси про любой из них, и он скорее всего не собьется в рассказе, так и не проснувшись до конца. Но, как часто случается с концепциями, гораздо реже встречается ответ на вопрос «зачем».</p><h2>Предпосылки популярности ООП</h2><p>Разработка программного обеспечения — дорогое удовольствие. Процесс этот должен быть максимально эффективным на всех этапах, от постановки задач до сопровождения продукта, иначе он становится экономически невыгодным. Он, этот процесс, не прост и чреват дорогостоящими ошибками, хотя и сулит выход бизнеса заказчика на новый уровень. Именно здесь кроется ответ на вопрос «зачем» в отношении ООП. Для эффективного (читай — экономически выгодного) преодоления сложностей разработки ПО.</p><p>Написание программного кода — это всегда решение той или иной поставленной перед программистом задачи, которую он может реализовать любыми доступными ему методами. Но зачастую приходится решать шаблонные задачи, практически не отличающиеся друг от друга. Или сталкиваться с тем, что задача распадается на отдельные, уже где-то встречавшиеся и решенные. ООП помогает систематизировать такие решения и избегать повторов.</p><p>Хорошим примером эффективного ответа ООП в сочетании с дженериками на целый класс типовых проблем могут служить коллекции java. Поверьте, хлеб программиста без них был бы горек… Многократное использование кода (по сути — многократное использование решений) — залог управляемости, вытекающей из постоянства и предсказуемости знакомых компонентов.</p><p>Жизнь программного проекта не линейна. Иногда проектные изменения возникают на позднем этапе, когда много чистого, отлаженного и, что важно, оплаченного кода уже написано. И вот необходимо обеспечить поддержку новых требований, сохраняя решения в актуальном, рабочем состоянии. ООП, если его «правильно готовить», позволяет предвосхитить подобные проблемы. В правильно спроектированных и реализованных системах даже драматические на первый взгляд изменения в требованиях адаптируются порой посредством настройки конфигурации. Ну или другой «малой кровью».</p><p>Но чаще бывает так, что когда кода много, и он пишется многими людьми, которые приходят и уходят, то без должной дисциплины на уровне самого кода с какого-то момента команда начинает тратить неприемлемо много своего дорогостоящего рабочего времени на адаптацию накопившейся кодовой массы к самым небольшим изменениям. ООП здесь существенно выручает, ибо эта методология сама по себе поощряет разделение задач и решений по функциональности, использование правила «необходимой достаточности», когда большая задача и связанные с ней данные рационально делится на меньшие подзадачи и те в свою очередь находят свои максимально изолированные решения. Связь же между ними, их взаимодействие, оказываются выражены в четких контрактах.</p><p>Опыт применения ООП породил интереснейший самостоятельный феномен — шаблоны проектирования. Даже если вы не знакомы с каждым членом этого многочисленного семейства, но обладаете здравым смыслом и поняли «зачем» ООП вообще, то наверняка использовали их. Многие из них естественным образом вытекают из прямых требований самого ООП. На самом деле шаблоны проектирования — это особый вид многократного использования, только в данном случае не кода, а подходов к решению. Описывать шаблоны проектирования и реализовывать их в терминах ООП намного проще, чем, скажем, в терминах процедурного программирования. Хотя реализовать принятое на их основе решение можно на любом языке.</p><p>ООП помноженное на грамотное использование шаблонов создает основу для «промышленной» разработки ПО. Это можно сравнить с принципами разработки современных, скажем, автомобилей или компьютеров: чтобы спроектировать новую модель, не нужно заново создавать для нее базовые компоненты — используются уже готовые. Новая модель — она насколько новая? И все-таки… Из готовых блоков с добавлением щепотки инноваций возникает нечто…</p><p>Вскоре стало понятно, что шаблоны проектирования существуют и для систем более высокого уровня. Например, в организации существует большое количество приложений для разных функциональных подразделений: логистики, бухгалтерии и так далее. Для каждого из них существуют свои приложения, но они должны каким-то образом связываться между собой: возникает запрос на некие шаблоны проектирования этого взаимодействия. Переходя с уровня application на уровень enterprise мы начинаем мыслить уже не категориями классов и интерфейсов, а категориями модулей и интеграционных каналов.</p><p>Кажется, на этом уровне мы уже утрачиваем связь с объектно-ориентированным программированием в том виде, как его задумал Создатель… Здесь уже совсем другие игроки: (микро)сервисы, сторонние АПИ, каналы интеграции, распределенные кэши, протоколы… Но если мы поищем в Сети практическое определение микросервиса, то оно удивительным образом будет напоминать классическое определение для объекта, принятое в ООП.</p><h2>Простые примеры</h2><p>Итак, OOП нам строить и жить помогает, воспитывая практичные и эффективные навыки реализации сложных проектов. По мне так и сам объектно-ориентированный код — штука приятная. Не знаю как вы, а я, когда пользуюсь навигацией по коду в моей любимой среде разработки, порой задумываюсь, насколько ее, навигации, удобство связано с тем, что это код на объектно-ориентированном языке. Думаю, напрямую. Возможно, авторы ООП и не задумывались о таком приятном «побочном эффекте».</p><p>Возьмем простой пример практического использования ООП. Допустим, требуется оперировать такой штукой как ИНН. Мы знаем, что ИНН бывает у физических и юридических лиц, но они отличаются количеством цифр в значении и механизмом верификации (да-да, у них по-разному вычисляется контрольная сумма). Значения приходят к нам в виде строк с непредсказуемым содержимым, но дальше в систему должны проникать только верифицированные значения. При этом конкретный интерес к тому, чей это ИНН — «юрика» или «физика» у нас отложен. До поры нам достаточно просто быть уверенными, что это «правильный» ИНН.</p><figure><img src="https://media.tproger.ru/uploads/2022/04/code1_part1.png" alt="" /></figure><figure><img src="https://media.tproger.ru/uploads/2022/04/code1_part2.png" alt="" /></figure><p><br /></p><p>В данном случае мы, сами того не замечая, применили аж несколько шаблонов проектирования. Даже не буду уточнять, каких… Но в итоге имеем две корректные анонимные реализации, а прочие запрещены, ибо в природе другого варианта ИНН пока не существует.</p><p>Другой пример: у нас есть набор сервисов для коммуникаций (почтовый, SMS и так далее) с одинаковым интерфейсом и схожим функционалом. Для отправки сообщений через них мы можем использовать этот единый интерфейс, вызывая его метод «send» у переданного нам экземпляра класса, реализующего этот интерфейс. Важно, что при этом возможность переключения канала отправки — например, с почты, на SMS — реализуется без изменения кода в точке отправки. Точка отправки не имеет ровным счетом никакого представления, т.к. для нее это просто какой-то носитель метода «send». Короче, получается что-то вроде</p><p>Если придерживаться этого каркаса, то, прописав классы исключений, конверторы, верификаторы и логирование, мы, надеюсь, заметим, что функциональность рассылки сообщений распалась не только на подзадачи, но и расслоилась на уровни, каждый из которых прост в понимании и в значительной степени изолирован. А это залог управляемости, тестируемости, стабильности. Что в данном случае мы использовали — инкапсуляцию, наследование, полиморфизм — об этом как-то уже не думаешь.</p><p>Преимущества ООП, как и любого инструмента программирования, проявляются только при правильном владении этим инструментом. Недостаточное изучение имеющихся в арсенале программиста средств может привести к «изобретению велосипеда», злоупотреблению абстракцией и усложнению проекта за счет каких-то лишних решений. Ну и здравый смысл… Не пренебрегаем — запрягаем.</p><p>Например, если необходимо представить ФИО в виде объекта со свойствами: «фамилия, имя и отчество», которые должны писаться с заглавной буквы, то создание трех отдельных реализаций интерфейса NamePart для каждого из них, хоть и возможно, но, как говорила моя бабушка, «декомпозируя задачу и выделяя доменные сущности, не сходи с ума, внучек». И то правда… Ибо на самом деле они по отдельности и не живут — фамилии с именами.</p><h2>Компетенции программиста</h2><p>Мы в IT_One используем язык Java. По крайней мере на том проекте, где я сейчас Java — наше все. И не только потому, что это «с момента зачатия» объектно-ориентированный язык. Java — это давно уже больше, чем язык. Это платформа, хотя само это слово мало что объясняет.</p><p>Дело в том, что профессионально разрабатывая современное ПО, инженер оперирует по большей части даже уже не пакетами классов, которых кстати, в Java великое множество, а скорее реализациями обширных спецификаций и фреймворками (Jakarta, Spring, Micronaut, Hibernate и др.), набором библиотек и инструментов, которые создают каркас приложения, задают тон всей разработке. Эти фреймворки — гигантское количество кода и модулей, воплощающих принципы и лучшие практики ООП.</p><p>При этом базовые знания о языке и принципы ООП современный developer использует автоматически, с мастерством — как водитель, выжимающий педаль сцепления и перемещающий рычаг переключения передач.</p><p>Сегодня в уже готовые, заранее созданные компоненты программист вносит специфическую для конкретной задачи или проекта функциональность, создает композиции из имеющихся компонентов, разрабатывает свои, следуя диктуемым самим фреймворком правилам. Эта «увлекательная рутина» занимает основное время современного разработчика.</p><p>Казалось бы, простор для творчества и самовыражения сужается пропорционально громадности фреймворка. Но все совсем не так грустно. Иногда (даже часто) достигнутый результат — «правильно» работающая система — доставляет гигантское удовольствие всей команде и каждому участнику «заплыва». Поверьте, расползающийся, кишащий повторами, плохо читаемый, запутанный, но «авторский» код, ставший таковым просто из-за игнорирования современных стандартов разработки, даже если «взлетит на проде», останется больше проблемой, чем решением.</p><h2>Итого</h2><p>Говорят, что объектно-ориентированное программирование сложнее в освоении и требует от программиста несколько больших, чем обычно, компетенций, помимо понимания базовых концепций.</p><p>Это мнение справедливо. Ибо то, зачем возникло ООП, уже с нами. Это особый пестрый мир взаимодействующих решений, новомодных или консервативных, тяжеловесных или облегченных, специфических или общего назначения.</p><p>Но всегда управляемых, ясных, выработанных с применением нескольких простых и очевидных до банальности принципах.</p><p>И этот мир сегодня на подъеме.</p>]]></content:encoded>
    </item>
    <item>
      <title>Плюсы, минусы и перспективы ООП в современной разработке</title>
      <link>https://tproger.ru/articles/pljusy-minusy-i-perspektivy-oop-v-sovremennoj-razrabotke</link>
      <comments>https://tproger.ru/articles/pljusy-minusy-i-perspektivy-oop-v-sovremennoj-razrabotke?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Yulia Sukhovey]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/pljusy-minusy-i-perspektivy-oop-v-sovremennoj-razrabotke</guid>
      <description><![CDATA[<p>Давайте применим концепцию MVP к рассмотрению объектно-ориентированного, функционального и прототипного программирования.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/pljusy-minusy-i-perspektivy-oop-v-sovremennoj-razrabotke">Плюсы, минусы и перспективы ООП в современной разработке</a>»</p>]]></description>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Гостевая публикация]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 15 Oct 2021 18:06:54 GMT</pubDate>
      <content:encoded><![CDATA[<p>Давайте применим концепцию MVP к рассмотрению объектно-ориентированного программирования, а чтобы не быть субъективными, также взглянем на функциональное и прототипное программирование. Прежде всего, интересно, что разработано на них. А также какие перспективы ждут разработчиков — то есть количество открытых вакансий на рынке труда.</p><h2>Прототипные языки программирования</h2><p>Императивная парадигма, где реализация программного продукта осуществляется за счет оперирования иерархиями объектов. Это полезно для компактных программ с небольшим объёмом кода.</p><p>Самая известная реализация прототипной спецификации ECMAScript — язык JavaScript. Традиционная его ниша — фронтенд. Но с недавних пор ведётся также активная разработка на этом языке и бэкенд-решений.</p><p>Вот как выглядит поиск по вакансиям программистов на прототипных языках:</p><ul><li>JavaScript — 16 603;</li><li>Node.js — 1 403;</li><li>Slate — 9;</li><li>Agora — 3;</li><li>Cecil — 0.</li></ul><h2>Функциональные языки программирования</h2><p>Декларативная парадигма программирования, которая строится на функциях, что удобно для параллельной и распределенной разработки. Программам, написанным с использованием данной парадигмы, свойственны такие свойства, как высокая степень параллелизации вычислений, повышенные требования к производительности и надежности.</p><p>Haskell применяется в финансовом программировании, при анализе рисков, в системах поддержки принятия решений. Erlang применяется в телекоммуникациях.</p><p>Смотрим вакансии (кстати, уровень зарплаты по ним выше среднего):</p><ul><li>Haskell — 71;</li><li>Erlang — 70;</li><li>Lisp — 10.</li></ul><h2>Объектно-ориентированное программирование</h2><p>Императивная парадигма, где реализация программного продукта осуществляется за счёт оперирования иерархиями классов и объектов. Базируется на таких подходах, как полиморфизм, инкапсуляция, абстракция и наследование.</p><p>Всё построено на повторном использовании кода, что ускоряет разработку. Кроме того, найти программистов на ООП несложно, потому что это очень развитый рынок — и в продуктовом, и в HR-смыслах. Это хорошо видно по количеству вакансий:</p><ul><li>Java — 12 346;</li><li>PHP — 7 159;</li><li>C++ — 5 795;</li><li>Kotlin — 3 049.</li></ul><p>ООП используется при написании операционных систем, СУБД, компиляторов, драйверов, множества прикладных программ. Например, достаточно сказать, что почти все известные браузеры, Microsoft Office, Adobe Photoshop и Illustrator — продукты объектно-ориентированного программирования.</p><p>Похоже, его рано списывать со счетов. Однако многие критикуют ООП. Какие претензии предъявляют очевидному лидеру, чем он не угодил?</p><h2>Критика объектно-ориентированного программирования</h2><p>ООП не раз подвергалось критике. Одно из самых ярких обвинений прозвучало от британского программиста — Джо Армстронга.</p><p>Проблема с объектно-ориентированными языками заключается в том, что у них есть вся эта неявная среда, которую они носят с собой. Вы хотели банан, но получили гориллу, держащую банан и все джунгли.</p><p>Это во многом справедливо. Помимо недостаточно качественной поддержки параллельных и распределенных систем, ООП отличается относительно низким качеством конечного продукта.</p><p>В процессе трансляции объектно-ориентированных программ в исполняемый код центрального процессора возникает ряд неоптимальностей по использованию памяти и вычислительного времени процессорных ядер.</p><p>ООП предоставляет вам множество способов замедлить работу ваших программ.</p><p>А небезызвестный Линус Торвальдс часто критиковал ООП и С++ в частности, упоминая в том числе отсутствие ограничений. Речь о том, что большое количество инструментов и методов позволяет добиваться функционально одинаковых реализаций множеством различных способов. Это можно было бы считать преимуществом, но появляется риск ошибок, обнаружить которые очень сложно. Наследование объектов может привести к тому, что баг «вылезет» в неожиданном месте, далеко от исходной неточности в описании «родителя».</p><p>Как же получилось, что такой уязвимый, избыточный, громоздкий подход к программированию стал основным в глобальном масштабе и до сих пор сохранил свои позиции?</p><h2>Преимущества объектно-ориентированного программирования</h2><p>Первые программы на языках программирования высокого уровня, по сути, не были структурированы, и это не вызывало проблем, потому что объёмы кода были, по современным меркам, ничтожны. Кроме того, тогда ещё не существовало репозитариев, не было интернета, и тиражирование исходных кодов программных продуктов было крайне затруднительным.</p><p>По мере того, как наперегонки развивались Hardware и Software, объёмы исходных кодов в программах начали стремительно расти. Теперь для его поддержки уже требовалась определенная архитектура. Стали применяться различные подходы к оформлению исходных кодов, и в конечном счёте появились парадигмы программирования. ООП выделялось сразу несколькими критично важными отличиями:</p><ul><li>Удобное разделение задач по разработке между разными программистами, отделами, компаниями. Модульность за счёт инкапсуляции, возможно, стала решающей причиной такого широкого распространения ООП.</li><li>Потенциал для масштабирования. Можно добавлять новые компоненты, расширяя уже написанное программное обеспечение — и всё будет работать.</li><li>Обработка разнородных структур данных. Благодаря полиморфизму, софт на ООП можно гибко модифицировать, дополнять, «апдейтить». Это незаменимое свойство для коммерческих продуктов, а ведь именно они определяют доходы, бюджеты и создают ресурсную базу для новых и новых проектов.</li></ul><p>Повторяемость кода ООП привела к созданию библиотек классов. Можно тратить всё меньше времени на получение уже полученных ранее результатов — причём даже не очень важно, кем именно и на каких проектах были сделаны прежние разработки. Так и начал формироваться парадокс банана: с одной стороны — упрощение разработки за счёт тиражирования уже реализованных классов. С другой — неоптимальное использование аппаратных ресурсов.</p><p>В ходе развития программного продукта могут потребоваться расширения и дополнения, уже реализованные в подключенных библиотеках, и тогда остаётся только их задействовать. Нечто похожее сейчас используется во многих коммерческих продуктах, причём не только софтверных.</p><p>Сама идея «разработки про запас» довольно удобна с точки зрения продаж. Как и выбор высокого темпа вместо перфекционизма в качестве кода. Лучше продать сегодня и сдать проект завтра, чем растянуть всё на годы и вылететь с рынка.</p><p>При всех своих недостатках объектно-ориентированное программирование позволяет быстрее, экономичнее и гораздо удобнее в плане управления процессом распределённой разработки получать работающий код. Да, возможно, в нём есть значительные неоптимальности, возможно их даже много. Но зато проект уже работает, по крайней мере, в виде прототипа. Этого достаточно для презентаций, получения первых клиентов и внедрений.</p><p>Всё равно первоначальные маркетинговые, интерфейсные и многие другие идеи придётся ещё неоднократно дорабатывать, а возможно, и менять. С точки зрения компании и особенно стартапа, выгоднее двигаться быстрее.</p><h2>Резюме</h2><p>В  нулевых годах начали массово распространяться многоядерные и многопроцессорные системы. Возникла потребность в распределенных вычислениях, а чуть позже в вычислениях на графических процессорах. Оказалось, что ООП справляется с такими задачами значительно хуже, чем функциональные программы. Даже исходя из одного этого фактора, можно усомниться в бесконечном доминировании ООП.</p><p>Конечно, пропорции в разработке будут меняться. Это уже происходит. Кроме того, рано или поздно появятся принципиально другие, новые подходы — и они могут оказаться недостижимо более производительными, особенно на модернизированном железе.</p><p>Тем не менее, пока что ООП остается надёжным, удобным инструментом. Похоже, в ближайшие годы ничего не предвещает серьезных подвижек, так что можно смело использовать объектно-ориентированное программирование и в качестве личного карьерного плана, и для запуска проектов.</p>]]></content:encoded>
    </item>
    <item>
      <title>Паттерн ООП «Хранитель»</title>
      <link>https://tproger.ru/articles/pattern-oop-hranitel</link>
      <comments>https://tproger.ru/articles/pattern-oop-hranitel?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Мария Кривоченко]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/pattern-oop-hranitel</guid>
      <description><![CDATA[<p>Обсудим паттерн ООП проектирования Хранитель на примере текстового редактора, который меняет форматирование текста и других элементов</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/pattern-oop-hranitel">Паттерн ООП «Хранитель»</a>»</p>]]></description>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Паттерны проектирования]]></category>
      <category><![CDATA[Гостевая публикация]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 11 May 2021 11:20:26 GMT</pubDate>
      <content:encoded><![CDATA[<p>«Хранитель» (Memento), также известный как Снимок – поведенческий паттерн проектирования. Он позволяет определять, сохранять, а также восстанавливать предыдущие состояния объектов без нарушения принципа инкапсуляции.</p><p>Самый простой и наглядный пример использования этого паттерна – некий текстовый редактор, который позволяет изменять форматирование текста и других элементов. Но при этом пользователь может эти изменения отменить. Другой пример – восстановление состояния персонажей в игре на контрольных точках.</p><p>Формально, в виде диаграмм структуру паттерна можно представить так:</p><p>Участники процесса:</p><ul><li>Memento («хранитель») – хранитель, сохраняет состояние объекта Originator;</li><li>Originator («создатель») – создает экземпляр объекта хранителя. Имеет полный доступ к Memento;</li><li>Caretaker («опекун») – производит сохранения состояний.</li></ul><p>Теперь рассмотрим очень упрощённый пример текстового редактора. У него будет всего пять команд:</p><ul><li>добавление нового блока текста;</li><li>установка стиля текста;</li><li>вывод всего текста на экран;</li><li>сохранение текущего состояния документа;</li><li>отмена последнего действия по редактированию документа.</li></ul><p>Для начала создадим класс нашего документа – класс Doc. Он будет содержать в себе два параметра – текст и стиль, которые пользователь может изменить с помощью соответствующих методов – AddBlock(string text) и SetStyle(int style). Выводить содержимое будем через метод Print().</p><p>Далее нам нужно создать класс для хранения состояния документа – DocMemento.</p><p>В данном случае это своего рода контейнер-копия сохранённого состояния объекта Doc. Мы передаём не копию экземпляра документа, а только его состояние со значимыми параметрами.</p><p>Теперь снова вернёмся к классу Doc и добавим два метода: для сохранения в объект-memento и восстановления состояния из объекта-memento:</p><p>Создадим класс, который будет в себе содержать историю изменений документа – EditorHistory. Вся история состояний будет храниться в стеке, который будет скрыт от пользователя для доступа напрямую.</p><p>Всё готово, теперь можно создать класс редактора и наполнить пользовательскими действиями:</p><p>Для примера мы сделали сохранение состояния после ввода блока текста «Привет, мир!» и смены параметра стиля текста. Сохранили в объекте history, снова изменили документ и вернули прежнее состояние. Каждое изменение сопроводили выводом всего документа на экран, то есть в консоль.</p><p>Если сравнивать с представленной в самом начале схемой, то в роли Originator у нас выступает Doc, Memento – DocMemento, а в роли Caretaker – EditorHistory. Документу доступны все поля, поэтому именно он делает снимок. А из истории берёт состояние для восстановления.</p><p>Данный пример слишком простой. Добавляя новые структуры и объекты в наш редактор, мы будем усложнять состояние документа (добавятся значения для разных текстовых блоков, страниц и абзацев, геометрические объекты, рисунки и тому подобное). Поэтому для более удобного представления состояния документа нам придётся использовать отдельные классы контейнеров данных, которые будут содержать множество полей. Таким образом, в более сложных программах для хранения состояний может потребоваться много памяти, если снимков будет много – это, пожалуй, основной недостаток паттерна.</p><p>Есть и чуть более сложные вариации реализации данного паттерна – создавая пустой промежуточный интерфейс или же более широкий вариант, с возможностью иметь множество видов создателей и снимков. Последний, например, позволяет полностью исключить доступ к состоянию создателей и снимков, но при этом сам опекун становится независимым от создателей.</p><p>Очень часто паттерн «Хранитель» совместно используется с паттерном «Команда» (как раз для выполнения команд «Сохранить» и «Восстановить»).</p><p>Итого, «Хранитель» позволяет нам передавать сохраняемые состояния объекту, но не передавать ему управление самим сохраняемым объектом, сохраняя инкапсуляцию.</p>]]></content:encoded>
    </item>
    <item>
      <title>Стоит прочитать: обзор книги Бретта Маклафлина «Объектно-ориентированный анализ и проектирование»</title>
      <link>https://tproger.ru/books/obzor-knigi-bretta-maklaflina-obektno-orientirovannyj-analiz-i-proektirovanie</link>
      <comments>https://tproger.ru/books/obzor-knigi-bretta-maklaflina-obektno-orientirovannyj-analiz-i-proektirovanie?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/books/obzor-knigi-bretta-maklaflina-obektno-orientirovannyj-analiz-i-proektirovanie</guid>
      <description><![CDATA[<p>Издание серии Head First о том, как устроены анализ и проектирование объектно-ориентированных программ, полезно разработчикам junior и middle.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/books/obzor-knigi-bretta-maklaflina-obektno-orientirovannyj-analiz-i-proektirovanie">Стоит прочитать: обзор книги Бретта Маклафлина «Объектно-ориентированный анализ и проектирование»</a>»</p>]]></description>
      <category><![CDATA[Архитектура ПО]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Стоит прочитать]]></category>
      <category><![CDATA[Книги]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 17 Dec 2020 12:45:45 GMT</pubDate>
      <content:encoded><![CDATA[<p>Пожалуй, одной из самых важных книг в моей карьере я могу назвать «Объектно-ориентированный анализ и проектирование» из серии Head First, авторы Бретт Маклафлин, Гэри Поллайс и Дэйв Уэст. Вообще Head First очень полезная серия книг для разработчиков уровня junior/middle, она способна подтолкнуть их по карьерной лестнице в мир бизнеса, архитектурных решений и большой ответственности в рамках всего продукта. Но для начала хочу остановиться на небольшой части этого мира, а именно – на приложении.</p><h2>С чего начинается приложение?</h2><p>Авторы рассказывают о том, что приложение начинается с потребностей заказчика, кем бы он ни был представлен. Владелец банковской карты, геймер, врач, официант — у всех них есть боли и потребности. В книге объясняется, почему важно правильно собрать требования, как сильно это влияет на проектирование приложения и что будет, если этого не сделать (спойлер – ничего хорошего:). Изучать этот вопрос вы начнете с приложения для одного заказчика, владельца гитарного магазина, которому нужно сделать систему учета своих гитар, и ваша цель – помочь ему в этом. Когда решите эту задачку, перейдете к более сложным примерам, которые при этом вполне понятные и подробно описаны. Кстати, хочу обратить внимание на подачу материала: он простой, язык близок к разговорному, а еще в книге много иллюстраций и схем, и это здорово помогает восприятию. В некоторых упражнениях книгу предлагают использовать как рабочую тетрадь, что тоже удобно (если только вы не читаете ее в электронном виде).</p><h2>Про требования и их изменчивость</h2><p>Авторы «Объектно-ориентированного анализа и проектирования» идут вместе с читателем по всем этапам жизненного цикла – от сбора требований заказчиков до тестирования готового решения.</p><p>Чуть подробнее расскажу про первый из этих этапов. «Как собирать требования? Что предусмотреть? А какие части могут меняться?» — эти вопросы программист задает себе не всегда, а стоило бы. Потому что от грамотного и подробного сбора требований зависит качество результата и удовлетворенность заказчика. Книга акцентирует внимание на этих вещах, и показывает, как заложить гибкость и изменчивость в код, чтобы доработки не заставали вас врасплох.</p><p>Как известно из практики и другой хорошей книги, «Совершенный код» Стива Макконелла, самые дорогие ошибки случаются из-за некорректных требований или из-за недосмотра потенциальных изменений. В нашем мире, разумеется, нельзя всё предусмотреть, но хотя бы попытаться — точно стоит!</p><h2>А где же разработка?</h2><p>Наверное, я вас уже напугал, что в книге не будет ни строчки кода. Но нет, в ней приведено очень много примеров и хорошего, и плохого кода, и советов как отличить один от другого. Плюс в книге много практических заданий на весь изученный материал – в каждой главе есть задачки, на которых можно применить свежие знания. А в последней четверти книги нас ждет большой проект, который предстоит сделать полностью самостоятельно – от сбора требований и до красиво структурированного кода.</p><p>Кстати, о структурированном коде. В книге подробно изложены важные моменты:</p><ul><li>основные принципы ООП — даже начинающий программист слышал про инкапсуляцию, наследование и полиморфизм. Тут вы закрепите свои знания, и поймёте, что инкапсуляция — это не только про область видимости;</li><li>принцип DRY — Don’t repeat yourself, он расскажет, как избежать повторения кода и инкапсулировать логику правильно;</li><li>принцип KISS — keep it simple stupid, здесь пойдет речь о том, почему не стоит строить слишком гибкие приложения, пытаясь предвидеть требования;</li><li>принцип YAGNI — You ain’t gonna need it, это история о том, как не писать лишний код, пытаясь играть в экстрасенса;</li><li>паттерны проектирования — важная составляющая, которая поможет вам использовать мудрость других разработчиков (:</li></ul><p>Все это показано на примерах из реальной жизни. Замечу, что с этой информацией вы, скорее всего, столкнетесь на собеседовании, что делает ее еще более ценной.</p><h2>Послесловие</h2><p>Мир программирования довольно обширен и описать его в одной книге, даже самой подробной, довольно сложно. Тем не менее, авторам удалось уложить в нее много важных моментов, которые однозначно пригодятся на практике программистам разного уровня. Кому-то будут полезны паттерны и принципы чистого программирования, кому-то пригодится анализ требований, кому-то — проектирование в UML.</p><p>Но что точно пригодится каждому, — понимание, что программы не бывает без заказчика.</p>]]></content:encoded>
    </item>
    <item>
      <title>Объектно-ориентированное программирование простым языком — объясняют эксперты</title>
      <link>https://tproger.ru/experts/oop-in-simple-words</link>
      <comments>https://tproger.ru/experts/oop-in-simple-words?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Никита Прияцелюк]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/experts/oop-in-simple-words</guid>
      <description><![CDATA[<p>Эксперты объясняют суть ООП через метафору реального мира: что такое объект, какие свойства его определяют и как из них складывается класс.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/experts/oop-in-simple-words">Объектно-ориентированное программирование простым языком — объясняют эксперты</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Ответы экспертов]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 09 Sep 2020 08:06:56 GMT</pubDate>
      <content:encoded><![CDATA[<p>В интернете можно найти много описаний ООП, однако начинающий программист рискует их не понять. Мы попросили экспертов объяснить суть этой методологии простыми словами.</p><p>Что такое объектно-ориентированное программирование?</p><p>Самый простой способ объяснить и понять ООП — воспользоваться метафорой. Метафорой объекта в ООП является объект реального мира, например, человек. Объекты надо отличать между собой и у них есть что-то, что их определяет. Например, для человека это может быть имя, когда мы говорим про нашего знакомого Васю, и все понимают о ком речь. Люди неким образом похожи друг на друга. Подмножество людей, обладающих одинаковым набором свойств (имя, фамилия, возраст и т.д.) и общим поведением, будет называться класс. Возьмем для примера сотрудников нашей компании. Для каждого из нас определен департамент (я, например, в департаменте разработки ПО числюсь, ДРПО), должность, уровень зарплаты и т.д. Эти свойства обычно определяют в момент, когда в компанию приходит новый сотрудник. У человека можно запросить информацию по его навыкам или попросить помочь коллеге — это общее поведение для всех сотрудников.</p><p>Зарплату сотрудника знает он сам, его руководитель и бухгалтер, остальные — нет. Такое сокрытие данных называется инкапсуляция. Какие свойства и поведение будет доступно другим объектам обычно определяется на уровне класса. Руководитель отдела также является сотрудником, но он обладает рядом дополнительных свойств, например, у него есть подчиненные. Таким образом класс «руководитель», расширяет класс «сотрудник» или, другими словами, происходит наследование. При этом между классами устанавливается отношение «является» — то есть любой руководитель является сотрудником, но не наоборот — не каждый сотрудник является руководителем. Если у класса больше одного наследника, то образуется иерархия. Классы, которые являются родственниками в иерархии не связаны отношением «является», например, бухгалтер является сотрудником, но бухгалтер не является руководителем.</p><p>При помощи этих правил иерархию можно проверить на корректность. Если взять ведомость со списком всех сотрудников, в нее очевидным образом попадут и руководители, и бухгалтеры, но в общем списке они не будут отличаться от других сотрудников. Если мы захотим уточнить список подчиненных у каждого руководителя, то нам понадобится подготовить отдельную ведомость со свойствами, специфичными для класса «руководитель». Такое свойство объектов называется полиморфизмом, где состав свойств и поведение будет определяться классом, через который мы смотрим на объект: мы можем обращаться к объекту, как и к любому из предков его класса, но это не верно для потомков или других родственников.</p><p>Так мы рассмотрели, как связаны объекты и классы, и такие понятия, как: инкапсуляция, наследование и полиморфизм. Все это — базовые понятия ООП.</p><p>Объектно-ориентированное программирование – это подход, при котором вся программа рассматривается как набор взаимодействующих друг с другом объектов. При этом нам важно знать их характеристики.</p><p>У каждого объекта в системе есть свойства и поведение, как и у любого реального объекта. Например, рассмотрим объект «машина». У него есть свойства (цвет, вес, стоимость) и поведение (машина может ехать, сигналить, потреблять топливо).</p><p>Такой подход помогает строить сложные системы более просто и естественно благодаря тому, что вся предметная область разбивается на объекты и каждый из них слабо связан с другими объектами. Слабая связанность возникает вследствие соблюдения трех принципов: инкапсуляции, наследования и полиморфизма.</p><ol><li>Инкапсуляция – сокрытие поведения объекта внутри него. Объекту «водитель» не нужно знать, что происходит в объекте «машина», чтобы она ехала. Это ключевой принцип ООП.</li><li>Наследование. Есть объекты «человек» и «водитель». У них есть явно что-то общее. Наследование позволяет выделить это общее в один объект (в данном случае более общим будет человек), а водителя — определить как человека, но с дополнительными свойствами и/или поведением. Например, у водителя есть водительские права, а у человека их может не быть.</li><li>Полиморфизм – это переопределение поведения. Можно снова рассмотреть «человека» и «водителя», но теперь добавить «пешехода». Человек умеет как-то передвигаться, но как именно, зависит от того, водитель он или пешеход. То есть у пешехода и водителя схожее поведение, но реализованное по-разному: один перемещается ногами, другой – на машине.</li></ol><p>ООП позволяет упростить сложные объекты, составляя их из более маленьких и простых, поэтому над программой могут работать сотни разработчиков, каждый из которых занят своим блоком. Большинство современных языков программирования — объектно-ориентированные, и, однажды поняв суть, вы сможете освоить сразу несколько языков.</p><p>Методология объектно-ориентированного программирования (ООП) подразумевает представление всей программы или ее частей объектами. У каждого объекта есть тип — в ООП он называется классом. Классы можно объявлять или наследовать и создавать из них экземпляры. Собственно, объект — это и есть экземпляр класса.</p><p>Обычно объект объединяет в себе данные и методы для работы с ними. Представим, что у нас есть тип «Позвоночное существо», у которого есть свойство «Класс». У каждого из Позвоночных существ это свойство равно одному из пяти значений: Рыба, Земноводное, Птица, Пресмыкающееся, Млекопитающее. Добавим метод получить_класс — он будет возвращать это значение. Далее объявим тип «Человек», который наследует типу «Позвоночное существо». Создадим несколько экземпляров: Иван Иванов, Марина Иванова, Антон Антонов. Добавим присущие только Человеку свойства: Имя и Фамилию. У каждого из них будет метод получить_имя/получить_фамилию, а также перешедший от Позвоночного существа метод получить_класс.</p><p>Так можно продолжать сколь угодно долго: повторно описывать методы родительских классов нам не нужно, и любой экземпляр класса будет обладать заявленными свойствами.</p><p>Основные задачи ООП — структурировать код, повысить его читабельность и ускорить понимание логики программы. Косвенно выполняются и другие задачи: например, повышается безопасность кода и сокращается его дублирование.</p><p>Дело в том, что человеку гораздо удобнее работать с реальными объектами, чем отдельно с набором данных и функциями. Представляя данные в программе как свойства объекта, а функции по обработке данных — как возможные методы объекта, мы приближаем процесс программирования к процессу описания метода решения задачи. Это достигается за счет добавления знакомой человеку структуры абстракций: ведь даже язык, на котором мы говорим, следует принципам ООП. У каждой буквы есть произношение и написание, каждое слово включает буквы и имеет свое произношение и написание, то же верно и для предложений, и для более крупных конструкций. Все в этом мире — объект!</p><p>Главное, о чем не стоит забывать: ООП — это не единственная парадигма. У нее есть свои плюсы и минусы, для каких-то задач она подходит, для каких-то — нет. Например, ООП не даст особых преимуществ, если вы пишете «однострочники» и простые скрипты. Однако в больших проектах неразделенный на отдельные сущности код быстро превратится в «лапшу» и перестанет читаться, и ООП здесь сильно упростит работу.</p><p>Наиболее классическое определение, к которому прибегают при необходимости объяснить что такое ООП, это — «способ моделирования реального мира».‎ Можно предположить, что ООП делает код более простым и наглядным, однако такая формулировка слишком размыта и уклончива, она не открывает самой сути ООП.</p><p>ООП стоит на трёх китах:</p><ul><li>Инкапсуляция — способ спрятать сложную логику внутри класса, предоставив программисту лаконичный и понятный интерфейс для взаимодействия с сущностью.</li><li>Наследование — способ легко и просто расширить существующий класс, дополнив его функциональностью.</li><li>Полиморфизм — принцип «один интерфейс — множество реализаций». Например, метод print может вывести текст на экран, распечатать его на бумаге или вовсе записать в файл.</li></ul><p>Если резюмировать: ООП даёт контроль над зависимостями в коде. Это способ сделать так, чтобы высокоуровневый код не зависел от низкоуровневой реализации. ООП позволяет вести разработку раздельно, поскольку взаимодействие между сущностями определено интерфейсами.</p><p>Суть ООП заключается в том, чтобы представить программу в виде объектов, которые каким-то образом взаимодействуют друг с другом.</p><p>Все, что угодно, можно представить в виде объекта: человека, воздушный шарик, сообщение в мессенджере. У объекта могут быть свойства, например, цвет – красный, размер – большой. Также у объекта могут быть методы для совершения операций. Например, если объект телевизор, вызываем метод «включить», и телевизор включается.</p><p>Объект — это экземпляр какого-то класса. Класс — это шаблон, в котором описаны все свойства будущего объекта и его методы. При этом если класс воздушного шарика определяет свойство цвет, то сам класс никакого значения цвета не имеет. Но экземпляры этого класса, которых, к слову, можно создавать сколько угодно, уже будут раскрашены в любые цвета.</p><p>Классы могут выстраиваться в хитрые витиеватые структуры. Чем структура хитрее, тем программа гибче, легче поддается изменениям и внедрениям нового функционала, но не обязательно. Такие слова как наследование, полиморфизм, инкапсуляция позволяют создавать структуры объектов еще витиеватее, при этом избавляют код от дублирования и делают его интуитивно понятным, но не всегда.</p><p>Понимание только лишь принципа работы объектов не сделает человека ООП-гуру. Суть мастерства ООП в умении конструировать многоуровневые структуры из классов, при этом оставляя код читаемым, надежным и гибким. Чтобы это постичь, потребуется пройти долгий и изнурительный путь, но в конечном итоге ООП станет лучше.</p><p>Часто статьи про ООП начинаются с кучи терминов, теории и сложных объяснений подходов и парадигм. В своем курсе программирования на Java для начинающих в Воронежском государственном университете я сначала объясняю на практике роль объектов, их связь и операции с ними, используя обычные слова, которые мы используем в повседневной жизни. Например, инкапсуляцию удобно объяснять с помощь магазина, где есть витрина, на которой все видно и красиво расставлено и есть склад, куда обычного покупателя не пускают.</p><p>Когда студенты начинают понимать и могут строить объектные модели, можно вводить первые термины, такие как: инкапсуляция, наследование и полиморфизм. Понимая работу ООП на практике, даже на совсем примитивном уровне, эти слова уже не кажутся такими страшными и непонятными. Дальше я ввожу больше теории и обязательно добавляю практические вещи, например, паттерны проектирования.</p><p>Проще говоря, преподавать ООП стоит от практики к теории. Очень много в этом процессе дают правильные примеры. В первое время они должны отражать окружающий нас мир и только потом трансформироваться в абстракции и переходить к языку программирования.</p><p>Напоминаем, что вы можете <a href="https://docs.google.com/forms/d/e/1FAIpQLSdSanNvlfPRrSyQWfnoGPflSVwO4KctnjOdEKHzuxjCmFX2dA/viewform">задать свой вопрос</a> экспертам, а мы соберём на него ответы, если он окажется интересным. Вопросы, которые уже задавались, можно найти в списке выпусков <a href="https://tproger.ru/experts/">рубрики</a>. Если вы хотите присоединиться к числу экспертов и прислать ответ от вашей компании или лично от вас, то пишите на <a>experts@tproger.ru</a>, мы расскажем, как это сделать.</p>]]></content:encoded>
    </item>
    <item>
      <title>Когда применять функциональное программирование, а когда ООП — отвечают эксперты</title>
      <link>https://tproger.ru/experts/oop-vs-functional-programming</link>
      <comments>https://tproger.ru/experts/oop-vs-functional-programming?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Никита Прияцелюк]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/experts/oop-vs-functional-programming</guid>
      <description><![CDATA[<p>Эксперты объясняют, в каких задачах уместна каждая парадигма и почему опытные разработчики выбирают её по эффективности, а не по симпатии.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/experts/oop-vs-functional-programming">Когда применять функциональное программирование, а когда ООП — отвечают эксперты</a>»</p>]]></description>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Ответы экспертов]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 03 Feb 2020 09:07:06 GMT</pubDate>
      <content:encoded><![CDATA[<p>Многие слышали про функциональное программирование и, возможно, задавались вопросом: «А зачем оно, когда есть ООП?». Мы спросили у экспертов, когда стоит использовать ту или иную парадигму.</p><p>Каждый инструмент хорош для определённого набора задач в определённой ситуации. Как некоторые могут забивать саморезы молотком, так и фанаты той или иной парадигмы при любых вводных могут задействовать то, чем лучше владеют. Однако опытные разработчики всё-таки руководствуются не симпатией к какой-либо технологии, а эффективностью. Также могут присутствовать ограничения, задаваемые заказчиком или программным окружением. Важен целый ряд факторов и характеристик разрабатываемого решения, его дальнейшей поддержки. Разумеется, выбор чаще опирается на собственный опыт, и в самом начале проектирования не всегда удаётся угадать будущее развитие и все нюансы использования ПО, так что универсального решения нет.</p><p>Объектно-ориентированное программирование (ООП) является более «традиционной» парадигмой. С её помощью разработано несчётное количество программ, в том числе огромные промышленные системы в финансовых организациях, телекоммуникации на производстве, складах, транспорте. Незнание принципов ООП фактически перекроет доступ ко всем этим системам.</p><p>Тут надо понимать, что если десяток лет пользовался ООП, то и сознание подстраивается под эту модель, проще проектировать именно через объекты и вызовы методов, а не через потоки данных и данные. Один из главных минусов ООП — чудовищная, запутанная система классов для системы, которая разрабатывается десятки лет большой командой. Как правило, некоторые «ядерные» классы оказываются на «дне» модели, их никто не рискует трогать даже в ущерб скорости разработки и устойчивости ПО. Появляются классы-наследники, переопределённые методы и прочий мусор, который со временем тоже становится «ядром системы».</p><p>Что касается функционального программирования (ФП). В каком-то сильно упрощённом виде оно используется и при ООП — в школах первые программы пишут функциями. С него нужно начинать изучение языков программирования, им же и завершать. Далее, в зависимости от нужд конкретного проекта, можно углублять знания той парадигмы, которая больше используется. Парадигма ФП влияет и на программирование, и на проектирование программного обеспечения. Для высоконагруженных систем переход к обработке потоков данных может быть спасением. Для выбора этой парадигмы в «большой» системе как минимум нужно иметь много данных и большую нагрузку (много вызовов, много пользователей). С одной стороны, она может дистанцировать бизнес-модель от реализации, с другой — позволит вовремя отвечать на запросы пользователей и иных внешних систем. Для небольших программ выбор ФП возможен, тут больше дело вкуса. Однако новичку может быть непросто разделить бизнес-модель на данные и потоки данных и спроектировать так, чтобы данные не хранились в классах и было чистое ФП.</p><h2>Что выбрать новичку для изучения?</h2><p>Изучить всё сразу не получится, но для быстрого старта карьеры, на мой взгляд, достаточно знать принципы ООП и иметь хотя бы общее представление о функциональных, процедурных языках: современные подходы используют некоторые более старые парадигмы, в новой реализации они могут быть очень эффективны. Если есть поверхностное знание о функциональном программировании — это вообще замечательно. Значит, у разработчика есть выбор, меньше ограничений на реализацию задуманного.</p><p>Я считаю, что каждый разработчик должен иметь представление и об ООП, и о ФП, знать сильные и слабые стороны каждого подхода и на основе этого определять, что лучше использовать для решения конкретной бизнес-задачи.</p><p>ООП-подход подразумевает написание базовых классов и расширение существующих путем добавления к ним методов. Данные хранятся в экземпляре класса вместе с методами, которые ими оперируют. Функции в ООП зависят от внешних данных (например содержат внутри себя ссылки на глобальные переменные) или коммуницируют с внешним миром (ввод-вывод).</p><p>В отличие от ООП, функциональное программирование характеризуется слабой связью функции с данными, которыми она оперирует. Это позволяет избежать побочных эффектов при выполнении функций — например чтения и изменения глобальных переменных, операций ввода-вывода и так далее. Детерминированные функции ФП возвращают один и тот же результат для одних и тех же аргументов.</p><p>Но эти подходы не являются взаимоисключающими. Нет необходимости выбирать только одну парадигму и следовать ей до конца. Вы можете передавать классы в чистые (то есть не связанные с внешними данными) функции или можете использовать чистые функции в качестве методов класса — одно другому не противоречит, а только дополняет. Если вы пишете простую и небольшую программу, следование той или иной парадигме — сугубо ваше личное мнение и видение прекрасного. Однако если вы пишете большой сервис с разноплановыми задачами, в определённый момент вы столкнетесь с необходимостью рефакторинга, так как для эффективного решения всех этих задач одного подхода будет недостаточно.</p><p>Приведу пример. Если вы пишете на Node.js, на первый взгляд удобнее использовать ФП. Дело в том, что сам по себе запрос на сервер — это функция с определённым входом и выходом (request, response). А работа с request’ом происходит с помощью цепочки функций (middleware), и результат (response) всегда будет одинаковый, если в качестве аргументов передавать одни и те же значения. Функциональный подход здесь смотрится естественно. С другой стороны, если Node.js-сервис подразумевает работу с БД, для описания моделей и работы с ними удобнее применить ООП-подход. Примером служит популярная библиотека sequelize.</p><p>На просторах frontend особой популярностью пользуются фреймворки, и каждый из них использует ту или иную парадигму, но для полноценной работы с ними необходимо знание как ООП, так и ФП. Взять для примера Angular: данный фреймворк построен на сервисах, которые в свою очередь являются классами, содержащими данные и методы для работы с ними. Однако при работе с библиотекой Redux, обычно работающей в паре с React, напрямую сталкиваешься с функциональным подходом, так как основная идея Redux — использование чистых функций без побочных эффектов.</p><p>ООП vs. ФП — вечная дилемма. Мне кажется, что наибольшей эффективности в разработке можно добиться, только если миксовать подходы. Точно не стоит писать проект только на ФП, потому что такой подход сильно ограничивает разработку: нужно постоянно прорабатывать поведение state.</p><p>Я бы порекомендовал начинающим разработчикам посмотреть отдельные, частные кейсы. Например, хороший кейс использования ФП, когда в проекте есть некая сортировка данных. Условно, есть датасет, в котором нужно отфильтровать данные по фамилии, а затем ещё и по имени. Функциональное программирование позволяет сделать это красиво и с умом.</p><p>Всегда стоит думать о логике проекта. Если в приложении много динамики, то архитектура REST API из функционального программирования отлично сработает, так делают те же ребята из tutu.ru. То же самое работает и наоборот — если у вас обычное сервисное приложение, где пользователю отображают JSON, то особо смысла использовать ФП нет.</p><p>«Когда применять функциональное программирование, а когда ООП?» — вопрос совсем непростой. Если посмотреть форумы, то понятно, что холивар возникает уже на этапе самого определения функционального программирования.</p><p>Если исходить из определения, что функциональный стиль — это когда результат выполнения кода всегда зависит только от поданных на вход значений, то лично я такой подход стараюсь применять как можно чаще. Это упрощает читабельность кода, его тестирование. Однако ФП более характеризуется тем, что аргументами одних функций являются другие, более простые функции, и вот это наиболее сложная часть, где можно легко выскочить за сложность вычислений O(n).</p><p>Не очень понятно, как сравнить ООП и функциональное программирование (ФП). Я считаю, что модели могут тесно пересекаться, не исключая друг друга, и где какую модель применять, зависит от архитектуры программы и задач, стоящими перед каждым модулем программы. Например ваш код имитирует движение транспортного средства (ТС). Нужно каждую секунду вычислять координаты ТС, его скорость, пройденный путь. Для того, чтобы это сделать, необходимо будет производить вычисления на основе предыдущих вычислений. Если применить функциональное программирование, то появляется необходимость хранения результатов вычислений и подачи их каждый раз на вход модели. Это создаст дополнительные логические сложности, поэтому в этой задаче лучше скомбинировать ООП и ФП. Для каждого ТС создаётся объект в стиле ООП, и каждый объект сам хранит свои предыдущие вычисления. Вам остаётся на вход подавать только ускорение, направление движения и время, в течение которого они действовали. Внутри же объекта ТС у вас будет два десятка методов, рассчитывающих его новое состояние. И вот тут рекомендация: стремиться большую часть из них сделать в виде простых функций, и только в одном или двух в вычисления добавить влияние его предыдущего состояния или используемого набора функций.</p><p>Сейчас порог входа в программисты очень высок, и изучать что-то одно и быть востребованным у программиста вряд ли получится.</p><h2>Итак, какую парадигму выбрать?</h2><p>ООП-подход подразумевает написание базовых классов и расширение существующих путем добавления к ним методов. Данные хранятся в экземпляре класса вместе с методами, которые ими оперируют. Функции в ООП зависят от внешних данных.</p><p>Функциональное программирование характеризуется слабой связью функции с данными, которыми она оперирует. Это позволяет избежать побочных эффектов при выполнении функций — например чтения и изменения глобальных переменных, операций ввода-вывода и так далее.</p><p>Тем не менее, эти подходы не являются взаимоисключающими. Вы можете передавать классы в чистые функции или использовать чистые функции в качестве методов класса. Нередко удачным подходом является именно смешение парадигм, а не использование какой-то одной.</p><p>При выборе парадигмы стоит смотреть на решаемую вами задачу, а также учитывать возможное развитие проекта, чтобы быть уверенным, что выбранная сегодня «правильная» парадигма не вынудит вас через полгода переписать весь проект.</p><p>Что до новичков, то определённо стоит изучить ООП, так как это более «традиционная» парадигма, которая много где используется. Но стоит иметь хотя бы общее представление о ФП, чтобы иметь у себя в арсенале ещё один инструмент.</p><p>Напоминаем, что вы можете <a href="https://docs.google.com/forms/d/e/1FAIpQLSdSanNvlfPRrSyQWfnoGPflSVwO4KctnjOdEKHzuxjCmFX2dA/viewform">задать свой вопрос</a> экспертам, а мы соберём на него ответы, если он окажется интересным. Вопросы, которые уже задавались, можно найти в списке выпусков <a href="https://tproger.ru/experts/">рубрики</a>. Если вы хотите присоединиться к числу экспертов и прислать ответ от вашей компании или лично от вас, то пишите на <a>experts@tproger.ru</a>, мы расскажем, как это сделать.</p>]]></content:encoded>
    </item>
    <item>
      <title>Мнение: объектно-ориентированное программирование — катастрофа на триллион долларов</title>
      <link>https://tproger.ru/translations/oop-the-trillion-dollar-disaster</link>
      <comments>https://tproger.ru/translations/oop-the-trillion-dollar-disaster?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Klara Oswald]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/oop-the-trillion-dollar-disaster</guid>
      <description><![CDATA[<p>Критика объектно-ориентированного программирования: автор называет его катастрофой на триллион долларов и сравнивает с функциональным подходом.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/oop-the-trillion-dollar-disaster">Мнение: объектно-ориентированное программирование — катастрофа на триллион долларов</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Функциональный C#]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 04 Sep 2019 13:08:45 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Илья Суздальницкий, senior full-stack-разработчик</p><p>Прим. ред. Мнение редакции может не совпадать с мнением автора оригинала.</p><p>По мнению многих, ООП является жемчужиной информатики. Идеальное решение для организации кода. Конец всем проблемам. Единственный верный способ написания программ. Дарован нам самим истинным Богом программирования.</p><p>Но это не так. Люди начинают уступать под тяжестью абстракций и сложного графа беспорядочно разделяемых изменяемых объектов. Драгоценное время и силы тратятся на размышления об абстракциях и шаблонах проектирования вместо решения реальных проблем. Многие критикуют объектно-ориентированное программирование, в том числе очень выдающиеся разработчики программного обеспечения. Даже сам изобретатель этой парадигмы известен своей критикой современного ООП.</p><p>Цель каждого разработчика — написать надёжное программное обеспечение. Ничто другое не имеет значения, если код глючит. При этом самый лучший подход к написанию надёжного кода — простота. Следовательно, первая и главная цель разработчиков должна заключаться в уменьшении сложности кода.</p><h2>Дисклеймер</h2><p>Буду честен, я не ярый поклонник ООП. И конечно, эта статья будет предвзятой. Однако у меня есть веские причины не любить этот подход.</p><p>Критика ООП — очень деликатная тема. Вероятно, она заденет многих читателей. Тем не менее, я делаю то, что считаю правильным. Цель — не обидеть, а повысить осведомлённость о проблемах ООП. Здесь нет критики ООП Алана Кея — он гений. Было бы замечательно, если бы эта парадигма была реализована так, как он это задумал. Я критикую современный подход Java/C#.</p><p>Думаю, что это неправильно — де-факто считать ООП стандартом для организации кода многими людьми, в том числе на очень высоких технических должностях. Также недопустимо, что многие основные языки не предлагают никаких альтернатив организации кода, кроме ООП.</p><p>Я перебарывал себя, работая над ООП-проектами. И я не имел ни малейшего понятия, почему я так напрягался. Может быть, я был недостаточно хорош? Может, мне нужно было выучить ещё несколько шаблонов проектирования? Так я думал, и в конце концов полностью выгорел.</p><p>Эта статья подводит итог моего первого десятилетнего пути из объектно-ориентированного в функциональное программирование. Как бы я ни старался, я больше не мог найти варианты использования для ООП. Я лично видел, как ООП-проекты терпят крах, потому что их становится слишком сложно обслуживать.</p><h2>В чём суть?</h2><p>Объектно-ориентированные программы предлагаются в качестве альтернативы правильным.</p><p>Объектно-ориентированное программирование было создано с одной целью — управлять сложностью процедурных кодовых баз. Другими словами, такой подход должен был улучшить организацию кода. Нет объективных и открытых доказательств того, что ООП лучше простого процедурного программирования.</p><p>Однако горькая правда в том, что ООП терпит неудачу в единственной задаче, для которой оно было предназначено. На бумаге это выглядит хорошо, но как только сложность приложения начинает увеличиваться, всё становится плохо. Вместо уменьшения сложности этот подход поощряет беспорядочное совместное использование изменяемого состояния и вносит дополнительную сложность своими многочисленными шаблонами проектирования. ООП делает общие практики, такие как рефакторинг и тестирование, неоправданно сложными.</p><p>Некоторые могут не согласиться со мной, но современное ООП в Java/C# никогда не было спроектировано должным образом. Оно никогда не было результатом работы хорошего исследовательского института (в отличие от Haskell/FP). Лямбда-исчисление предлагает полную теоретическую основу для функционального программирования. ООП не имеет ничего соответствующего этому. Использование ООП невинно в краткосрочной перспективе, особенно в начальных проектах. Но в долгосрочной перспективе это бомба замедленного действия, которая может взорваться, когда кодовая база станет достаточно большой. Проекты откладываются, сроки горят, разработчики перегорают, добавление новых функций становится практически невозможным. Организация помечает код как «легаси», а команда разработчиков планирует переписать его.</p><p>ООП не является естественным для человеческого мозга. Мыслительный процесс сосредоточен на том, чтобы «делать» вещи — гулять, разговаривать с другом, есть пиццу. Мозг развился, чтобы делать вещи, а не организовывать мир в сложные иерархии абстрактных объектов.</p><p>ООП-код является недетерминированным. В отличие от функционального программирования, нет гарантий в получении одинакового вывода при одинаковых входных данных. Это делает анализ программы очень сложным. В качестве упрощённого примера: вывод 2 + 2 или calculator.Add(2, 2) в основном равен четырём, но иногда он может становиться равным трём, пяти и даже 1004. Зависимости объекта Calculator могут изменить результат вычисления трудно уловимым, но основательным способом.</p><h2>Необходимость в стабильном фреймворке</h2><p>Хорошие программисты пишут хороший код, а плохие — плохой независимо от парадигмы программирования. Однако эта парадигма должна ограничивать плохих программистов от нанесения слишком большого ущерба. Конечно, вы не плохой программист, так как уже читаете эту статью и стараетесь учиться. Плохие никогда не успевают учиться, они просто нажимают кнопки на клавиатуре как сумасшедшие. Нравится вам это или нет, но вы будете работать с такими людьми. И, к сожалению, ООП не имеет достаточных ограничений, которые бы помешали плохим программистам нанести слишком большой ущерб.</p><p>Я не считаю себя плохим программистом. Но даже я не могу написать хороший код без сильного фреймворка, на котором можно было бы основать работу. Речь не идёт о софтверных фреймворках. Здесь подразумевается более общее словарное определение фреймворка — «необходимая несущая конструкция». Это фреймворки, которые регулируют более абстрактные вещи, такие как организация кода и уменьшение сложности кода. Хоть объектно-ориентированное и функциональное программирование являются парадигмами программирования, они также являются высокоуровневыми фреймворками.</p><h3>Ограничение выбора</h3><p>C++ — ужасный [объектно-ориентированный] язык… Ограничение вашего проекта до C означает, что люди не напортачат ни с какой идиотской «объектной моделью».</p><p>Линус Торвальдс широко известен своей открытой критикой C++ и ООП. Одна вещь, в которой он был на 100 % прав — это необходимость ограничения программистов в выборе. На самом деле, чем меньше у программистов выбора, тем более устойчивым становится их код. В приведённой выше цитате Торвальдс настоятельно рекомендует иметь хороший фреймворк, на котором будет основан ваш код.</p><p>Многим не нравятся ограничения скорости на дорогах, но они необходимы, чтобы не дать людям разбиться насмерть. Точно так же хорошая среда программирования должна обеспечивать механизмы, которые мешают делать глупости. Хорошая среда помогает писать надёжный код. Прежде всего, это должно помочь уменьшить сложность, предоставляя следующие вещи:</p><ul><li>модульность и возможность повторного использования;</li><li>правильная изоляция состояния;</li><li>высокое <a href="https://ru.wikipedia.org/wiki/%D0%9E%D1%82%D0%BD%D0%BE%D1%88%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81%D0%B8%D0%B3%D0%BD%D0%B0%D0%BB/%D1%88%D1%83%D0%BC">отношение сигнал/шум</a>.</li></ul><p>ООП предоставляет разработчикам слишком много инструментов и вариантов, не налагая правильных ограничений. Несмотря на обещания ООП рассмотреть модульность и улучшить возможность повторного использования, оно не выполняет свои обещания (подробнее об этом позже). ООП-код поощряет использование разделяемого изменяемого состояния, которое может быть небезопасно от раза к разу. ООП обычно требует большого количества бойлерплейта (низкого отношения сигнал/шум).</p><h3>Функциональное программирование</h3><p>Что такое функциональное программирование (ФП)? Некоторые люди считают, что это очень сложная парадигма организации кода, которая применима только в академических кругах и не подходит для «реального мира». Функциональное программирование имеет прочную математическую основу и берёт начало в <a href="https://ru.wikipedia.org/wiki/Лямбда-исчисление">лямбда-исчислениях</a>. Большинство его идей возникли как ответ на слабые стороны более распространённых языков программирования. Функции являются основной абстракцией функционального программирования. При правильном использовании функции обеспечивают такой уровень модульности и возможности повторного использования кода, какой никогда не встречался в ООП. ФП даже имеет шаблоны проектирования, которые решают проблемы null-значений и обеспечивают превосходную обработку ошибок.</p><p>Одну вещь ФП делает действительно хорошо — помогает писать надёжное программное обеспечение. Потребность в отладчике почти полностью исчезает. Нет необходимости проверять весь код и смотреть переменные. Лично я не трогал отладчик в течение очень долгого времени. При этом, если вы знаете, как использовать функции, вы уже функциональный программист. Вам просто нужно узнать, как использовать их наилучшим образом.</p><p>Я не проповедую функциональное программирование, мне всё равно, какую парадигму вы используете при написании кода. Я пытаюсь рассказать о механизмах, которые предоставляет функциональное программирование для решения проблем, присущих ООП / императивному программированию.</p><h2>Мы всё не так поняли про ООП</h2><p>Мне очень жаль, что давным давно я ввёл термин «объекты», потому что они заставляют многих людей сосредоточиться на менее важной идее. Основная же идея — обмен сообщениями.</p><p>Erlang, вероятно, — <a href="https://stackoverflow.com/questions/3431509/is-erlang-object-oriented/3433808#3433808">единственный</a> популярный объектно-ориентированный язык, хоть и обычно не считается таковым. Конечно, Smalltalk — это тоже чистый ООП-язык, однако он не используется широко. И Smalltalk, и Erlang используют ООП так, как это было задумано его изобретателем Аланом Кеем.</p><h3>Обмен сообщениями</h3><p>Алан Кей ввёл термин «объектно-ориентированное программирование» в 1960-х годах. Он имел опыт работы в области биологии и пытался заставить компьютерные программы общаться так же, как живые клетки. Основная идея состояла в том, чтобы независимые программы (ячейки) общались, отправляя друг другу сообщения. Состояние независимых программ никогда бы не открылось внешней среде (инкапсуляция). Вот оно. ООП никогда не предназначался для наследования, полиморфизма, ключевого слова new и множества шаблонов проектирования.</p><h3>ООП в чистом виде</h3><p>Erlang — это ООП в чистом виде. В отличие от других популярных языков, он сосредоточен на основной идее ООП — обмен сообщениями. В Erlang объекты взаимодействуют, передавая между собой неизменяемые сообщения. Есть ли доказательства того, что неизменяемые сообщения лучше методов? Да, чёрт возьми! Erlang самый надёжный язык в мире. Он поддерживает большую часть мировой телекоммуникационной инфраструктуры, включая интернет. Некоторые из систем, написанных на Erlang, имеют надежность 99,9999999 % (вы правильно прочитали — девять девяток).</p><h2>Сложность кода</h2><p>Благодаря объектно-ориентированным языкам программирования, компьютерное программное обеспечение становится более многословным, менее читаемым, менее наглядным и более сложным для модификации и обслуживания.</p><p>Наиболее важным аспектом разработки ПО является снижение сложности кода. Ни одна из фич не имеет значения, если кодовую базу становится невозможно поддерживать. В этом случае даже стопроцентное тестовое покрытие ничего не стоит.</p><p>Что делает кодовую базу сложной? Есть много вещей, на которые следует обратить внимание. На мой взгляд, главными причинами являются: общее изменяемое состояние, ошибочные абстракции и низкое отношение сигнал/шум (бойлерплейт). Все они распространены в ООП.</p><h2>Проблемы состояния</h2><p>Состояние — это любые временные данные, хранящиеся в памяти: переменные или поля/свойства в ООП. Императивное программирование (включая ООП) описывает вычисления с точки зрения состояния программы и изменений в этом состоянии. Декларативное (функциональное) программирование описывает желаемые результаты и не указывает явно изменения состояния.</p><h3>Изменчивое состояние — акт умственного жонглирования</h3><p>Я думаю, что большие объектно-ориентированные программы борются со сложностью, возрастающей по мере построения большого графа изменяемых объектов. Понимаете, это как пытаться понять и держать в голове, что произойдет при вызове метода и какими будут побочные эффекты.</p><p>Состояние само по себе довольно безобидно. Но изменчивое состояние — большая угроза стабильности. Особенно, если его распространять. Что именно является изменчивым состоянием? Любое состояние, которое может измениться: переменные или поля в ООП.</p><p>Рассмотрим пример из реального мира. У вас есть чистый кусок бумаги, вы пишете на нём заметку. В результате вы получаете тот же кусок в другом состоянии (текст). Вы фактически изменили его состояние. Это вполне нормально в реальном мире, поскольку никто не заботится об этом куске бумаги. Если только это не оригинальная картина «Мона Лиза».</p><h3>Ограничения человеческого мозга</h3><p>Почему изменчивое состояние такая большая проблема? Человеческий мозг — самая мощная машина в известной вселенной. Но он очень плохо работает с состоянием, поскольку мы можем удерживать в рабочей памяти только 5 сущностей за раз. Гораздо проще рассуждать о куске кода, если вы думаете только о том, что он делает, а не о том, какие переменные он изменяет вокруг кодовой базы.</p><p>Программирование с изменяемым состоянием — это акт умственного жонглирования. Делать это двумя шарами довольно просто, но взяв три или больше, я уроню их все. С написанием кода так же. Я стал намного продуктивнее, а мой код стал намного надёжнее, как только я отбросил изменчивое состояние. Почему мы тогда пытаемся выполнять этот акт умственного жонглирования каждый день на работе? К сожалению, умственное манипулирование изменчивым состоянием лежит в основе ООП. Единственная цель существования методов объекта состоит в том, чтобы видоизменить этот же объект.</p><h3>Разрозненное состояние</h3><p>ООП ещё больше усугубляет проблему организации кода, разбрасывая состояние по всей программе. Рассеянное состояние затем беспорядочно распределяется между различными объектами.</p><p>Рассмотрим пример из реального мира. Забудем на секунду, что мы все взрослые. Притворимся, что пытаемся собрать крутой грузовик из Lego. Но здесь есть одна загвоздка — все детали грузовика случайно смешаны с деталями других ваших игрушек Lego. И они были разложены в 50 разных коробках, снова случайно. И вам не разрешают группировать детали вашего грузовика. Вы должны держать в голове, где находятся различные его части, и можете вынимать их только одну за другой. Да, вы в конечном счёте соберёте этот грузовик, но сколько времени это займет?</p><p>Какое это имеет отношение к программированию? В функциональном программировании состояние обычно является изолированным. Вы всегда знаете, откуда оно исходит. Состояние никогда не разбросано по разным функциям. В ООП каждый объект имеет своё состояние. При построении программы вы должны иметь в виду состояние всех объектов, с которыми вы в данный момент работаете. Чтобы облегчить жизнь, лучше всего иметь очень небольшую часть кода, связанную с состоянием. Пусть основные части вашего приложения не будут содержать состояния и будут чистыми. Это является главной причиной успеха для Flux-паттерна в фронтенде (он же <a href="https://ru.wikipedia.org/wiki/Redux">Redux</a>).</p><h3>Беспорядочно разделённое состояние</h3><p>Изменчивое состояние в реальном мире почти никогда не является проблемой, поскольку вещи хранятся в частном порядке. Это «правильная инкапсуляция» на работе. Представьте художника, который работает над следующей картиной Моны Лизы. Он работает над картиной один. Заканчивает, а затем продаёт свой шедевр за миллионы. А потом он решает устроить рисовальную вечеринку и приглашает своих друзей — эльфа, Гэндальфа, полицейского и зомби. Все они начинают рисовать на одном и том же холсте одновременно. Конечно, ничего хорошего из этого не выйдет.</p><p>Общее изменяемое состояние не имеет смысла в реальном мире. Но это именно то, что происходит в программах ООП — состояние беспорядочно разделяется между различными объектами и они изменяют его любым удобным для них способом. Это, в свою очередь, делает анализ программы всё сложнее, так как кодовая база продолжает расти.</p><h3>Проблемы параллелизма</h3><p>Беспорядочное совместное использование изменяемого состояния в ООП делает распараллеливание такого кода практически невозможным. Для решения этой проблемы были изобретены сложные механизмы: блокировка потоков, мьютекс и многие другие. У таких сложных подходов есть свои недостатки — взаимоблокировки, отсутствие возможности компоновки, плюс отладка многопоточного кода очень сложна и отнимает много времени. Речь даже не идёт об увеличении сложности, вызванном использованием таких механизмов параллелизма.</p><h3>Не все состояния — зло</h3><p>Вероятно, изменение состояний по задумке Алана Кея — не зло. Оно, вероятно, полезно, если действительно изолировано (не как в ООП). Также вполне нормально иметь неизменяемые объекты передачи данных. Ключ здесь «неизменяемые». Такие объекты затем используются для передачи данных между функциями.</p><p>Однако такие объекты также делают методы и свойства ООП избыточными. Какая польза от наличия методов и свойств объекта, если его нельзя изменить?</p><h3>Изменчивость неотъемлема в ООП</h3><p>Некоторые утверждают, что изменяемое состояние — решение разработчиков в ООП, а не данность. Но это не так. Это не выбор разработчиков, а практически единственный вариант. Да, неизменяемые объекты можно передавать в методы Java/C#. Но это делается редко, поскольку большинство разработчиков по умолчанию используют изменение данных. Даже если разработчики пытаются использовать неизменяемость в своих программах ООП, языки не предоставляют встроенных механизмов для неизменяемости и для эффективной работы с неизменяемыми данными (то есть постоянными структурами данных).</p><p>Можно сделать так, что объекты будут общаться только путём передачи неизменяемых сообщений и никогда не будут передавать никакие ссылки (что на самом деле редкость). Такие программы будут более надёжными, чем основные в ООП. Но объекты всё ещё должны изменить своё собственное состояние после получения сообщения. Сообщение является побочным эффектом, и его единственная цель — вызвать изменения. Сообщения были бы бесполезны, если бы они не могли изменить состояние других объектов.</p><p>Невозможно использовать ООП, не вызывая изменения состояния.</p><h3>Троянский конь инкапсуляции</h3><p>Часто упоминается, что инкапсуляция является одним из величайших преимуществ ООП. Предполагается защитить внутреннее состояние объекта от внешнего доступа. Но есть небольшая проблема. Это не работает.</p><p>Инкапсуляция — это троянский конь ООП. Он продвигает идею общего изменяемого состояния, делая его, казалось бы, безопасным. Инкапсуляция позволяет небезопасному коду проникать в кодовую базу (и даже поощряет это), заставляя её гнить изнутри.</p><h3>Проблема глобального состояния</h3><p>Часто твердят, что глобальное состояние является корнем всех бед и его следует избегать любой ценой. Инкапсуляция, по сути, является глобальным состоянием. Чтобы код был более эффективным, объекты передаются не по значению, а по ссылке. Вот где «внедрение зависимости» падает в грязь лицом.</p><p>Позвольте объяснить. Всякий раз, когда создаётся объект, ссылки на его зависимости передаются конструктору. Эти зависимости также имеют своё внутреннее состояние. Вновь созданный объект хранит ссылки на эти зависимости в своём внутреннем состоянии, а затем изменяет их любым удобным для него способом. Он также передаёт эти ссылки на всё остальное, что может в конечном счёте использовать. Это создаёт сложный граф разнородных общих объектов, которые изменяют состояние друг друга. Это, в свою очередь, вызывает огромные проблемы. Становится почти невозможно увидеть, что вызвало изменение состояния программы. Дни могут быть потрачены впустую в попытках отладить такие изменения. И вам повезёт, если не придётся иметь дело с параллелизмом (подробнее об этом позже).</p><h3>Методы/Свойства</h3><p>Методы или свойства, которые обеспечивают доступ к определённым полям, не лучше, чем непосредственное изменение значения поля. Не имеет значения, изменяете ли вы состояние объекта с помощью необычного свойства или метода, результат один и тот же — изменённое состояние.</p><h2>Проблема с моделированием реального мира</h2><p>Некоторые утверждают, что ООП пытается смоделировать реальный мир. Это неправда — ООП не имеет ничего общего с реальным миром. Попытка смоделировать программы как объекты является одной из самых больших ошибок ООП.</p><h3>Реальный мир не иерархичен</h3><p>ООП пытается моделировать всё как иерархию объектов. Но это не так. Объекты в реальном мире взаимодействуют друг с другом с помощью сообщений, но в основном они независимы друг от друга.</p><h3>Наследование в реальном мире</h3><p>ООП-наследование не отражает наследование реального мира. Родительский объект не может изменить поведение дочерних объектов во время выполнения. Даже если вы наследуете свою ДНК от родителей, они не могут вносить изменения в вашу ДНК по своему усмотрению. Вы не наследуете поведение от своих родителей. Вы развиваете своё поведение. И вы не можете переопределить поведение своих родителей.</p><h3>В реальном мире нет методов</h3><p>Имеет ли лист бумаги, на котором вы пишете, метод «записи»? Нет. Вы просто берёте пустой лист бумаги, берёте ручку и пишете текст. Вы, как человек, тоже не имеете метода «записи» — вы принимаете решение написать какой-то текст на основе внешних событий или ваших внутренних мыслей.</p><h2>Королевство Существительных</h2><p>Объекты связывают функции и структуры данных вместе в неделимых единицах. Я думаю, что это фундаментальная ошибка, поскольку функции и структуры данных принадлежат совершенно разным мирам.</p><p>Объекты (или существительные) находятся в самом центре ООП. Основное ограничение ООП состоит в том, что он превращает всё в объекты. Но не всё должно быть смоделировано как существительные. Операции (функции) не должны моделироваться как объекты. Зачем создавать класс Multiplier, когда всё, что нужно, — функция, умножающая два числа? Просто сделайте функцию Multiply. Пусть данные будут данными, а функции будут функциями.</p><p>В языках, отличных от ООП, выполнять тривиальные задачи вроде сохранения данных в файл очень просто. Это похоже на то, как вы описали бы действие на простом английском языке.</p><p>Рассмотрим ситуацию из реального мира. Возьмём того же художника. Пусть он владеет фабрикой рисования (PaintingFactory). Он нанял менеджера по кистям (BrushManager), менеджера по цвету (ColorManager), менеджера по холсту (CanvasManager) и поставщика Моны Лизы (MonaLisaProvider). Его хороший друг зомби использует стратегию потребления мозга (BrainConsumingStrategy). Эти объекты, в свою очередь, определяют следующие методы: создать картину (CreatePainting), найти кисть (FindBrush), выбрать цвет (PickColor), вызвать Мону Лизу (CallMonaLisa) и потреблять мозги (ConsumeBrainz).</p><p>Конечно, это чушь. Это никогда не могло бы произойти в реальном мире. Сколько ненужной сложности было создано для простого акта рисования картины? Нет необходимости изобретать странные концепции для хранения ваших функций, когда им разрешено существовать отдельно от объектов.</p><h2>Модульное тестирование</h2><p>Автоматическое тестирование является важной частью процесса разработки и очень помогает в предотвращении регрессий (ошибок, вносимых в существующий код). Модульное тестирование играет огромную роль в процессе автоматического тестирования.</p><p>Некоторые могут не согласиться, но ООП-код общеизвестно труден для модульного тестирования. Модульное тестирование предполагает изоляцию, и чтобы создать метод для такого вида тестирования, нужно:</p><ul><li>извлечь его зависимости в отдельный класс;</li><li>создать интерфейс для вновь созданного класса;</li><li>объявить поля для хранения экземпляра вновь созданного класса;</li><li>использовать фреймворк, чтобы «mock-ать» зависимости;</li><li>использовать специальный фреймворк для внедрения зависимостей.</li></ul><p>Сколько ещё препятствий нужно преодолеть, чтобы сделать фрагмент кода тестируемым? Сколько времени было потрачено впустую? Кроме того, нужно создавать экземпляр всего класса, чтобы протестировать один метод. Это подтянет код из всех его родительских классов. С ООП писать тесты для унаследованного кода ещё сложнее, практически невозможно. Целые компании были созданы (<a href="https://www.typemock.com/">TypeMock</a>) из-за проблемы тестирования легаси-кода.</p><h3>Шаблонный код</h3><p>Шаблонный код (бойлерплейт) является самой большой проблемой, когда речь идёт о соотношении сигнал/шум. Шаблонный код — это «шум» для компиляции программы. Такой код требует времени для написания и делает кодовую базу менее читаемой.</p><p>Хоть в ООП пропагандируется «программа для интерфейса, а не для реализации», не всё должно становиться интерфейсом. Следовало бы прибегнуть к использованию интерфейсов во всей кодовой базе с единственной целью — тестирование. Также, вероятно, пришлось бы использовать внедрение зависимостей, что в дальнейшем привело бы к ненужной сложности.</p><h3>Тестирование приватных методов</h3><p>Некоторые утверждают, что приватные методы не должны тестироваться. Я склонен не согласиться, модульное тестирование называется «модульным», так как тестируются небольшие изолированные блоки кода. Тем не менее, тестирование приватных методов в ООП практически невозможно. Не следует делать приватные методы внутренними только ради тестирования.</p><p>Чтобы достичь тестируемости частных методов, их обычно извлекают в отдельный объект. Это, в свою очередь, вносит ненужную сложность и шаблонный код.</p><h2>Рефакторинг</h2><p>Рефакторинг является важной частью работы разработчика. По иронии судьбы ООП-код трудно реорганизовать. Рефакторинг должен сделать код менее сложным и более понятным. Напротив, реорганизованный код в ООП становится значительно более сложным. Чтобы сделать код тестируемым, нужно использовать внедрение зависимостей и создать интерфейс для реорганизованного класса. И даже тогда рефакторинг ООП-кода сложен без специальных инструментов, таких как Resharper.</p><p>В этом простом примере количество строк увеличилось более чем в два раза только для извлечения одного метода. Почему рефакторинг создаёт ещё большую сложность, когда код подвергается рефакторингу, который нужен в первую очередь для уменьшения сложности?</p><p>Сравните это с аналогичным рефакторингом не-ООП-кода в JavaScript:</p><p>Код буквально остался прежним. Функция isValidInput() просто переместилась в другой файл и добавилась одна строка для импорта этой функции. Также добавилась _isValidInput() к сигнатуре функции для удобства тестирования.</p><p>Это простой пример, но на практике сложность ООП-кода возрастает экспоненциально по мере увеличения кодовой базы. И это ещё не всё. Рефакторинг ООП-кода крайне рискован. Сложные графики зависимостей и состояния, разбросанные по всей кодовой базе ООП, не позволяют человеческому мозгу рассмотреть все потенциальные проблемы.</p><h2>Костыли в ООП</h2><p>Что вы делаете, когда что-то не работает? Обычно есть два варианта — выбросить или попробовать исправить. ООП не может быть легко выброшено: миллионы разработчиков обучаются ООП, миллионы организаций по всему миру используют его. В течение десятилетий люди много думали, пытаясь решить проблемы, распространённые в ООП-коде. И они придумали множество шаблонов проектирования.</p><h3>Шаблоны проектирования</h3><p>ООП предоставляет набор рекомендаций, которые теоретически должны позволить разработчикам постепенно наращивать всё большие и большие системы: принцип SOLID, внедрение зависимостей, шаблоны проектирования и т. д. К сожалению, шаблоны проектирования — это не что иное, как костыли. Они существуют исключительно для устранения недостатков ООП. На эту тему было написано множество книг. И они не были бы такими плохими, если бы не вносили огромную сложность в кодовые базы.</p><h3>Фабрика проблем</h3><p>Фактически невозможно написать хороший и поддерживаемый объектно-ориентированный код.</p><p>На одной стороне спектра есть кодовая база, которая является непоследовательной и не придерживается каких-либо стандартов. По другую сторону — башня с чрезмерно сконструированным кодом, куча ошибочных абстракций, построенных одна поверх другой. Шаблоны проектирования очень помогают при построении таких башен абстракций.</p><p>Вскоре добавление новой функциональности и даже понимание всей сложности становится всё труднее и труднее. Кодовая база будет полна таких вещей, как SimpleBeanFactoryAwareAspectInstanceFactory, AbstractInterceptorDrivenBeanDefinitionDecorator, TransactionAwarePersistenceManagerFactoryProxy или RequestProcessorFactoryFactory.</p><p>Нужно тратить драгоценные интеллектуальные ресурсы, пытаясь понять башню абстракций, которую создали сами разработчики. Отсутствие структуры во многих случаях лучше, чем плохая структура.</p><figure><img src="https://media.tproger.ru/uploads/2019/08/1__xDSrTC0F2lke6OYtkRm8g.png" alt="" /></figure><p>Статья на тему: <a href="https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition">FizzBuzzEnterpriseEdition</a>.</p><h2>Падение четырёх столпов ООП</h2><p>Четыре столпа ООП: абстракция, наследование, инкапсуляция и полиморфизм.</p><p>Посмотрим, что они из себя представляют на самом деле, один за другим.</p><h3>Наследование</h3><p>Я думаю, что невозможность повторного использования затрагивает объектно-ориентированные, а не функциональные языки. Поскольку проблема с ООП-языками заключается в том, что у них есть вся эта неявная среда, которую они носят с собой. Вы хотели банан, а получили гориллу с бананом и целые джунгли в придачу.</p><p>Наследование не имеет ничего общего с реальным миром. Оно является худшим способом достижения повторного использования кода. <a href="https://ru.wikipedia.org/wiki/Design_Patterns">Банда четырёх</a> недвусмысленно рекомендовала отдавать предпочтение композиции перед наследованием, а <a href="https://ru.wikipedia.org/wiki/Go">некоторые современные языки программирования</a> вообще его избегают.</p><p>Есть несколько проблем с наследованием:</p><ul><li>происходит добавление большого количества кода, который даже не нужен вашему классу (проблема с бананом и джунглями);</li><li>определение частей вашего класса где-либо ещё затрудняет анализ кода, особенно с несколькими уровнями наследования;</li><li>в большинстве языков множественное наследование даже невозможно, это в основном делает наследование бесполезным в качестве механизма совместного использования кода.</li></ul><h3>Полиморфизм</h3><p>Полиморфизм великолепен, он позволяет изменять поведение программы во время выполнения. Это очень базовая концепция в компьютерном программировании. Я не очень уверен, почему в ООП ему уделяют так много внимания. Он выполняет свою работу, но в очередной раз приводит к умственному жонглированию. Это делает кодовую базу значительно более сложной. Анализ конкретного метода, который вызывается, становится очень трудным.</p><p>Функциональное программирование позволяет добиться того же полиморфизма гораздо более элегантным способом — просто передав функцию, определяющую желаемое поведение во время выполнения. Что может быть проще этого? Не нужно определять кучу перегруженных абстрактных виртуальных методов в нескольких файлах (и интерфейсе).</p><h3>Инкапсуляция</h3><p>Как обсуждалось ранее, инкапсуляция — троянский конь ООП. На самом деле это глобальное изменяемое состояние, благодаря которому небезопасный код выглядит безопасным. Небезопасная практика написания кода — это основа, на которую программисты ООП полагаются в своей повседневной работе.</p><h3>Абстракция</h3><p>Абстракция в ООП пытается решить сложность, скрывая ненужные детали от программиста. Теоретически, это должно позволить разработчику анализировать кодовую базу, не думая о скрытой сложности.</p><p>Причудливое слово для простой концепции. В процедурных/функциональных языках вы можете просто «спрятать» детали реализации в соседнем файле. Нет необходимости называть этот основной акт «абстракцией».</p><p>Для подробной информации о столпах ООП можете прочитать статью: <a href="https://medium.com/@cscalfani/goodbye-object-oriented-programming-a59cda4c0e53">«Goodbye, Object-Oriented Programming»</a>.</p><h2>Почему ООП доминирует в индустрии?</h2><p>Ответ прост: рептилоидная инопланетная раса вступила в сговор с АНБ (и русскими), чтобы замучить вас (программистов) до смерти.</p><p>А если серьезно, то, вероятно, ответ — Java.</p><p>Java — самая неприятная вещь, случившаяся с компьютерами со времен MS-DOS.</p><h3>Java был прост</h3><p>Когда он был впервые представлен в 1995 году, Java был очень простым языком программирования по сравнению с альтернативами. В то время входной барьер для написания настольных приложений был высок. Разработка настольных приложений включала написание низкоуровневых API-интерфейсов win32 на С. Разработчики также должны были заниматься ручным управлением памятью. Другой альтернативой был Visual Basic, но многие не хотели замыкаться в экосистеме Microsoft.</p><p>Когда появился Java, для многих разработчиков работать с ним было просто. Он был бесплатным и мог использоваться на всех платформах. Такие вещи, как встроенная сборка мусора, понятные API-интерфейсы (по сравнению с загадочными win32-API), правильные пространства имён и знакомый C-подобный синтаксис сделали Java ещё более доступным.</p><p>GUI-программирование также становилось всё более популярным. Казалось, что различные компоненты пользовательского интерфейса хорошо отображаются на классы. Автозаполнение метода в IDE также заставило людей утверждать, что ООП API проще в использовании.</p><p>Возможно, Java не был бы таким плохим, если бы он не вынуждал разработчиков реализовывать ООП. Всё остальное в Java выглядело довольно хорошо. Сборка мусора, переносимость, функции обработки исключений, которых не хватало в других основных языках, — всё это было очень удобным.</p><h3>Затем появился C#</h3><p>Первоначально Microsoft в значительной степени опиралась на Java. Когда что-то начало идти не так (и после долгой юридической битвы с Sun Microsystems за лицензирование Java), Microsoft решила инвестировать в свою собственную версию Java. Так появился C# 1.0. C# как язык всегда считался «лучшей версией Java». Однако есть одна огромная проблема — это тот же язык ООП с теми же недостатками, скрытыми под слегка улучшенным синтаксисом.</p><p>Microsoft вкладывала значительные средства в свою экосистему .NET, которая также включала в себя хорошие инструменты для разработчиков. В течение многих лет Visual Studio была одной из лучших доступных сред IDE. Это привело к широкому распространению платформы .NET, особенно на предприятиях.</p><p>В последнее время Microsoft вкладывает значительные средства в экосистему браузера, продвигая свой TypeScript. TypeScript великолепен, потому что он может компилировать чистый JavaScript и добавляет такие вещи, как статическая проверка типов. Но уже не так великолепно отсутствие надлежащей поддержки функциональных конструкций: нет встроенных неизменяемых структур данных, нет композиции функций, нет правильного сопоставления с образцом. TypeScript является в первую очередь ООП-языком. В основном это C# для браузеров. Даже отвечал за разработку и C#, и TypeScript один человек — <a href="https://ru.wikipedia.org/wiki/Хейлсберг,_Андерс">Андерс Хейлсберг</a>.</p><h3>Функциональные языки</h3><p>Функциональные языки никогда не поддерживались такими крупными компаниями, как Microsoft. F# не считается, так как инвестиции были незначительными. Разработка функциональных языков в основном осуществляется сообществом. Это объясняет различия в популярности между объектно-ориентированными и функциональными языками.</p><h2>Пора двигаться дальше?</h2><p>Теперь мы знаем, что ООП — эксперимент, который провалился. Настало время двигаться дальше. Настало время нам, как сообществу, признать эту идею провальной и отказаться от неё.</p><p>Думаю, довольно легко продолжать использовать то, что использовали десятилетиями. Большинство людей никогда не пробовало функциональное программирование. А те, кто попробовал (как и я), никогда не смогут вернуться к написанию ООП-кода.</p><p>Генри Форд однажды сказал: «Если бы я спросил людей, чего они хотят, они бы ответили — более быстрых лошадей». В мире программного обеспечения большинство людей захотят «лучший ООП-язык». Люди могут легко описать пожелания, которые у них есть (более организованная и менее сложная кодовая база), но не лучшее решение.</p><h2>Каковы альтернативы?</h2><p>Внимание, спойлер — функциональное программирование.</p><p>Если термины вроде «функтор» или «монада» вызывают у вас некоторое беспокойство, то вы не одиноки. Функциональное программирование не было бы таким страшным, если бы оно дало более интуитивные названия некоторым из его концепций. Функтор — то, что можно преобразовать с помощью функции (вспомните list.map). Монада — вычисления, которые можно объединить в цепочку.</p><p>Используя функциональное программирование, вы станете более хорошим разработчиком. У вас будет время писать реальный код, который решает реальные проблемы, вместо размышлений об абстракциях и шаблонах проектирования.</p><p>Вы можете не осознавать этого, но вы уже функциональный программист, если используете функции в своей повседневной работе. Вам просто нужно узнать, как использовать их наилучшим образом.</p><p>Два великолепных функциональных языка с очень плавной кривой обучения — это <a href="https://elixir-lang.org/">Elixir</a> и <a href="https://elm-lang.org/">Elm</a>. Они позволяют разработчику сосредоточиться на важнейшем — написании надёжного программного обеспечения — одновременно устраняя все сложности, которые имеют более традиционные функциональные языки.</p><p>Какие есть ещё варианты? Ваша организация уже использует C#? Попробуйте F# — удивительный функциональный язык, обеспечивающий отличную совместимость с существующим кодом .NET. Используете Java? Тогда Scala или Clojure — хорошие альтернативы. Используете JavaScript? С правильным руководством и линтингом JavaScript может быть хорошим функциональным языком.</p><h2>Защитники ООП</h2><p>Реакция от защитников ООП вполне ожидаема. Они скажут, что эта статья полна неточностей. Некоторые могут даже начать называть имена. Они могли бы даже назвать меня junior-разработчиком без реального опыта ООП. Кто-то может сказать, что мои предположения ошибочны, а примеры бесполезны. Это не имеет значения.</p><p>Они имеют право на своё мнение. Однако их аргументы в защиту ООП обычно довольно слабы. По иронии судьбы, большинство из них никогда не программировали на настоящем функциональном языке. Как можно провести сравнение между двумя вещами, если вы никогда не пробовали их обе? Такие сравнения не очень хороши.</p><p>Закон Деметры не очень полезен — он ничего не делает для решения проблемы недетерминированности. Общее изменяемое состояние всё ещё является общим изменяемым состоянием, независимо от того, каким образом вы получаете доступ или изменяете его. Метод a.total() не сильно лучше a.getB().getC().total(). Это просто заметает проблему под ковёр.</p><p>Предметно-ориентированное проектирование? Это полезная методология разработки, она немного помогает со сложностью. Но она по-прежнему ничего не делает для решения фундаментальной проблемы общего изменяемого состояния.</p><h3>Просто инструмент в наборе</h3><p>Часто слышу мнение людей, что ООП — просто ещё один инструмент в наборе. Да, это такой же инструмент, как и то, что лошади и машины — инструменты для перевозки. В конце концов, все они служат одной цели. Зачем использовать машины, если можно продолжать ездить на старых добрых лошадях?</p><h3>История повторяется</h3><p>В начале 20-го века автомобили начали заменять лошадей. В 1900 году в Нью-Йорке было всего несколько автомобилей на дорогах, но в основном люди использовали для перевозки лошадей. Огромная индустрия была сосредоточена вокруг конного транспорта. Целые предприятия были созданы вокруг уборки навоза. <a href="https://99percentinvisible.org/article/cities-paved-dung-urban-design-great-horse-manure-crisis-1894/">Люди сопротивлялись переменам</a>. Они называли автомобили ещё одной «модой», которая в конечном итоге проходит, ведь лошади были здесь веками. Некоторые даже просили правительство вмешаться. В 1917 году на дорогах больше не осталось лошадей.</p><p>Насколько это актуально? Индустрия программного обеспечения сосредоточена вокруг ООП. Миллионы людей обучаются ООП и миллионы компаний используют его в своём коде. Конечно, они попытаются дискредитировать всё, что угрожает их хлебу с маслом. Это просто здравый смысл. Ясно видно, как история повторяется. В 20-м веке это были лошади против автомобилей, а в 21-м — объектно-ориентированное и функциональное программирование.</p>]]></content:encoded>
    </item>
    <item>
      <title>Прототипно-ориентированное программирование в JavaScript</title>
      <link>https://tproger.ru/blogs/js-classes</link>
      <comments>https://tproger.ru/blogs/js-classes?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Никита Прияцелюк]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/blogs/js-classes</guid>
      <description><![CDATA[<p>Классы в JavaScript — синтаксический сахар над прототипами: основные идеи ООП и то, как работа с классами устроена под капотом языка.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/blogs/js-classes">Прототипно-ориентированное программирование в JavaScript</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Блоги]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 04 Apr 2019 07:37:20 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Илья Махонин, разработчик</p><p>Сегодня мы поговорим о работе с классами в JS. Думаю, что все, кто интересуется разработкой, слышали про объектно-ориентированное программирование (ООП). Эта парадигма программирования в настоящее время одна из самых популярных. И сейчас мы узнаем, как она реализуется в языке JS. ООП — очень обширная тема, в ней много понятий, поэтому данная статья не будет исчерпывающей. Мы разберём только основные моменты, не сильно вдаваясь в понятия и термины.</p><p>Сперва нужно разобраться с двумя основным идеями ООП — классом и объектом. Класс можно представить как чертёж, то есть проект чего-то, ещё не созданного, а объект — как воплощённый в жизнь чертёж. Если проводить параллель со строительством, то чертёж будущего дома — это класс, а построенный дом — объект.</p><p>Сразу хочу отметить, что в JS нет ООП как такового. Вместо него используется прототипно-ориентированное программирование — один из стилей ООП. У него есть отличия, но сейчас я их опущу.</p><p>Раньше, в ES5, работа в такой парадигме сводилась к написанию функции конструктора, а затем к неудобным манипуляциям со свойствами этой функции.</p><p>В ES6 был добавлен сахарок для работы с прототипами (для удобства буду называть их «классами»). Появились разнообразные команды для удобной разработки, которые скрывают под капотом все те «старые» команды из ES5.</p><p>Мы напишем простенький калькулятор, придерживаясь прототипно-ориентированного программирования. Такой пример познакомит вас с данной парадигмой, но не раскроет всех её преимуществ. Это я к тому, чтобы те, кто только начинает работать с JS, не сочли прототипное программирование чем-то непонятным и усложняющим разработку.</p><p>Создадим «класс» и объявим в нём конструктор (constructor). В конструкторе создадим две переменные: numberA и numberB. This — это указатель на текущий «класс». Если говорить проще, то теперь объявленные переменные будут доступны везде внутри «класса».</p><p>В итоге получится следующее:</p><p>Этот калькулятор будет хранить два значения «по умолчанию» и использовать их при работе, если не указано иное.</p><p>Наш маленький калькулятор будет поддерживать 4 основные операции. Поэтому создадим метод add(), который будет суммировать переданные ему аргументы. А если таковых нет, то калькулятор просуммирует те самые значения по «умолчанию».</p><p>Я не буду добавлять различные проверки вроде «числа ли переданы?», «не на ноль ли делим?» и т.д. Остановлюсь только на том, что должны быть переданы два аргумента, либо будут использоваться значения по умолчанию.</p><p>Вот так будет выглядеть метод сложения:</p><p>По аналогии с ним напишем методы для вычитания, умножения и деления. Единственное отличие — знак между операндами.</p><p>После создания всех методов «класс» приобретёт следующий вид:</p><p>Основная часть работы сделана. Осталось «создать экземпляр класса» и протестировать методы на работоспособность. Объявим константу calculate и присвоим ей новый объект. Посмотрите на пример, чтобы всё стало ясно:</p><p>Команда new используется для создания нового «экземпляра класса». Значения по умолчанию в этом примере — 72 и 39. Их мы передаём в «класс» как аргументы в обычную функцию. По сути, «класс» это и есть простая функция с некоторыми отличиями.</p><p>После описанных выше действий вызовем все четыре метода, передав в некоторые из них аргументы. Проверим, корректно ли они работают:</p><p>Да, всё правильно. Только в случае передачи двух аргументов, метод использует их. В остальных случаях используются значения по умолчанию.</p><p>Сегодня я познакомил вас с прототипно-ориентированным программированием в JavaScript. Да-да, несмотря на то что ключевое слово для создания прототипа — class, это всё те же прототипы. Именно поэтому я брал слова «класс» и «экземпляр» в кавычки.</p>]]></content:encoded>
    </item>
    <item>
      <title>Фундаментальные принципы объектно-ориентированного программирования на JavaScript</title>
      <link>https://tproger.ru/translations/oop-js-fundamentals</link>
      <comments>https://tproger.ru/translations/oop-js-fundamentals?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Никита Прияцелюк]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/oop-js-fundamentals</guid>
      <description><![CDATA[<p>Классовое и прототипное наследование в JavaScript, их плюсы и минусы, а также подход к разработке более модульных и масштабируемых приложений.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/oop-js-fundamentals">Фундаментальные принципы объектно-ориентированного программирования на JavaScript</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 13 Sep 2018 17:35:54 GMT</pubDate>
      <content:encoded><![CDATA[<p>Объектно-ориентированное программирование (ООП) — это шаблон проектирования программного обеспечения, который позволяет решать задачи с точки зрения объектов и их взаимодействий. ООП обычно реализуется с помощью классов или прототипов. Большинство объектно-ориентированных языков (Java, C++, Ruby, Python и др.) используют наследование на основе классов. JavaScript реализует ООП через прототипное наследование. В этой статье мы рассмотрим оба эти подхода в JavaScript, обсудим их преимущества и недостатки, а также предложим альтернативу для разработки более модульных и масштабируемых приложений.</p><h3>Что такое объект?</h3><p>Принцип ООП заключается в том, чтобы составлять систему из объектов, решающих простые задачи, которые вместе составляют сложную программу. Объект состоит из приватных изменяемых состояний и функций (методов), которые работают с этими состояниями. У объектов есть определение себя (self, this) и поведение, наследуемое от чертежа, т.е. класса (классовое наследование) или других объектов (прототипное наследование).</p><p>Наследование — способ сказать, что эти объекты похожи на другие за исключением некоторых деталей. Наследование позволяет ускорить разработку за счёт повторного использования кода.</p><h2>Классовое наследование</h2><p>В классовом ООП классы являются чертежами для объектов. Объекты (или экземпляры) создаются на основе классов. Существует конструктор, который используется для создания экземпляра класса с заданными свойствами.</p><p>Например:</p><p>Здесь при помощи ключевого слова class из ES6 мы создаём класс Person со свойствами firstName и lastName, которые хранятся в this. Значения свойств задаются в конструкторе, а доступ к ним осуществляется в методе getFullName().</p><p>Мы создаём экземпляр класса Person с именем person с помощью ключевого слова new:</p><p>Объекты, созданные с помощью ключевого слова new, изменяемы. Другими словами, изменения в классе повлияют на все объекты, являющиеся экземплярами этого класса, а также на дочерние классы, которые его расширяют (extends).</p><p>Для расширения класса мы можем создать другой класс. Расширим класс Person с помощью класса User. User — это Person с почтой и паролем:</p><p>Выше мы создали класс User, расширяющий возможности Person путём добавления свойств email и password и функций доступа к ним. В функции App() ниже мы создаём объект нового класса user:</p><p>Всё вроде бы хорошо работает, но использование классового подхода к наследованию привело к большому конструктивному недостатку: откуда пользователи класса User (например, App) могут знать, что у этого класса есть свойства firstName и lastName и функция getFullName? Одного взгляда на код класса User недостаточно для того, чтобы сказать что-либо о его родительском классе. В итоге приходится копаться в документации или искать нужный код по всей иерархии классов.</p><p>Как говорит <a href="https://github.com/gaearon">Дэн Абрамов</a>:</p><p>Проблема с наследованием заключается в том, что у потомков слишком высокий уровень доступа к деталям реализации каждого базовогокласса в иерархии и наоборот. После изменения требований рефакторинг иерархии классов настолько сложно провести, что она превращается в полную неразбериху со следами устаревших требований.</p><p>Классовое наследование построено на создании связей через зависимости. На основе базовых классов (или суперклассов) создаются производные классы. Классовое наследование хорошо подходит для небольших и простых приложений, которые редко меняются и у которых не более одного уровня наследования (неглубокие деревья наследования позволяют избежать проблемы <a href="https://ru.wikipedia.org/wiki/%D0%A5%D1%80%D1%83%D0%BF%D0%BA%D0%B8%D0%B9_%D0%B1%D0%B0%D0%B7%D0%BE%D0%B2%D1%8B%D0%B9_%D0%BA%D0%BB%D0%B0%D1%81%D1%81">хрупкого базового класса</a>) или совершенно разные сценарии использования. Однако по мере расширения иерархии такое наследование со временем будет невозможно поддерживать.</p><p>Эрик Эллиот описал, как классовое наследование может потенциально привести к провалу проекта, а в худшем случае — к провалу компании:</p><p>Как только у вас наберётся достаточно клиентов, использующих new, вы даже при желании не сможете изменить реализацию конструктора, а если попытаетесь, то сломаете весь чужой код.</p><p>Когда много производных классов с очень разными функциями наследуются от одного базового класса, любое, казалось бы, безобидное изменение в базовом классе может привести к сбою в работе производных. За счёт усложнения кода и всего процесса создания продукта вы могли бы смягчить побочные эффекты, создав контейнер для инъекций зависимостей. Это обеспечило бы единый интерфейс для создания сервисов, потому что позволило бы абстрагироваться от подробностей создания. Есть ли способ получше?</p><h2>Прототипное наследование</h2><p>В прототипном наследовании классы не используются совсем. Вместо этого объекты создаются из других объектов. Мы начинаем с обобщённого объекта — прототипа. Прототип можно использовать для создания других объектов путём его клонирования или расширять его разными функциями.</p><p>Хотя в предыдущем разделе мы показали, как использовать class из ES6, классы в JavaScript не такие уж и классы:</p><p>Классы в ES6 — на самом деле синтаксический сахар для существующего в JavaScript прототипного наследования. Под капотом при создании класса с помощью ключевого слова new создаётся новый объект функции с кодом из constructor.</p><p>По сути, JavaScript — прототипно-ориентированный язык.</p><p>Числа, строки, логические переменные (true и false), а также значения null и undefined в JavaScript относятся к простым типам данных. Всё остальное — объекты. Числа, строки и логические переменные похожи на объекты тем, что имеют методы, но в отличие от объектов они неизменны. Объекты в JavaScript имеют изменяемые ключевые коллекции. В JavaScript объектами являются массивы, функции, регулярные выражения, и, конечно, объекты также являются объектами.Из книги Дугласа Крокфорда «JavaScript: сильные стороны»</p><p>Посмотрим на один из таких объектов, доступных в JavaScript «из коробки», — Array.</p><p>Массивы (экземпляры Array) наследуются от Array.prototype, который включает в себя много методов, разделённых на акцессоры (не изменяют исходный массив), мутаторы (изменяют исходный массив) и итераторы (применяют функцию, переданную в качестве аргумента, на каждом элементе массива для создания нового).</p><p>Акцессоры:</p><ul><li>Array.prototype.includes(e) — возвращает true, если массив содержит элемент e, в противном случае — false.</li><li>Array.prototype.slice(i, j) — возвращает новый массив, который является срезом исходного от индекса i до j включительно.</li></ul><p>Мутаторы:</p><ul><li>Array.prototype.push(e) — помещает элемент e в конец массива.</li><li>Array.prototype.pop() — удаляет последний элемент массива.</li><li>Array.prototype.splice(i, j) — извлекает срез массива от индекса i до j включительно без сохранения исходного.</li></ul><p>Мутаторы изменяют исходный массив. Метод splice() извлекает такой же срез, как и slice(), однако если вам нужно оставить исходный массив, то лучше выбрать slice().</p><p>Итераторы:</p><ul><li>Array.prototype.map(f) — применяет функцию f на каждом элементе массива и создаёт новый массив с результатом вызова указанной функции.</li><li>Array.prototype.filter(f) — создаёт новый массив со всеми элементами, прошедшими проверку, задаваемую в функции f.</li><li>Array.prototype.forEach(f) — применяет функцию f на каждом элементе массива.</li></ul><p>Методы map() и forEach() похожи тем, что они что-то делают со всеми элементами массива, но ключевая разница в том, что map() возвращает массив, а forEach() — ничего. В хороших практиках проектирования ПО всегда рекомендуют писать функции без побочных эффектов, т.е. не использовать void-функции. Метод forEach() никак не изменяет исходный массив, поэтому map() будет лучшим выбором, если вам нужно как-то преобразовать данные. Один из возможных вариантов использования forEach() — вывод в консоль для отладки:</p><p>Предположим, что мы хотим расширить прототип Array новым методом partition(), который делит массив на два новых в зависимости от предиката. Например, [1,2,3,4,5] становится [[1,2,3], [4,5]], если предикат — «меньше либо равно 3». Вот как это можно реализовать:</p><p>Теперь мы можем применить partition() на любом массиве:</p><p>[1,2,3,4,5] называется литералом. Литерал — один из способов создания объекта. Также мы можем использовать фабричные функции или Object.create() для создания такого же массива:</p><p>Фабричная функция — это функция, которая принимает несколько аргументов и возвращает новый объект, состоящий из этих аргументов. В JavaScript любая функция может возвращать объект. Если она делает это без ключевого слова new, то её можно назвать фабричной. Такие функции всегда были привлекательны, так как они дают возможность легко создавать новые объекты, не вникая в сложности классов и ключевого слова new.</p><p>Выше мы создали массив arr с помощью Object.create() и поместили в него 5 элементов. arr доступны все функции прототипа Array вроде map(), pop(), slice() и даже partition(), которую мы недавно создали. Добавим ещё функциональности объекту arr:</p><p>Время викторины! Что будет возвращено после запуска кода ниже?</p><p>Ответы:</p><ul><li>№1 вернёт [[1,2],[3,4,5]], так как функция partition() определена для Array, от которого наследуется arr.</li><li>№2 вернёт "hello", поскольку мы создали новую функцию hello() для объекта arr, которая не принимает аргументов и возвращает строку "hello".</li><li>В случае с №3 будет выведена ошибка «TypeError: foo.hello is not a function». Так как foo — новый объект, созданный из прототипа Array, для которого не определена функция hello(), то и для foo она не определена.</li><li>№4 и №5 вернут "bye", поскольку строкой выше мы добавили в прототип Array новую функцию bye(), которую наследуют arr и foo. Любые изменения в прототипе влияют на объекты на его основе даже после их создания.</li></ul><p>Мы разобрались с основами прототипов, поэтому вернёмся к предыдущему примеру и создадим Person и User с помощью прототипного наследования:</p><p>Теперь мы можем использовать прототип Person таким образом:</p><p>person — объект. Если ввести console.log(person), мы увидим следующее:</p><p>Для User нам всего лишь нужно расширить класс Person:</p><p>user — объект. Если ввести console.log(user), мы увидим следующее:</p><p>Что будет, если мы захотим изменить функцию getFullName() для User? Как этот код повлияет на person и user?</p><p>Как и ожидалось, на person это никак не отразилось.</p><p>Давайте добавим в Person атрибут gender и соответствующие геттер и сеттер:</p><p>Изменения затронули как person, так и user, поскольку User наследуется от Person, поэтому при изменении последнего меняется и User.</p><p>Паттерн «декоратор» из прототипного наследования не сильно отличается от классового.</p><h3>Классы vs Прототипы</h3><p>Дэн Абрамов говорит, что:</p><ul><li>Классы скрывают прототипное наследование в основе JS;</li><li>Классы побуждают к использованию наследования, но лучше использовать композицию;</li><li>Классы, как правило, не дают вам изменить первую плохую структуру проекта, которая пришла вам в голову.</li></ul><p>Вместо классовой иерархии лучше создайте несколько фабричных функций. Они могут вызывать друг друга по цепочке, настраивая своё поведение. Также вы можете научить «основную» фабричную функцию принимать «стратегию», настраивающую поведение других фабричных функций, и передавать её из остальных фабричных функций.</p><h2>Путь третий: без ООП</h2><p>Три краеугольных камня ООП — наследование, инкапсуляция и полиморфизм — мощные средства/концепции, но со своими недостатками.</p><h3>Наследование</h3><p>Наследование способствует повторному использованию кода, но зачастую приходится брать больше, чем нужно.</p><p>Джо Армстронг (создатель Erlang) высказал мысль об этом лучшим образом:</p><p>Проблема объектно-ориентированных языков заключается во всей их неявной среде, которую они всегда тянут за собой. Вы хотели банан, а получили гориллу, держащую банан, вместе со всеми джунглями.</p><p>Так что делать, если мы получили больше, чем просили? Просто игнорировать то, что нам не нужно? Только в простых случаях. Если нам нужны классы, которые зависят от других классов, а те, в свою очередь, зависят от третьих, то нам придётся иметь дело со всем этим адом зависимостей, что сильно замедляет процессы сборки и отладки. К тому же приложения с такими длинными цепочками зависимостей плохо портируются.</p><p>Здесь присутствует проблема хрупкого базового класса, упомянутая выше. Не стоит ожидать, что всё будет идти как по маслу, когда мы соотносим реальные объекты и их классы. Наследование не будет к вам снисходительно, когда вам придётся рефакторить код, особенно базовый класс. Также оно ослабляет инкапсуляцию, ещё один краеугольный камень ООП:</p><p>Проблема в том, что если вы наследуете реализацию суперкласса, а затем меняете её, то эти изменения отзываются эхом во всей иерархии классов. В конечном итоге это может повлиять на все подклассы.</p><h3>Инкапсуляция</h3><p>Инкапсуляция защищает от влияния извне внутренние переменные каждого объекта. В идеале программа должна состоять из «островов объектов»: каждый из них со своими состояниями, передающий сообщения туда и обратно. Звучит как хорошая идея в том случае, если вы создаёте идеально распределённую систему, но на практике разработка такой программы сложна и вгоняет в определённые рамки.</p><p>Много реальных приложений требуют решения проблем с множеством составных частей. Когда вы выбираете объектно-ориентированный подход для разработки программы, вы столкнётесь с разными головоломками вроде «как распределить функциональность приложения между разными объектами?» или «как управлять взаимодействием и обменом данными между разными объектами?». В этой статье есть несколько интересных мыслей насчёт задач, стоящих при проектировании ООП-приложений:</p><p>Когда мы рассматриваем необходимую функциональность нашего кода, многие из поведений по своей сути являются общими проблемами и потому не относятся к какому-то конкретному типу данных. Тем не менее, эти установки нужно куда-то пристроить, поэтому в итоге мы создаём бессмысленные классы для их содержания. У всех этих бессмысленных сущностей есть привычка становиться ещё более бессмысленными: когда у меня есть много объектов Manager, мне приходится создавать ManagerManager.</p><p>И ведь так и есть. Такие ManagerManager классы можно зачастую увидеть в продакшне, который по задумке не должен был стать настолько сложным с течением времени.</p><p>Далее мы увидим альтернативу ООП — функциональную композицию, где вместо объектов используются функции.</p><p>Но перед этим поговорим о последнем краеугольном камне ООП.</p><h3>Полиморфизм</h3><p>Полиморфизм позволяет описывать поведение вне зависимости от типа данных. В ООП это означает создание класса или прототипа, который может быть адаптирован объектами, работающими с другими типами данных. Объекты, которые используют полиморфный класс/прототип, должны определить специфичное для типа данных поведение, чтобы всё заработало. Посмотрим на пример.</p><p>Предположим, что мы хотим создать общий (полиморфный) объект, который принимает какие-то данные и флаг состояния в качестве параметров. Если состояние говорит, что данные валидные (т.е. status === true), на данных можно применить функцию, результат которой будет возвращён вместе с флагом состояния. В противном случае мы не применим функцию и просто вернём данные и флаг.</p><p>Начнём с создания полиморфного объекта-прототипа Maybe:</p><p>Maybe — это обёртка для данных. Чтобы обернуть их, мы добавили поле status, которое указывает на валидность данных.</p><p>Мы можем добавить в прототип функцию apply(), которая принимает функцию и применяет её на данных, если статус говорит, что они валидны:</p><p>Ещё можем добавить функцию, которая возвращает либо данные, либо сообщение, если с ними что-то не так:</p><p>Теперь создадим на основе Maybe два объекта: Number:</p><p>и String:</p><p>Посмотрим на объекты в действии. Создадим функцию increment(), которая определена только для чисел, и split(), которая определена только для строк:</p><p>Так как JavaScript не типобезопасен, вам никто не запретит использовать increment() для строки или split() для числа. Вы просто увидите ошибку исполнения. Например:</p><p>При запуске выдаст TypeError.</p><p>Тем не менее если мы используем объекты Number и String, чтобы обернуть числа и строки до работы с ними, мы сможем предотвратить эти ошибки:</p><p>Что будет выведено в консоль?</p><p>Поскольку мы описали прототип Maybe таким образом, чтобы функция применялась на данных верного типа, результат будет таким:</p><p>Только что мы сделали что-то вроде монады (хотя мы и не реализовывали Maybe по всем законам монад). Монада Maybe — это обёртка, которая используется, когда данные могут не пройти проверку или отсутствовать, и вам не важно по какой причине. Как правило, такое случается при извлечении и проверке данных. Maybe обрабатывает ошибки при валидации данных или применении функции схожим с try-catch образом. Здесь вся обработка заключается в выводе в консоль, но мы легко можем переделать функцию getOrElse() так, чтобы она вызывала другую функцию-обработчик.</p><p>У некторых языков вроде Haskell монада является встроенным типом, но в JavaScript вам придётся создавать свою реализацию. В ES6 появились Promise — монады для работы с задержкой. Иногда нам требуются данные, на получение которых требуется время. Promise дают возможность писать синхронный код, откладывая работу с данными до того момента, когда они станут доступными. Использование Promise — более «чистый» способ асинхронного программирования, чем callback-функции, использование которых может привести к ситуации, известной как «ад обратных вызовов».</p><h3>Композиция</h3><p>Как упоминалось ранее, существует нечто гораздо более простое, чем классы/прототипы — функциональная композиция. Её легко можно использовать снова, она инкапсулирует внутренние состояния, выполняет операции на любом типе данных и может быть полиморфной.</p><p>JavaScript позволяет легко объединить связанные функции и данные в объекте:</p><p>Теперь мы можем использовать объект Person таким образом:</p><p>Создадим объект User, склонировав объект Person, и добавим туда дополнительные данные и функции:</p><p>Затем мы можем создать экземпляр User с помощью Object.create():</p><p>Хитрость здесь заключается в использовании Object.create() для копирования. Объекты в JavaScript изменяемы, поэтому, когда вы используете присваивание для создания нового объекта и меняете второй объект, это изменяет и исходный объект!</p><p>За исключением чисел, строк и булевых значений в JavaScript всё является объектом:</p><p>Здесь мы использовали ключевое слово const, чтобы показать, что оно не защищает вас от изменения объектов. Объекты определяются их ссылкой, поэтому хоть const и не даёт переназначить arr, вы всё ещё можете его изменить.</p><p>Чтобы убедиться, что мы не передаём ссылку объекта, а копируем его, мы используем Object.create().</p><p>Как и с кубиками Лего, мы можем создавать копии одного и того же объекта, настраивать их, совмещать и передавать другим объектам для увеличения их возможностей.</p><p>В качестве примера определим объект Customer с данными и функциями. Когда наш пользователь (User) станет клиентом (Customer), мы хотим добавить к объекту user всё, что есть в Customer:</p><p>Теперь мы можем добавить в объект user методы и поля Customer:</p><p>После выполнения этих двух строк объект user будет выглядеть так:</p><p>Когда мы захотим добавить ещё больше возможностей, объекты высшего уровня нам с этим всегда помогут.</p><p>Как показано в примере выше, классовому наследованию нам стоит предпочесть композицию, так как она проще, выразительнее и более гибкая.</p><h2>Заключение</h2><p>Программистам часто приходится искать компромисс между повторным использованием кода и его масштабируемостью. Вероятно, использование классового ООП имеет смысл для корпоративного ПО, так как оно не сильно меняется. Поведение в ООП чётко прописано в абстрактных классах, но его можно в какой-то степени настроить во время создания экземпляров класса. Это способствует лучшему повторному использованию кода, что экономит разработчикам много времени. Тем не менее, если вы ожидаете, что в будущем много раз придётся дополнять код и даже пересматривать проект, тогда ООП в итоге будет мешать продуктивности разработчика и код станет нетестируемым и сильно связанным со средой.</p>]]></content:encoded>
    </item>
    <item>
      <title>Курс «Объектно-Ориентированное Программирование»</title>
      <link>https://tproger.ru/video/oop-introduction</link>
      <comments>https://tproger.ru/video/oop-introduction?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Лапа]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/video/oop-introduction</guid>
      <description><![CDATA[<p>Видеокурс объясняет базовые понятия ООП, ключевые концепции и их реализацию в отдельных языках программирования; многие примеры даны на C++.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/video/oop-introduction">Курс «Объектно-Ориентированное Программирование»</a>»</p>]]></description>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Обучающие курсы]]></category>
      <category><![CDATA[Видео]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 02 May 2017 19:47:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>В этом видеокурсе описаны основные аспекты объектно-ориентированного программирования — наиболее широко распространенной сегодня парадигмы программирования. Автор курса — <a href="https://www.youtube.com/user/VladimirMozhenkov/">Владимир Моженков</a>, преподаватель со стажем работы в России и Британии.</p><p>В процессе просмотра курса вы узнаете о базовых понятиях ООП, а затем перейдете к рассмотрению интересных концепций парадигмы и реализации их в отдельных языках. В данном курсе многие примеры даны на языке C++. Кстати, одна из рассматриваемых тем — возможности ООП в перегрузке операторов, а что это такое, можно <a href="https://tproger.ru/translations/cpp-operator-overload-p1/">прочитать</a> у нас на сайте.</p><p>Если же вы пишете на C#, то вам может пригодиться <a href="https://tproger.ru/tag/oop/">наша серия статей</a> об ООП в этом языке.</p>]]></content:encoded>
    </item>
    <item>
      <title>Задача на перегрузку функций в C++, которая может оказаться сложнее, чем выглядит</title>
      <link>https://tproger.ru/problems/cpp-function-override</link>
      <comments>https://tproger.ru/problems/cpp-function-override?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Антон Корольков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/problems/cpp-function-override</guid>
      <description><![CDATA[<p>Два класса и два фрагмента кода: разбор механизма перегрузки функций и скрытия имён, из-за которого компилятор выдаёт неожиданную ошибку.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/problems/cpp-function-override">Задача на перегрузку функций в C++, которая может оказаться сложнее, чем выглядит</a>»</p>]]></description>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Задачки]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 25 Jan 2017 20:08:10 GMT</pubDate>
      <content:encoded><![CDATA[<p>Предположим, у нас есть два класса:</p><p>Что выведут два следующих куска кода и почему?</p><h3>Решение</h3><p>Не все так просто, как кажется на первый взгляд. Если для вас эта задача показалась легкой, то проверьте свои навыки в C++, прочитав решение.</p><ul><li>В первом случае программа завершится с ошибкой.</li><li>Во втором случае выведется «Родительский класс».</li></ul><p>Мы имеем дело с механизмом перегрузки функций и скрытия имен. В первом случае функция внутри производного класса переопределит родительские функции вне зависимости от их сигнатуры. Поэтому, несмотря на то, что в родительском классе имеется функция, соответствующая вызываемой внутри main(), компилятор об этом не узнает и выдаст ошибку</p><p>Почему же во втором случае мы не получаем ошибку, хотя также используем объект Derived для вызова print()?</p><p>Ключевым моментом здесь является то, что поиск имени начинается с класса, указанного в типе переменной, а не фактического типа объекта. Переменная derived типа Parent указывает на объект типа Derived, поэтому изначально поиск функции print() будет производиться внутри класса Parent. Вследствие этого компиляция завершается успешно и мы получаем соответствующий вывод.</p>]]></content:encoded>
    </item>
    <item>
      <title>Введение в ООП с примерами на C#. Часть пятая. Всё о модификаторах доступа</title>
      <link>https://tproger.ru/translations/diving-in-oop-p5</link>
      <comments>https://tproger.ru/translations/diving-in-oop-p5?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Пётр Соковых]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/diving-in-oop-p5</guid>
      <description><![CDATA[<p>Модификаторы доступа задают параметры доступа для классов, методов и прочих элементов и облегчают инкапсуляцию. Продолжение серии Akhil Mittal об ООП.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/diving-in-oop-p5">Введение в ООП с примерами на C#. Часть пятая. Всё о модификаторах доступа</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 22 Aug 2016 18:08:25 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Akhil Mittal</p><p>В прошлых статьях серии “Введение в ООП” мы рассматривали <a href="https://tproger.ru/translations/diving-in-oop-p1/">полиморфизм</a> (а также <a href="https://tproger.ru/translations/diving-in-oop-p3">нюансы</a> использования его на практике), <a href="https://tproger.ru/translations/diving-in-oop-p2/">наследование</a> и <a href="https://tproger.ru/translations/diving-in-oop-p4">абстрактные классы</a>. В этой части я постараюсь раскрыть все тонкости использования модификаторов доступа, которые знаю сам. Продолжаем погружаться в ООП!</p><h3>Что такое модификаторы доступа?</h3><p>Давайте в этот раз возьмём определение из Википедии (в русской Википедии статьи access modifiers нет, поэтому здесь приводим свой перевод — прим. перев.):</p><blockquote>Модификаторы доступа (или спецификаторы доступа) — ключевые слова в объектно-ориентированных языках, которые задают (внезапно!) параметры доступа для классов, методов и прочих элементов. Модификаторы доступа — специфичная часть языков программирования для облегчения инкапсуляции компонентов.</blockquote><h3>Модификаторы public, private, protected</h3><p>Каждый раз, когда мы создаём класс, мы хотим иметь возможность определять, кто и откуда может взаимодействовать с его членами. Иными словами, нам иногда нужно ограничивать доступ к некоторым членам класса. Есть одно простое правило — члены одного класса всегда имеют доступ друг к другу. Если же говорить про доступ извне, то стоит запомнить, что модификатор доступа по умолчанию — private, т.е. все члены класса доступны только изнутри него самого.</p><p>Традиционно сразу переходим к практике. Давайте попробуем выполнить следующий код:</p><p>Результатом выполнения этого кода будет:</p><blockquote>Modifiers BBB<br />Modifiers AAA</blockquote><p>BBB() отмечен как public, соответственно его можно вызывать откуда угодно. Метод AAA() же никак не отмечен, значит, он является приватным. Однако для члена того же класса (ведь AAA() и BBB() принадлежат одному классу, верно?) это не имеет никакого значения.</p><p>Теперь попробуем получить доступ к AAA() напрямую:</p><p>Вывод:</p><blockquote>‘AccessModifiers.Modifiers.AAA()’ is inaccessible due to its protection level</blockquote><p>Для внешних вызовов модификатор private — непреодолимая преграда. То же самое можно сказать и о модификаторе protected.</p><h3>Модификаторы доступа и наследование</h3><p>Снова попробуем выполнить код:</p><p>Запускаем код и видим…</p><blockquote>‘AccessModifiers.ModifiersBase.AAA()’ is inaccessible due to its protection level</blockquote><p>Приватные члены недоступны даже дочерним классам. Публичные члены доступны всем, это понятно. Модификатор же protected по сути и обозначает, что член доступен только дочерним классам — вызов CCC() в примере выше не вызывает никаких ошибок.</p><h3>Модификатор Internal для классов</h3><p>Давайте рассмотрим следующий сценарий: мы создаём в новой библиотеке классов (назовём её AccessModifiersLibrary) класс ClassA и помечаем его как internal:</p><p>Теперь в созданном ранее файле попробуем выполнить:</p><blockquote>Compile time error: ‘AccessModifiersLibrary.ClassA’ is inaccessible due to its protection level</blockquote><p>Мы встретили эту ошибку из-за спецификатора доступа internal, который обозначает, что ClassA доступен только внутри AccessModifiersLibrary и ниоткуда больше. Впрочем, если мы уберём этот модификатор, ничего не изменится — internal является спецификатором по умолчанию.</p><h3>Модификаторы для пространств имён</h3><p>Давайте попробуем сделать с предыдущим кодом следующее:</p><p>Конечно, это не скомпилируется:</p><blockquote>Compile time error: A namespace declaration cannot have modifiers or attributes</blockquote><p>Все пространства имён по умолчанию являются публичными, и мы не можем добавить к их объявлению никаких модификаторов, включая ещё один public.</p><h3>Приватные классы</h3><p>Если мы попробуем скомпилировать код, приведённый выше, то получим ошибку:</p><blockquote>Compile time error: Elements defined in a namespace cannot be explicitly declared as private, protected, or protected internal</blockquote><p>Всё правильно: классы могут быть либо public, либо internal.</p><h3>Подробнее о модификаторах членов класса</h3><p>Что будет, если мы захотим назначить члену класса больше одного модификатора доступа?</p><p>Будет ошибка компиляции:</p><blockquote>Compile time error: More than one protection modifier</blockquote><p>А как поведёт себя язык, если мы создадим public метод в internal классе?</p><p>Вывод после компиляции:</p><blockquote>‘AccessModifiersLibrary.ClassA’ is inaccessible due to its protection level</blockquote><p>Как много ошибок… Дело в том, что какими бы модификаторами не обладали члены internal класса, их всё равно нельзя вызвать оттуда, где не виден сам класс. А что будет, если мы попробуем сделать наоборот — вызвать private или internal метод у public класса?</p><blockquote>‘AccessModifiersLibrary.ClassA’ does not contain a definition for ‘MethodClassA’ and no extension method ‘MethodClassA’ accepting a first argument of type ‘AccessModifiersLibrary.ClassA’ could be found (are you missing a using directive or an assembly reference?)</blockquote><p>Не-а, всё равно не работает. А если изменим модификатор метода на internal?</p><blockquote>‘AccessModifiersLibrary.ClassA’ does not contain a definition for ‘MethodClassA’ and no extension method ‘MethodClassA’ accepting a first argument of type ‘AccessModifiersLibrary.ClassA’ could be found (are you missing a using directive or an assembly reference?)</blockquote><p>Увы, так делать тоже нельзя.</p><h3>Модификатор protected internal</h3><p>Этот код компилируется без ошибок. Модификатор internal proteted (как не слишком сложно догадаться) даёт понять, что метод доступен как для вызовов из того же файла, в котором он объявлен, так и для вызовов из дочерних классов.</p><h3>Protected поля</h3><p>Здесь всё будет немного сложнее. Давайте напишем следующий код:</p><p>Если мы его запустим, то получим ошибку:</p><blockquote>Cannot access protected member ‘AccessModifiers.AAA.a’ via a qualifier of type ‘AccessModifiers.AAA’; the qualifier must be of type ‘AccessModifiers.BBB’ (or derived from it)</blockquote><p>Совершенно неочевидно, правда? Компилятор ругается на строчку aaa.a = 100 из метода MethodBBB. Почему никаких ошибок не вызывает метод MethodAAA понять достаточно просто — поле a объявлено в том же файле, в том же классе, в котором к нему и происходит обращение, это не может быть ошибкой. Почему в классе BBB доступен член bbb.a тоже понятно — модификатор protected прямо разрешает использовать члены родительского класса в дочернем как свои. Почему же вызов aaa.a = 100 из метода MethodBBB под запретом? Пожалуй, это стоит просто запомнить.</p><p>(От редакции) Скорее всего, это сделано, чтобы нельзя было делать следующим образом:</p><h3>Приоритет модификаторов</h3><blockquote>Compile time error: Inconsistent accessibility: base class ‘AccessModifiers.AAA’ is less accessible than class ‘AccessModifiers.BBB’</blockquote><p>К дочернему классу не может быть большего доступа, чем к родительскому. Как вы понимаете, public предоставляет гораздо больший доступ, чем модификатор по умолчанию internal. Причём нельзя делать даже так:</p><blockquote>Inconsistent accessibility: return type ‘AccessModifiers.AAA’ is less accessible than method ‘AccessModifiers.BBB.MethodB()’</blockquote><p>Или так:</p><blockquote>Inconsistent accessibility: field type ‘AccessModifiers.AAA’ is less accessible than field ‘AccessModifiers.BBB.aaa’</blockquote><h3>Подведём итоги:</h3><ul><li>Модификатор доступа по умолчанию для членов класса — private;</li><li>Модификатор доступа internal значит, что доступ разрешён только из того же файла;</li><li>У пространств имён нет и не может быть модификаторов доступа (можно считать, что они все public);</li><li>Классы могут иметь только два модификатора доступа — internal (по умолчанию) и public</li><li>Модификатор protected internal значит, что доступ есть как из того же файла, так и из дочерних классов</li><li>Родительский класс не может быть менее доступен, чем дочерний</li><li>Возвращаемое значение метода не может быть менее доступно, чем сам метод</li><li>Поле не может быть более доступно, чем его тип</li></ul><p>Работу с константами и sealed классами (которая тоже осуществляется за счёт модификаторов доступа) мы разберём в следующей статье.</p>]]></content:encoded>
    </item>
    <item>
      <title>Введение в ООП с примерами на C#. Часть четвёртая. Абстрактные классы</title>
      <link>https://tproger.ru/translations/diving-in-oop-p4</link>
      <comments>https://tproger.ru/translations/diving-in-oop-p4?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Пётр Соковых]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/diving-in-oop-p4</guid>
      <description><![CDATA[<p>Модификатор abstract означает неполную или отсутствующую реализацию и применяется к классам, методам, свойствам, индексаторам и событиям в C#.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/diving-in-oop-p4">Введение в ООП с примерами на C#. Часть четвёртая. Абстрактные классы</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 13 Aug 2016 15:52:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Akhil Mittal</p><p>В прошлых статьях серии “Введение в ООП” мы рассматривали <a href="https://tproger.ru/translations/diving-in-oop-p1/">полиморфизм</a> (а также его <a href="https://tproger.ru/translations/diving-in-oop-p3">нюансы на практике</a>) и <a href="https://tproger.ru/translations/diving-in-oop-p2/">наследование</a>. В этой мы поговорим о самой захватывающей части ООП-парадигмы — об абстрактных классах. В целом концепция абстрактных классов в C# ничем не отличается от таковой в других языках, но в C# работать с ней приходится несколько иначе.</p><h3>Что такое абстрактные классы</h3><p>В плане терминологии давайте доверимся <a href="https://msdn.microsoft.com/ru-ru/library/sf985hc5.aspx">MSDN</a>:</p><blockquote>Модификатор abstract указывает, что реализация сущности с данным модификатором является неполной или отсутствует. Модификатор abstract может использоваться с классами, методами, свойствами, индексаторами и событиями. Модификатор abstract в объявлении класса указывает, что класс предназначен только для использования в качестве базового класса для других классов. Члены, помеченные как абстрактные или включенные в абстрактный класс, должны быть реализованы с помощью классов, производных от абстрактных классов.</blockquote><h3>Абстрактные классы в действии</h3><p>Итак, попробуем создать абстрактный класс:</p><p>Попытаемся скомпилировать этот код:</p><blockquote>Compile time error: Cannot create an instance of the abstract class or interface ‘InheritanceAndPolymorphism.ClassA’</blockquote><p>Что нужно запомнить: Мы не можем создать экземпляр абстрактного класса с помощью ключевого слова new.</p><h3>Описание методов в абстрактном классе</h3><p>Попробуем добавить в наш абстрактный класс немного кода:</p><p>С кодом класса никаких проблем нет, но скомпилировать снова не получается, потому что нельзя создавать экземпляры абстрактных классов.</p><h3>Использование абстрактного класса в качестве базового</h3><p>Давайте попробуем создать ещё один класс:</p><p>Вау. Теперь всё спокойно компилируется.</p><p>Что нужно запомнить: Мы можем унаследовать обычный класс от абстрактного.</p><p>Что нужно запомнить: Мы можем создать экземпляр обычного класса, унаследованного от абстрактного, с помощью ключевого слова new.</p><h3>Декларация методов в абстрактном классе</h3><p>Теперь попробуем сделать вот так:</p><p>И… мы получаем ошибку компиляции:</p><blockquote>Compile time error: ‘InheritanceAndPolymorphism.ClassA.YYY()’ must declare a body because it is not marked abstract, extern, or partial</blockquote><p>Дело в том, что если мы объявляем метод в абстрактном классе и при этом хотим, чтобы его конкретное поведение было определено в производных классах, то к такому методу мы должны так же добавить ключевое слово abstract. Добавим его:</p><p>…и снова получим ошибку компиляции:</p><blockquote>Compiler error: ‘InheritanceAndPolymorphism.ClassB’ does not implement inherited abstract member ‘InheritanceAndPolymorphism.ClassA.YYY()’</blockquote><p>Что нужно запомнить: Если мы хотим объявить метод в абстрактном классе, но не реализовывать его, к методу нужно добавить ключевое слово abstract.</p><p>Что нужно запомнить: Если мы объявляем абстрактный метод в абстрактном классе, то этот метод должен реализовываться в неабстрактных наследниках этого класса.</p><h3>Реализация абстрактного метода в производном классе</h3><p>Так, давайте тогда попробуем реализовать метод YYY() в классе ClassB:</p><p>На первый взгляд всё отлично, правда? Но на этот раз мы получим сразу две ошибки компиляции:</p><blockquote>Compile time error: ‘InheritanceAndPolymorphism.ClassB’ does not implement inherited abstract member ‘InheritanceAndPolymorphism.ClassA.YYY()’</blockquote><p>Дело в том, что в C# нужно явно объявить, что мы реализуем абстрактный метод класса-родителя с помощью ключевого слова override:</p><p>Ура! ^_^ У нас наконец-то нет никаких ошибок!</p><h3>Абстрактный метод базового класса и метод с override класса-наследника должны быть одинаковы</h3><p>Это значит, что мы не можем менять тип возвращаемого значения или аргументы, которые передаются в метод. Например, если мы напишем такое:</p><p>То в консоли увидим следующую ошибку:</p><blockquote>Compile time error: ‘InheritanceAndPolymorphism.ClassB.YYY()’: return type must be ‘void’ to match overridden member ‘InheritanceAndPolymorphism.ClassA.YYY()’</blockquote><h3>Инициализация переменных в абстрактных классах</h3><p>В примерах выше, переменная int a будет обладать значением по умолчанию (0). Мы можем изменить его на нужное нам прямо в абстрактном классе — с этим не связано никаких особенностей.</p><h3>Абстрактные методы в неабстрактных классах</h3><p>Такой код не скомпилируется:</p><blockquote>Compiler error: ‘InheritanceAndPolymorphism.ClassA.YYY()’ is abstract but it is contained in non-abstract class ‘InheritanceAndPolymorphism.ClassA’</blockquote><p>Что нужно запомнить: Абстрактные методы могут быть объявлены только в абстрактных классах.</p><h3>Вызов абстрактного метода родителя</h3><p>Вывод:</p><blockquote>Compile time error : Cannot call an abstract base member: ‘InheritanceAndPolymorphism.ClassA.YYY()’</blockquote><p>Разумеется, мы не можем исполнить код, которого не существует.</p><h3>Абстрактный класс, который наследуется от другого абстрактного класса</h3><p>Вывод:</p><blockquote>ClassA XXX ClassC XXX</blockquote><p>Почему вывод именно такой, вы должны понимать из материалов <a href="https://tproger.ru/translations/diving-in-oop-p3">третьей статьи</a> этой серии.</p><h3>Может ли абстрактный класс быть sealed</h3><p>Проверим:</p><p>И получим ошибку:</p><blockquote>Compile time error: ‘InheritanceAndPolymorphism.ClassA’: an abstract class cannot be sealed or static</blockquote><p>Разумеется, абстрактный класс не может быть sealed, т.к. он для того и создан, чтобы от него создавались производные классы.</p><p>Что нужно запомнить: Абстрактный класс не может иметь модификатор sealed.</p><p>Что нужно запомнить: Абстрактный класс не может иметь модификатор static.</p><h3>Что мы узнали сегодня:</h3><p>Что нужно запомнить</p><ul><li>Мы не можем создать экземпляр абстрактного класса с помощью ключевого слова new;</li><li>Мы можем унаследовать обычный класс от абстрактного;</li><li>Мы можем создать экземпляр обычного класса, унаследованного от абстрактного, с помощью ключевого слова new;</li><li>Если мы хотим объявить метод в абстрактном классе, но не реализовывать его, к методу нужно добавить ключевое слово abstract;</li><li>Если мы объявляем абстрактный метод в абстрактном классе, то этот метод должен реализовываться в неабстрактных наследниках этого класса;</li><li>Абстрактные методы могут быть объявлены только в абстрактных классах;</li><li>Абстрактный класс не может иметь модификатор sealed;</li><li>Абстрактный класс не может иметь модификатор static.</li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Введение в ООП с примерами на C#. Часть третья. Практические аспекты использования полиморфизма</title>
      <link>https://tproger.ru/translations/diving-in-oop-p3</link>
      <comments>https://tproger.ru/translations/diving-in-oop-p3?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Пётр Соковых]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/diving-in-oop-p3</guid>
      <description><![CDATA[<p>Практические нюансы полиморфизма в C# вместо теории: ключевые слова new и override, два класса с одноимёнными методами и запуск кода.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/diving-in-oop-p3">Введение в ООП с примерами на C#. Часть третья. Практические аспекты использования полиморфизма</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 04 Aug 2016 17:42:44 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает <a href="https://www.codeproject.com/script/Membership/View.aspx?mid=7869570">Akhil Mittal</a></p><h3>Введение</h3><p>Раньше в этой серии мы говорили о <a href="https://tproger.ru/translations/diving-in-oop-p1/">полиморфизме</a> и <a href="https://tproger.ru/translations/diving-in-oop-p2/">наследовании</a>.</p><p>В этой статье мы опять будем говорить о полиморфизме, но в этот раз сосредоточимся именно на практических нюансах, а не на теории. Если вы овладеете технологией, описанной в этой статье, то считайте, что изучили 50% ООП.</p><h3>Ключевые слова New и Override в C#</h3><p>Для начала создадим новое консольное приложение и два класса в нём:</p><p>Мы видим, что эти классы содержат три метода с попарно одинаковыми именами. Теперь выполним следующий код из Program.cs:</p><p>Жмём F5, т.е. выполняем код, и что мы видим?</p><p>Но кроме вывода, мы получили ещё и три предупреждения от компилятора:</p><p>Что нужно запомнитьи&gt;: мы можем записать в переменную класса-родителя объект наследника, но не наоборот.</p><p>ClassA — родитель ClassB. То есть ClassB содержит то, что находится в ClassA и ещё что-то своё. В этом причина правила, которое мы записали выше: класс-родитель не содержит описания всех необходимых полей и методов класса-наследника, поэтому мы не можем использовать ClassA как ClassB.</p><p>Теперь посмотрим, что у нас происходит в коде. С x и y всё понятно: они объявлены и инициализированны одним и тем же типом. Рассмотрим подробнее z. Эта переменная типа ClassB, а её значение — объект типа ClassA, хотя в данном контексте нет никакой разницы, какого типа её значение, вывод всегда будет аналогичен выводу от y. Выбор метода по типу ссылки, а не по типу объекта — это стандартное поведение, когда явно не указан приоритет методов, о чём свидетельствуют warning’и. Как же описать требуемое поведение? Здесь нам как раз помогут ключевые слова new и override.</p><h3>Давайте проведём эксперимент</h3><p>Добавим к двум методам из ClassB ключевые слова new и override следующим образом:</p><p>Если мы сейчас выполним Program.cs, то на выходе получим:</p><p>Ошибка возникает из-за того, что поля родителя не помечены ключевым словом virtual. Этот модификатор обозначает, что мы имеем право вызывать метод из дочернего класса или перезаписывать его. Добавим virtual ко всем методам ClassA:</p><p>И снова запустим Program.cs:</p><p>Очевидно, что метод дочернего класса вызвался только там, где стоял модификатор override. В свзяи с чем делаем вывод: override значит, что помеченный метод — новая версия родительского и должен использоваться вместо него. И, напротив, new обозначает, что метод, хоть и случайно имеет такое же имя, является по сути абсолютно другим, а значит, в нашем примере должен выполняться метод родительского класса. Если мы не пишем никакого модификатора, мы подразумеваем именно new.<br />Разберём подробнее логику C#. Когда вызывается метод какого-то объекта по ссылке, то в первую очередь он смотрит на тип ссылки. Если в этом классе обнаружен модификатор virtual, он начинает искать среди дочерних классов тип объекта, и, если встречает new, запускает последний override метод, который встретил (либо метод типа ссылки). Возможно, это не слишком понятно, обратимся к более сложному примеру.</p><h3>Эксперимент с тремя классами</h3><p>Результатом такого эксперимента станет:</p><p>В первом случае мы имеем дело с типом ссылки ClassA и типом объекта ClassB. Компилятор действует вполне очевидно:</p><p>Во втором случае тип объекта у нас уже ClassC. Поскольку он наследуется от ClassA не напрямую, а через ClassB, наша диаграмма будет уже несколько сложнее.</p><p>В третьем случае мы имеем дело снова с двумя классами, ClassA мы просто игнорируем. Если учесть это, то вывод будет очевиден, но всё же вот схема:</p><p>Важным замечанием будет, что следующий код:</p><p>Выдаст ошибку:</p><p>Ведь из-за того, что в B метод помечен как new, он не наследует свойство virtual, а значит не может быть перезаписан с помощью override в C. Правильным вариантом было бы добавить к описанию метода в B ключевое слово virtual или изменить в C override на new, в зависимости от требуемого поведения.</p><h3>Ключевое слово base</h3><p>Теперь, когда мы разобрались с самой сложной частью, стоит напомнить про возможность вызова методов родительского класса из дочернего. Простой пример:</p><p>Даст нам вывод:</p><p>В первом случае выполняется метод ClassB, который через base вызывает метод из ClassA. Во втором — XXX() из ClassC, который обращается к ClassB, а тот, в свою очередь, к ClassA.</p><h3>Немного рекурсии</h3><p>В этом примере вызов ClassB.XXX() всегда будет приводить к созданию нового объекта типа ClassB в ссылке ClassA. Очевидно, что по такой ссылке снова будет вызван ClassB.XXX() и т.д. В данном случае выводом будет ошибка:</p><h3>В заключение</h3><p>Подведём итоги:</p><ul><li>В C# мы можем записать в переменную класса-родителя объект наследника, но не наоборот;</li><li>Модификатор override используется, чтобы указать на то, что должен вызваться метод именно дочернего класса;</li><li>Чтобы использовать модификаторы override и new, метод родительского класса должен быть помечен ключевым словом virtual;</li><li>Когда вызывается метод какого-то объекта по ссылке, то С# в первую очередь смотрит на тип ссылки. Если в этом классе обнаружен модификатор virtual, он начинает искать среди дочерних классов тип объекта, и, если встречает new, запускает последний override метод, который встретил (либо метод типа ссылки).</li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Введение в ООП с примерами на C#. Часть первая. Все, что нужно знать о полиморфизме</title>
      <link>https://tproger.ru/translations/diving-in-oop-p1</link>
      <comments>https://tproger.ru/translations/diving-in-oop-p1?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Пётр Соковых]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/diving-in-oop-p1</guid>
      <description><![CDATA[<p>Гайд для новичков — что такое ООП, полиморфизм и параметры и как написать код</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/diving-in-oop-p1">Введение в ООП с примерами на C#. Часть первая. Все, что нужно знать о полиморфизме</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 14 Jul 2016 18:00:08 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает <a href="http://www.codeproject.com/script/Membership/View.aspx?mid=7869570">Akhil Mittal</a></p><p>Я много писал на смежные темы, вроде концепции MVC, Entity Framework, паттерна «Репозиторий» и т.п. Моим приоритетом всегда было полное раскрытие темы, чтобы читателю не приходилось гуглить недостающие детали. Этот цикл статей опишет абсолютно все концепции ООП, которые могут интересовать начинающих разработчиков. Однако эта статья предназначена не только для тех, кто начинает свой путь в программировании: она написана и для опытных программистов, которым может потребоваться освежить свои знания.</p><p>Сразу скажу, далеко в теорию мы вдаваться не будем — нас интересуют специфичные вопросы. Где это будет нужно, я буду сопровождать повествование кодом на C#.</p><h3>Что такое ООП и в чём его плюсы?</h3><p>«ООП» значит «Объектно-Ориентированное Программирование». Это такой подход к написанию программ, который основывается на объектах, а не на функциях и процедурах. Эта модель ставит в центр внимания объекты, а не действия, данные, а не логику. Объект — реализация класса. Все реализации одного класса похожи друг на друга, но могут иметь разные параметры и значения. Объекты могут задействовать методы, специфичные для них.</p><p>ООП сильно упрощает процесс организации и создания структуры программы. Отдельные объекты, которые можно менять без воздействия на остальные части программы, упрощают также и внесение в программу изменений. Так как с течением времени программы становятся всё более крупными, а их поддержка всё более тяжёлой, эти два аспекта ООП становятся всё более актуальными.</p><h3>Что за концепции ООП?</h3><p>Сейчас коротко о принципах, которые мы позже рассмотрим в подробностях:</p><ul><li>Абстракция данных: подробности внутренней логики скрыты от конечного пользователя. Пользователю не нужно знать, как работают те или иные классы и методы, чтоб их использовать. Подходящим примером из реальной жизни будет велосипед — когда мы ездим на нём или меняем деталь, нам не нужно знать, как педаль приводит его в движение или как закреплена цепь.</li><li>Наследование: самый популярный принцип ООП. Наследование делает возможным повторное использование кода — если какой-то класс уже имеет какую-то логику и функции, нам не нужно переписывать всё это заново для создания нового класса, мы можем просто включить старый класс в новый, целиком.</li><li>Инкапсуляция: включение в класс объектов другого класса, вопросы доступа к ним, их видимости.</li><li>Полиморфизм: «поли» значит «много», а «морфизм» — «изменение» или «вариативность», таким образом, «полиморфизм» — это свойство одних и тех же объектов и методов принимать разные формы.</li><li>Обмен сообщениями: способность одних объектов вызывать методы других объектов, передавая им управление.</li></ul><p>Ладно, тут мы коснулись большого количества теории, настало время действовать. Я надеюсь, это будет интересно.</p><h3>Полиморфизм</h3><p>В этой статье мы рассмотрим буквально все сценарии использования полиморфизма, использование параметров и разные возможные типы мышления во время написания кода.</p><h4>Перегрузка методов</h4><p>1. Давайте создадим консольное приложение InheritanceAndPolymorphism и класс Overload.cs с тремя методами DisplayOverload с параметрами, как ниже:</p><p>В главном методе Program.cs теперь напишем следующее:</p><p>И теперь, когда мы это запустим, вывод будет следующим:</p><p>Класс Overload содержит три метода, и все они называются DisplayOverload, они различаются только типами параметров. В C# (как и в большистве других языков) мы можем создавать методы с одинаковыми именами, но разными параметрами, это и называется «перегрузка методов». Это значит, что нам нет нужды запоминать кучу имён методов, которые совершают одинаковые действия с разными типами данных.Что нужно запомнить: метод идентифицируется не только по имени, но и по его параметрам.</p><p>2. Если же мы запустим следующий код:</p><p>Мы получим ошибку компиляции:</p><p>Здесь вы можете видеть две функции, которые различаются только по возвращаемому типу, и скомпилировать это нельзя. Что нужно запомнить: метод не идентифицируется по возвращаемому типу, это не полиморфизм.</p><p>3. Если мы попробуем скомпилировать:</p><p>У нас это тоже не получится:</p><p>Здесь присутствуют два метода, принимающих целое число в качестве аргумента, с той лишь разницей, что один из них помечен как статический. Что нужно запомнить: модификаторы вроде static также не являются свойствами, идентифицирующими метод.</p><p>4. Если мы запустим нижеследующий код в надежде, что теперь-то идентификаторы у методов будут разными:</p><p>То нас опять ждёт разочарование:</p><p>Что нужно запомнить: на идентификатор метода оказывают влияние только его имя и параметры (их тип, количество). Модификаторы доступа не влияют. Двух методов с одинаковыми идентификаторами существовать не может.</p><h4>Роль ключевого слова params в полиморфизме</h4><p>Параметры могут быть четырёх разных видов:</p><ul><li>переданное значение;</li><li>переданная ссылка;</li><li>параметр для вывода;</li><li>массив параметров.</li></ul><p>С первыми тремя мы, вроде, разобрались, теперь подробнее взглянем на четвёртый.</p><ul><li>Если мы запустим следующий код:</li></ul><p>То получим две ошибки:</p><p><b>Отсюда следуют вывод: имена параметров должны быть уникальны. Также не могут быть одинаковыми имя параметра метода и имя переменной, созданной в этом же методе.</b></p><ul><li>Теперь попробуем запустить следующий код:</li></ul><p>Мы получим следующий вывод:</p><p>Мы можем передавать одинаковые ссылочные параметры столько раз, сколько захотим. В методе Display строка name имеет значение «Akhil». Когда мы меняем значение x на «Akhil1», на самом деле мы меняем значение name, т.к. через параметр x передана ссылка именно на него. То же и с y — все эти три переменных ссылаются на одно место в памяти.</p><ul><li>Теперь самое интересное:</li></ul><p>Это даст нам такой вывод:</p><p>Нам часто может потребоваться передать методу n параметров. В C# такую возможность предоставляет ключевое слово params.</p><p><b>Важно</b>: это ключевое слово может быть применено только к последнему аргументу метода, так что метод ниже работать не будет:</p><p>1. В случае DisplayOverload первый аргумент должен быть целым числом, а остальные — сколь угодно много строк или наоборот, ни одной.</p><p>Вывод:<b> 200 100300 100100 200</b></p><p>Важно запомнить: C# достаточно умён, чтоб разделить обычные параметры и массив параметров, даже если они одного типа.</p><p>2. Посмотрите на следующие два метода:</p><p>Разница между ними в том, что первый запустится, и такая синтаксическая конструкция будет подразумевать, что в метод будет передаваться n массивов строк. Вторая же выдаст ошибку:</p><p>Запомните: массив параметров должен быть одномерным.</p><p>3. Следует упомянуть, что последний аргумент не обязательно заполнять отдельными объектами, можно его использовать, будто это обычный аргумент, принимающий массив, то есть:</p><p>Вывод будет следующим:</p><p>Однако такой код:</p><p>Уже вызовет ошибку:</p><p>Думаю, тут всё понятно — или, или. Смешивать передачу отдельными параметрами и одним массивом нельзя.</p><ul><li>Теперь рассмотрим поведение следующей программы:</li></ul><p>После её выполнения мы получим в консоли: <b>1000</b></p><p><b></b><br />Это происходит из-за того, что при подобном синтаксисе массив передаётся по ссылке. Однако стоит отметить следующую особенность:</p><p>Результатом выполнения такого кода будет<br /><b>102</b><br />Ведь из переданных параметров C# автоматически формирует новый, временный массив.</p><ul><li>Теперь поговорим о приоритете языка в выборе методов. Предположим, у нас есть такой код:</li></ul><p>C# рассматривает методы с массивом параметров последними, так что во втором случае будет вызван метод, принимающий два целых числа. В первом и третьем случае будет вызван метод с params, так как ничего кроме него запустить невозможно. Таким образом, на выходе мы получим:</p><ul><li>Теперь кое-что интересное. Как вы думаете, каким будет результат выполнения следующей программы?</li></ul><p>В консоли мы увидим:</p><p>То есть, в первом и в четвёртом случаях массив передаётся именно как массив, заменяя собой objectParamArray, а во втором и третьем случаях массив передаётся как единичный объект, из которого создаётся новый массив из одного элемента.</p><h3>В заключение</h3><p>В этой статье мы рассмотрели перегрузку методов, особенности компиляции, с ней связанные, и буквально все возможные случаи использования ключевого слова params. В следующей мы рассмотрим наследование. Напоследок ещё раз повторим основные пункты, которые нужно запомнить:</p><ul><li>Метод идентифицируется не только по имени, но и по его параметрам.</li><li>Метод не идентифицируется по возвращаемому типу.</li><li>Модификаторы вроде static также не являются свойствами, идентифицирующими метод.</li><li>На идентификатор метода оказывают влияние только его имя и параметры (их тип, количество). Модификаторы доступа не влияют. Двух методов с одинаковыми идентификаторами существовать не может.</li><li>Имена параметров должны быть уникальны. Также не могут быть одинаковыми имя параметра метода и имя переменной, созданной в этом же методе.</li><li>Ключевое слово params может быть применено только к последнему аргументу метода.</li><li>C# достаточно умён, чтоб разделить обычные параметры и массив параметров, даже если они одного типа.</li><li>Массив параметров должен быть одномерным.</li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Шпаргалка по принципам ООП</title>
      <link>https://tproger.ru/translations/oop-principles-cheatsheet</link>
      <comments>https://tproger.ru/translations/oop-principles-cheatsheet?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Алексей Михайлишин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/oop-principles-cheatsheet</guid>
      <description><![CDATA[<p>Шпаргалка по принципам ООП: абстракция, полиморфизм, наследование, инкапсуляция и SOLID. Краткие определения и практические советы для начинающих разработчиков.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/oop-principles-cheatsheet">Шпаргалка по принципам ООП</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Лучшая практика]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Шпаргалки]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 24 Aug 2015 16:40:26 GMT</pubDate>
      <content:encoded><![CDATA[<p><a href="https://tproger.ru/articles/kak-stat-programmistom/">Чтобы стать программистом</a>, нужно знать принципы ООП как Отче наш. Держите структурированную шпаргалку по объектно-ориентированному программированию.</p><p>• Четыре базовых принципа ООП: абстракция, полиморфизм, наследование и инкапсуляция
• Вместе с наследованием используйте делегацию, композицию и агрегацию
• Принципы SOLID помогают проектировать гибкие и поддерживаемые системы
• Правило DRY: каждая часть логики должна существовать в единственном месте</p><h2>Главное</h2><ul><li>Инкапсулируйте все, что может изменяться;</li><li>Уделяйте больше внимания интерфейсам, а не их реализациям;</li><li>Каждый класс в вашем приложении должен иметь только одно назначение;</li><li>Классы — это их поведение и функциональность.</li></ul><h2>Базовые принципы ООП</h2><ul><li>Абстракция — отделение концепции от ее экземпляра;</li><li>Полиморфизм — реализация задач одной и той же идеи разными способами;</li><li>Наследование — способность объекта или класса базироваться на другом объекте или классе. Это главный механизм для повторного использования кода. Наследственное отношение классов четко определяет их иерархию;</li><li>Инкапсуляция — размещение одного объекта или класса внутри другого для разграничения доступа к ним.</li></ul><h2>Используйте следующее вместе с наследованием</h2><ul><li>Делегация — перепоручение задачи от внешнего объекта внутреннему;</li><li>Композиция — включение объектом-контейнером объекта-содержимого и управление его поведением; последний не может существовать вне первого;</li><li>Агрегация — включение объектом-контейнером ссылки на объект-содержимое; при уничтожении первого последний продолжает существование.</li></ul><h2>Не повторяйся (Don't repeat yourself — DRY)</h2><p>Избегайте повторного написания кода, вынося в абстракции часто используемые задачи и данные. Каждая часть вашего кода или информации должна находиться в единственном числе в единственном доступном месте. Это один из <a href="https://tproger.ru/articles/how-to-write-readable-code/">принципов читаемого кода</a>.</p><h2>Принцип единственной обязанности</h2><p>Для каждого класса должно быть определено единственное назначение. Все ресурсы, необходимые для его осуществления, должны быть инкапсулированы в этот класс и подчинены только этой задаче.</p><h2>Принцип открытости/закрытости</h2><p>Программные сущности должны быть открыты для расширения, но закрыты для изменений.</p><h2>Принцип подстановки Барбары Лисков</h2><p>Методы, использующие некий тип, должны иметь возможность использовать его подтипы, не зная об этом.</p><h2>Принцип разделения интерфейсов</h2><p>Предпочтительнее разделять интерфейсы на более мелкие тематические, чтобы реализующие их классы не были вынуждены определять методы, которые непосредственно в них не используются.</p><h2>Принцип инверсии зависимостей</h2><p>Система должна конструироваться на основе абстракций "сверху вниз": не абстракции должны формироваться на основе деталей, а детали должны формироваться на основе абстракций.</p><h2>Часто задаваемые вопросы</h2><h3>Чем отличается инкапсуляция от абстракции?</h3><p>Абстракция скрывает сложность, выделяя только существенные характеристики объекта. Инкапсуляция скрывает внутреннюю реализацию, ограничивая прямой доступ к данным через модификаторы доступа (private, protected). Абстракция отвечает на вопрос «что делает объект», а инкапсуляция — «как именно он это делает и кто имеет доступ».</p><h3>Когда использовать наследование, а когда композицию?</h3><p>Наследование подходит, когда между классами есть отношение «является» (is-a): например, Кошка является Животным. Композицию выбирают при отношении «содержит» (has-a): Автомобиль содержит Двигатель. На практике композиция предпочтительнее, потому что она даёт более слабую связанность и гибкость при изменениях.</p><h3>Что такое SOLID простыми словами?</h3><p>SOLID — это пять принципов проектирования, которые помогают писать поддерживаемый код. S — один класс решает одну задачу. O — расширяйте классы, не меняя их код. L — подклассы должны работать везде, где работает родительский класс. I — лучше несколько маленьких интерфейсов, чем один большой. D — зависьте от абстракций, а не от конкретных реализаций.</p><p>Перевод статьи «Object-Orientated Design Principles»</p>]]></content:encoded>
    </item>
    <item>
      <title>Введение в ООП с примерами на C#. Часть вторая. Все, что нужно знать о наследовании</title>
      <link>https://tproger.ru/translations/diving-in-oop-p2</link>
      <comments>https://tproger.ru/translations/diving-in-oop-p2?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Пётр Соковых]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/diving-in-oop-p2</guid>
      <description><![CDATA[<p>Akhil Mittal продолжает серию после разбора перегрузки: тезисы о наследовании и консольное приложение InheritanceAndPolymorphism с двумя классами.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/diving-in-oop-p2">Введение в ООП с примерами на C#. Часть вторая. Все, что нужно знать о наследовании</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 14 Jul 2015 22:28:01 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает <a href="http://www.codeproject.com/script/Membership/View.aspx?mid=7869570">Akhil Mittal</a></p><h3>Вступление</h3><p><a href="https://tproger.ru/translations/diving-in-oop-p1">В первой статье</a> этой серии мы рассматривали работу разных вариантов реализации перегрузки. В этой части мы сосредоточимся на таком разделе объектно-ориентированного программирования, как наследование.</p><p>Давайте сразу тезисно опишем, что такое наследование:</p><ul><li>Это механизм создания нового класса на основе уже существующего старого.</li><li>Старый класс называется «родительским», «предком» («super class»).</li><li>Новый класс называется «дочерним», «наследником» («sub class»).</li><li>Наследование нужно для повторного использования кода, которое облегчает следование принципу DRY (Don’t Repeat Yourself — Не повторяйся).</li><li>Дочерний класс содержит методы и переменные родительского.</li></ul><h3>Рассмотрим наследование в действии</h3><p>Создайте консольное приложение и назовите его InheritanceAndPolymorphism. Добавьте два класса, с названиями ClassA и ClassB, как показано ниже:</p><p>Как вы можете видеть, класс A пуст, а в B мы добавили два метода и переменную x со значением 100.</p><p>Теперь в главном методе Program.cs напишите следующее:</p><p>Разумеется, этот код вызовет ошибку:</p><p>Error: ‘InheritanceAndPolymorphism.ClassA’ does not contain a definition for ‘Display1’ and no extension method ‘Display1’ accepting a first argument of type ‘InheritanceAndPolymorphism.ClassA’ could be found (are you missing a using directive or an assembly reference?)</p><p>Очевидно, причина в том, что в классе <b>А</b> нет метода, который мы вызываем. Однако он есть у класса <b>B</b>. Было бы здорово, если бы мы могли получить доступ ко всему коду в <b>B</b> из <b>A</b>!</p><p>Теперь измените описание первого класса на следующее:</p><p>Теперь после выполнения программы мы получим:</p><p>ClassB Display1</p><p>Т.е. теперь ClassA наследует публичные методы из ClassB, это то же самое, если бы мы скопировали весь код из <b>B</b> в <b>A</b>. Всё, что объект класса <b>B</b> может делать, может и объект класса <b>A</b>. ClassA — дочерний класс, а ClassB — родительский.</p><p>Что нужно запомнить: как сын получается похожим на отца, наследует его черты, так и дочерний класс имеет параметры родительского.</p><p>Теперь давайте представим, что ClassA тоже имеет метод Display1:</p><p>Что будет, если мы запустим код теперь? Каким будет вывод? И будет ли вывод вообще или выйдет ошибка компиляции? Давайте проверим.</p><p>ClassA Display1</p><p>Однако мы также получим предупреждение:</p><p>Warning: ‘InheritanceAndPolymorphism.ClassA.Display1()’ hides inherited member ‘InheritanceAndPolymorphism.ClassB.Display1()’. Use the new keyword if hiding was intended.</p><p>Что нужно запомнить: ничто не может помешать создать в дочернем классе такой же метод, как и в родительском.</p><p>Когда мы вызываем a.Display1(), C# сначала ищет Display1() в ClassA, а только потом в <b>ClassB</b>. Поскольку в <b>A</b> такой метод есть, вызывается именно он.</p><p>Что нужно запомнить: методы дочерних классов имеют приоритет при выполнении.</p><p>Такая возможность нам даётся для того, чтобы мы могли изменить поведение методов предка, если оно нас не устраивает. Однако мы всё равно можем вызывать методы родительского класса следующим образом:</p><p>В таком случае вывод будет:</p><p>Что нужно запомнить: ключевое слово base может быть использовано для обращения к методам класса-предка.</p><p>Что же, вверх по иерархии мы обращаться можем. Давайте попробуем сделать наоборот:</p><p><b>Что нужно запомнить</b>: наследование не работает в обратном направлении.</p><p>Когда класс <b>A</b> наследуется от B, он получает все его методы и может их использовать. Однако методы, которые были добавлены в <b>A</b>, не загружаются наверх в B, наследование не имеет обратной совместимости. Если попытаться вызвать из класса-родителя метод, который создан в классе-наследнике, вы получите ошибку.</p><p><b>Что нужно запомнить</b>: кроме конструкторов и деструкторов, дочерний класс получает от родителя абсолютно всё.</p><p>Если класс <b>С</b>, будет унаследован от класса <b>B</b>, который, в свою очередь, будет унаследован от класса <b>A</b>, то класс <b>C</b> унаследует члены как от класса <b>B</b>, так и от класса <b>A</b>. Это транзитивное свойство наследования. Потомок перенимает все члены родителей и не может исключить какие-либо. Он может «спрятать» их, создав свой метод с тем же именем. Конечно, это никак не повлияет на родительский класс, просто в дочернем метод не будет виден.</p><p>Члены класса могут быть двух типов — статический, который принадлежит именно классу, или обычный, который доступен только из реализаций класса (его объектов). Чтобы сделать член статическим мы должны использовать ключевое слово static.</p><p>Если мы не наследуем класс ни от какого другого, подразумевается, что мы наследуем его от класса object. Это — родитель всех классов, и он единственный не унаследован ни от чего. Таким образом, такой код:</p><p>Автоматически воспринимается C# так:</p><p>Таким образом, по свойству транзитивности, ClassA также является наследником object.</p><p>Теперь ещё один момент. Если мы захотим сделать так:</p><p>То у нас это не получится:</p><p>Заметили словосочетание «special class»? Такие классы нельзя расширять.</p><p><b>Что нужно запомнить</b>: ваши классы не могут быть унаследованы от встроенных классов вроде System.ValueType, System.Enum, System.Delegate, System.Array и т.д.</p><p>И ещё кое-что:</p><p>Выше мы описали три класса: ClassW, ClassX и ClassY, который наследуется от первых двух. Теперь попробуем это скомпилировать:</p><p>Что ещё нужно запомнить: класс может иметь только одного родителя, множественное наследование в C# не поддерживается (оно поддерживается у интерфейсов, но в этой статье мы о них речи не ведём).</p><p>Если мы попробуем обойти это правило таким образом:</p><p>То это не пройдёт:</p><p>Что нужно запомнить: классы не могут наследоваться циклически (1-й от 2-го, 2-й от 3-го 3-й от 1-го), что, в общем-то, логично.</p><h3>Операции с объектами</h3><p>Здесь мы пытаемся приравнять объект от разных классов друг к другу.</p><p>Однако у нас это плохо получается. Даже несмотря на то, что они имеют одинаковые поля с одинаковыми значениями. Даже если бы эти поля имели одинаковые названия. C# работает с типами очень чётко — вы не можете приравнять два объекта от двух независимых классов. Однако, если бы класс <b>A</b> наследовался от <b>B</b>:</p><p>…мы бы продвинулсь немногим дальше:</p><p>Как я уже говорил, C# подходит к вопросам типов очень дотошно. Класс A унаследован от B, значит, имеет все его поля и методы — при назначении переменной типа B объекта типа A проблем не возникает. Однако вы уже знаете, что в обратную сторону это не работает — в классе B нет полей и методов, которые могут быть в A.</p><p><b>Что нужно запомнить</b>: вы можете назначить переменной родительского типа объект дочернего, но не наоборот.</p><p>Здесь нам наконец-то представляется шанс обмануть правило:</p><p>Приведение типа здесь сработает, но только потому, что эти классы находятся в наследственных отношениях. Два обособленных непримитивных типа привести друг к другу нельзя.</p><p>Итак, наш последний блок кода:</p><p><b>Что нужно запомнить</b>: можно конвертировать char в int. Нельзя конвертировать int в char (причина в том, что диапазон целого числа больше, чем символа).</p><h3>Заключение</h3><p>В этой части мы рассмотрели наследование. Мы попробовали запускать разные варианты кода, чтобы возможно глубже понять суть этого принципа. *этот текст будет изменён после перевода следующей статьи* In my next article, we’ll be discussing about run time polymorphism. Inheritance plays a very important role in run time polymorphism.</p><p>Вот что вы должны были запомнить за сегодня:</p><ul><li>как сын получается похожим на отца, наследует его черты, так и дочерний класс имеет параметры родительского;</li><li>ничто не может помешать создать в дочернем классе такой же метод, как и в родительском;</li><li>методы дочерних классов имеют приоритет при выполнении;</li><li>ключевое слово base может быть использовано для обращения к методам класса-предка;</li><li>наследование не работает в обратном направлении;</li><li>кроме конструкторов и деструкторов, дочерний класс получает от родителя абсолютно всё;</li><li>ваши классы не могут быть унаследованы от встроенных классов вроде System.ValueType, System.Enum, System.Delegate, System.Array и т.д.;</li><li>класс может иметь только одного родителя, множественное наследование классов в C# не поддерживается;</li><li>классы не могут наследоваться циклически (1-й от 2-го, 2-й от 3-го 3-й от 1-го), это невозможно чисто логически;</li><li>вы можете назначить переменной родительского типа объект дочернего, но не наоборот;</li><li>можно конвертировать char в int. Нельзя конвертировать int в char (причина в том, что диапазон целого числа больше, чем символа).</li></ul><p>Напоминаем вам, что в <a href="https://tproger.ru/translations/diving-in-oop-p1">первой статье</a> этой серии вы можете прочитать о полиморфизме. Продолжайте учиться программировать с нами!</p>]]></content:encoded>
    </item>
    <item>
      <title>Чем отличаются наследование и композиция в Java</title>
      <link>https://tproger.ru/translations/inheritance-and-composition-in-java</link>
      <comments>https://tproger.ru/translations/inheritance-and-composition-in-java?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/inheritance-and-composition-in-java</guid>
      <description><![CDATA[<p>Композиция даёт повторное использование кода без расширения класса, работает с final-классами и несколькими классами сразу — в отличие от наследования в Java.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/inheritance-and-composition-in-java">Чем отличаются наследование и композиция в Java</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 17 Jun 2015 12:14:14 GMT</pubDate>
      <content:encoded><![CDATA[<p>Несмотря на то, что и композиция, и наследование позволяют использовать код повторно, они делают это по-разному. Основное отличие между ними состоит в том, что композиция позволяет переиспользовать код без его расширения. Наследование при этом требует расширения существующего класса.</p><p>Другое важное отличие: при композиции мы можем повторно использовать код даже из final-класса, тогда как унаследоваться от него невозможно. Кроме того при композиции возможно использование кода из нескольких различных классов.</p><p>С наследованием такой трюк не сработает: в Java не поддерживается множественное наследование. Зато вы можете сделать это в C++. Кстати, всегда стоит предпочитать композицию наследованию в Java. И не только потому, что так советует Джошуа Блох в своей книге <a href="http://www.amazon.com/dp/0321356683/?tag=javamysqlanta-20">Effective Java</a> (которая является отличным источником полезных советов для Java-программиста). Аргументы за использование композиции вы можете прочитать <a href="http://javarevisited.blogspot.sg/2013/06/why-favor-composition-over-inheritance-java-oops-design.html">здесь</a>.</p><h4>Наследование и композиция</h4><p>Давайте посмотрим более детально на отличия композиции и наследования. Мы рассмотрим каждый пункт достаточно подробно, но без скучных деталей.</p><h4>Гибкость</h4><p>Первое отличие касается гибкости кода. При использовании наследования вы должны описать, какой класс вы расширяете. При этом, вы не можете заменить его во время выполнения программы. Используя композицию, вы можете определить используемый тип, который может содержать несколько различных реализаций. Таким образом, композиция предоставляет гораздо больше гибкости, чем наследование.</p><h4>Ограниченное повторное использование кода при наследовании</h4><p>Как уже было сказано, унаследоваться в Java можно только от одного класса, а это значит, что повторно можно использовать один, и только один класс. Если вы хотите получить функциональность нескольких классов, то вам следует использовать композицию. К примеру: ваш код должен использовать аутентификацию, следовательно вам надо унаследовать класс Authenificator, для использования авторизации — Autorizer и т. д. Но, поскольку <a href="http://javarevisited.blogspot.sg/2011/07/why-multiple-inheritances-are-not.html">множественное наследование в Java не поддерживается</a>, вы этого сделать не можете. Единственный выход — композиция.</p><h4>Юнит-тесты</h4><p>Важный аргумент при выборе способа повторного использования кода состоит в том, что классы, расширенные при помощи композиции, легче тестировать, поскольку вы всегда можете предоставить заглушку для используемого класса. При наследовании вам потребуется родительский класс для тестирования, и заменить его заглушкой не получится.</p><h2>Final-классы</h2><p>Невозможность расширить final-классы — еще одно ограничение наследования. В Java <a href="http://javarevisited.blogspot.sg/2011/12/final-variable-method-class-java.html">нет возможности унаследоваться от final-классов</a>, поэтому опять единственный выход — использовать композицию для повторного использования кода.</p><h4>Инкапсуляция</h4><p>Последнее отличие композиции и наследования в Java заключается в их отношении к инкапсуляции. Несмотря на то, что и та, и другая техники позволяют повторно использовать код, наследование нарушает принцип инкапсуляции, поскольку подкласс зависит от поведения родительского класса. Если класс-родитель изменит свое поведение, это повлияет и на его потомков. Если классы плохо документированы и класс-потомок неправильно использует родительский, то при любом изменении родителя функциональность потомка окажется сломанной. Для того, чтобы увидеть это на примере, вы можете прочитать главы <a href="http://www.amazon.com/dp/0321356683/?tag=javamysqlanta-20">16 и 17 Effective Java</a>.</p><p>Вот и все, что можно сказать об отличиях композиции и наследования в Java и в объектно-ориентированном программировании вообще. Как видите, они по-разному служат одной цели: повторное использование проверенных и протестированных участков кода. Композиция при этом позволяет защитить повторно используемый класс от клиентов, тогда как наследование этого не гарантирует. Тем не менее, иногда наследование необходимо. В частности, когда вы создаете классы из одного семейства.<br />Перевод статьи <a href="http://javarevisited.blogspot.ru/2015/06/difference-between-inheritance-and-Composition-in-Java-OOP.html">«Difference between Inheritance and Composition in Java OOPS»</a></p>]]></content:encoded>
    </item>
  </channel>
</rss>