0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>В современной разработке программного обеспечения важно не только написать рабочий код. Еще нужно сделать так, чтобы его было удобно поддерживать, расширять и масштабировать. Для этого были разработаны принципы SOLID, которые позволяют упорядочить и структурировать код и делать его более понятным и простым в поддержке.</p>
1
<p>В современной разработке программного обеспечения важно не только написать рабочий код. Еще нужно сделать так, чтобы его было удобно поддерживать, расширять и масштабировать. Для этого были разработаны принципы SOLID, которые позволяют упорядочить и структурировать код и делать его более понятным и простым в поддержке.</p>
2
<p>В этом уроке мы рассмотрим несколько примеров применения принципов SOLID и других подходов ООП в реальных задачах.</p>
2
<p>В этом уроке мы рассмотрим несколько примеров применения принципов SOLID и других подходов ООП в реальных задачах.</p>
3
<h2>Принципы SOLID и их применение</h2>
3
<h2>Принципы SOLID и их применение</h2>
4
<p><strong>SOLID</strong>- это акроним, который состоит из пяти основных принципов организации и написания кода:</p>
4
<p><strong>SOLID</strong>- это акроним, который состоит из пяти основных принципов организации и написания кода:</p>
5
<ul><li>SRP - принцип единственной ответственности</li>
5
<ul><li>SRP - принцип единственной ответственности</li>
6
<li>OCP - принцип открытости/закрытости</li>
6
<li>OCP - принцип открытости/закрытости</li>
7
<li>LSP - принцип подстановки Барбары Лисков</li>
7
<li>LSP - принцип подстановки Барбары Лисков</li>
8
<li>ISP - принцип разделения интерфейса</li>
8
<li>ISP - принцип разделения интерфейса</li>
9
<li>DIP - принцип инверсии зависимостей</li>
9
<li>DIP - принцип инверсии зависимостей</li>
10
</ul><p>В этом уроке мы сфокусируемся на первом принципе - SRP. Он гласит: "Должна быть только одна причина для изменения класса". Этот принцип является полезным инструментом для улучшения качества и читаемости кода.</p>
10
</ul><p>В этом уроке мы сфокусируемся на первом принципе - SRP. Он гласит: "Должна быть только одна причина для изменения класса". Этот принцип является полезным инструментом для улучшения качества и читаемости кода.</p>
11
<p>Возьмем для примера библиотеку для работы с датами и временем. С чего нужно начать ее проектирование?</p>
11
<p>Возьмем для примера библиотеку для работы с датами и временем. С чего нужно начать ее проектирование?</p>
12
<p>Правильно начинать с вариантов использования. Представить себе как будто библиотека уже написана и мы пробуем ей воспользоваться (TDD толкает именно к этому, поэтому оно так мощно работает). Прежде чем мы перейдем к коду, попробуйте ответить на вопрос, так ли нужны классы и ООП для реализации этой библиотеки?</p>
12
<p>Правильно начинать с вариантов использования. Представить себе как будто библиотека уже написана и мы пробуем ей воспользоваться (TDD толкает именно к этому, поэтому оно так мощно работает). Прежде чем мы перейдем к коду, попробуйте ответить на вопрос, так ли нужны классы и ООП для реализации этой библиотеки?</p>
13
<p>Получение текущего времени - это операция, у которой есть конец и начало. Вопрос, который мы должны себе задать: "Действительно ли нам нужны классы и ООП для реализации этой библиотеки?". Нет, конечно. Для операций достаточно функций. Поэтому наша библиотека в самом простом случае может выглядеть так:</p>
13
<p>Получение текущего времени - это операция, у которой есть конец и начало. Вопрос, который мы должны себе задать: "Действительно ли нам нужны классы и ООП для реализации этой библиотеки?". Нет, конечно. Для операций достаточно функций. Поэтому наша библиотека в самом простом случае может выглядеть так:</p>
14
<p>В этом коде мы создаем интерфейс для нашей будущей библиотеки. Здесь не требуются объекты для ее реализации. Операция получения текущего времени выражена через функцию.</p>
14
<p>В этом коде мы создаем интерфейс для нашей будущей библиотеки. Здесь не требуются объекты для ее реализации. Операция получения текущего времени выражена через функцию.</p>
15
<p>Теперь, когда интерфейс библиотеки готов, можно приступать к ее реализации. При этом не важно, как она выполнена внутри. Внутренности останутся внутренностями, и никто про них не узнает. А их размер никогда не станет слишком большим, так как это библиотека для работы с датами и временем.</p>
15
<p>Теперь, когда интерфейс библиотеки готов, можно приступать к ее реализации. При этом не важно, как она выполнена внутри. Внутренности останутся внутренностями, и никто про них не узнает. А их размер никогда не станет слишком большим, так как это библиотека для работы с датами и временем.</p>
16
<p>Это значит, что мы в любой момент можем их переписать. И делать это лучше после, когда накопится опыт поддержки и опыт использования. Только в этом случае появится настоящее понимание того, как лучше структурировать библиотеку внутри.</p>
16
<p>Это значит, что мы в любой момент можем их переписать. И делать это лучше после, когда накопится опыт поддержки и опыт использования. Только в этом случае появится настоящее понимание того, как лучше структурировать библиотеку внутри.</p>
17
<p>При этом даже с пониманием принципов SOLID и других методологий ООП, может быть непросто применить их на практике. Поэтому рассмотрим несколько примеров, которые помогут лучше понять, как применять эти принципы в реальных задачах.</p>
17
<p>При этом даже с пониманием принципов SOLID и других методологий ООП, может быть непросто применить их на практике. Поэтому рассмотрим несколько примеров, которые помогут лучше понять, как применять эти принципы в реальных задачах.</p>
18
<h2>Объекты и их использование</h2>
18
<h2>Объекты и их использование</h2>
19
<p>В Python, как и во многих других языках, принято использовать классы. Поэтому перепишем наш интерфейс выше на объектный:</p>
19
<p>В Python, как и во многих других языках, принято использовать классы. Поэтому перепишем наш интерфейс выше на объектный:</p>
20
<p>Здесь мы используем класс DateTime для хранения времени. Метод to_iso преобразует это время в строку в формате ISO. Важно помнить, что применение классов полезно тогда, когда они помогают упростить работу с кодом. Например, когда они позволяют удобно управлять связанными данными и методами.</p>
20
<p>Здесь мы используем класс DateTime для хранения времени. Метод to_iso преобразует это время в строку в формате ISO. Важно помнить, что применение классов полезно тогда, когда они помогают упростить работу с кодом. Например, когда они позволяют удобно управлять связанными данными и методами.</p>
21
<p>Однако важно понимать, что дизайн и внутренняя структура класса должны быть обдуманы. Всегда следует применять принцип минимального усилия: если функциональность может быть достигнута с меньшим количеством классов или методов, то следует использовать их. Это помогает уменьшить сложность кода и упрощает его поддержку и расширение.</p>
21
<p>Однако важно понимать, что дизайн и внутренняя структура класса должны быть обдуманы. Всегда следует применять принцип минимального усилия: если функциональность может быть достигнута с меньшим количеством классов или методов, то следует использовать их. Это помогает уменьшить сложность кода и упрощает его поддержку и расширение.</p>
22
<p>Например, при создании класса клиента API, состояние класса - это конфигурация, а не конкретные запросы и ответы. Состояние класса обычно меняется редко, поэтому его лучше хранить внутри класса.</p>
22
<p>Например, при создании класса клиента API, состояние класса - это конфигурация, а не конкретные запросы и ответы. Состояние класса обычно меняется редко, поэтому его лучше хранить внутри класса.</p>
23
<p>Какими принципами нужно руководствоваться, чтобы понять внутреннюю архитектуру и количество классов? Для старта достаточно здравого смысла. У нас есть сам клиент, который представлен объектом (его состояние - это конфигурация), и есть результат функции получения времени.</p>
23
<p>Какими принципами нужно руководствоваться, чтобы понять внутреннюю архитектуру и количество классов? Для старта достаточно здравого смысла. У нас есть сам клиент, который представлен объектом (его состояние - это конфигурация), и есть результат функции получения времени.</p>
24
<p>Дальнейшее разбиение не нужно. Возможно, это не понадобится никогда. А если и понадобится, то сначала нужно почувствовать такую необходимость, а затем уже реализовывать ее. Причем главное основание для такого разделения - это не абстрактная единственная ответственность, а выделение чистого кода, который не связан с побочными эффектами.</p>
24
<p>Дальнейшее разбиение не нужно. Возможно, это не понадобится никогда. А если и понадобится, то сначала нужно почувствовать такую необходимость, а затем уже реализовывать ее. Причем главное основание для такого разделения - это не абстрактная единственная ответственность, а выделение чистого кода, который не связан с побочными эффектами.</p>
25
<p>Внутри нашей библиотеки может быть код, который взаимодействует с системой, проверяет текущее время, а есть код, который работает с данными, приводит их в нормальный вид, чистит и как-то структурирует. В первую очередь, нужно отслеживать такой код и отделять его на уровне функций или методов. Любая операция, которая может быть чисто вычислительной, потенциальный кандидат на вынесение.</p>
25
<p>Внутри нашей библиотеки может быть код, который взаимодействует с системой, проверяет текущее время, а есть код, который работает с данными, приводит их в нормальный вид, чистит и как-то структурирует. В первую очередь, нужно отслеживать такой код и отделять его на уровне функций или методов. Любая операция, которая может быть чисто вычислительной, потенциальный кандидат на вынесение.</p>
26
<p>Важно также понимать, что не всегда необходимо делить класс на более мелкие части. Иногда это может усложнить код. Если такое разделение становится необходимым, оно должно быть обосновано конкретными потребностями и проблемами, а не только идеями абстрактных принципов.</p>
26
<p>Важно также понимать, что не всегда необходимо делить класс на более мелкие части. Иногда это может усложнить код. Если такое разделение становится необходимым, оно должно быть обосновано конкретными потребностями и проблемами, а не только идеями абстрактных принципов.</p>
27
<h2>Использование функций и классов</h2>
27
<h2>Использование функций и классов</h2>
28
<p>Пример выше может быть реальной библиотекой. Например, возьмем библиотеку luxon для работы с датами и временем в JavaScript. Это действительно набор функций:</p>
28
<p>Пример выше может быть реальной библиотекой. Например, возьмем библиотеку luxon для работы с датами и временем в JavaScript. Это действительно набор функций:</p>
29
<p>Здесь мы видим два основных подхода к работе со временем. Мы вызываем функцию DateTime.now(), которая возвращает текущую дату и время. Затем применяем к результату методы toISO() или toFormat(), чтобы форматировать результат.</p>
29
<p>Здесь мы видим два основных подхода к работе со временем. Мы вызываем функцию DateTime.now(), которая возвращает текущую дату и время. Затем применяем к результату методы toISO() или toFormat(), чтобы форматировать результат.</p>
30
<p>Но если нужно, она позволяет создать объект, но только чтобы мы могли запомнить конфигурацию внутри для избежания дублирования:</p>
30
<p>Но если нужно, она позволяет создать объект, но только чтобы мы могли запомнить конфигурацию внутри для избежания дублирования:</p>
31
<p>Здесь мы создаем новый объект DateTime с определенной конфигурацией, что позволяет избежать дублирования кода. При этом мы создаем объект только тогда, когда это действительно необходимо - для хранения и использования определенной конфигурации.</p>
31
<p>Здесь мы создаем новый объект DateTime с определенной конфигурацией, что позволяет избежать дублирования кода. При этом мы создаем объект только тогда, когда это действительно необходимо - для хранения и использования определенной конфигурации.</p>
32
<p>Такой подход отражает общую идею использования функций и классов: функции используются для выполнения конкретных операций, а классы - для управления связанными данными и методами, включая конфигурацию.</p>
32
<p>Такой подход отражает общую идею использования функций и классов: функции используются для выполнения конкретных операций, а классы - для управления связанными данными и методами, включая конфигурацию.</p>
33
<p>Но в Python не принято создавать классы на все подряд. Вместо этого мы стараемся использовать функции там, где это возможно. И переходим к использованию классов, когда это действительно необходимо. Например, когда нужно управлять состоянием или конфигурацией.</p>
33
<p>Но в Python не принято создавать классы на все подряд. Вместо этого мы стараемся использовать функции там, где это возможно. И переходим к использованию классов, когда это действительно необходимо. Например, когда нужно управлять состоянием или конфигурацией.</p>
34
<p>В противном случае использование функций обычно является более простым и эффективным решением.</p>
34
<p>В противном случае использование функций обычно является более простым и эффективным решением.</p>
35
<p>Когда мы используем классы, важно правильно разделять различные виды работ в рамках одного класса. Это помогает оптимально организовать работу внутри классов.</p>
35
<p>Когда мы используем классы, важно правильно разделять различные виды работ в рамках одного класса. Это помогает оптимально организовать работу внутри классов.</p>
36
<h2>Разделение грязной и чистой работы</h2>
36
<h2>Разделение грязной и чистой работы</h2>
37
<p>Класс может одновременно выполнять "грязную работу" - с побочными эффектами, и "чистую работу" - обработка данных.</p>
37
<p>Класс может одновременно выполнять "грязную работу" - с побочными эффектами, и "чистую работу" - обработка данных.</p>
38
<p>Предположим, у нас есть класс, который отвечает за генерацию отчета. Этот класс одновременно выполняет чтение файла с диска и обрабатывает данные для формирования отчета:</p>
38
<p>Предположим, у нас есть класс, который отвечает за генерацию отчета. Этот класс одновременно выполняет чтение файла с диска и обрабатывает данные для формирования отчета:</p>
39
<p>Здесь мы имеем класс ReportGenerator, который отвечает за генерацию отчета. Этот класс выполняет две основные функции: он читает данные из файла и затем обрабатывает эти данные для формирования отчета.</p>
39
<p>Здесь мы имеем класс ReportGenerator, который отвечает за генерацию отчета. Этот класс выполняет две основные функции: он читает данные из файла и затем обрабатывает эти данные для формирования отчета.</p>
40
<p>В одном классе смешиваются две разные области ответственности: чтение данных из файла и их обработка.</p>
40
<p>В одном классе смешиваются две разные области ответственности: чтение данных из файла и их обработка.</p>
41
<p>Если мы разделим эти операции, то класс станет более универсальным и более легко тестируемым:</p>
41
<p>Если мы разделим эти операции, то класс станет более универсальным и более легко тестируемым:</p>
42
<p>В этом примере мы разделяем ответственности класса ReportGenerator. Теперь он принимает данные напрямую и обрабатывает их для формирования отчета. Загрузка данных теперь происходит вне этого класса. Это делает его более универсальным - он может работать с любыми данными, необязательно загруженными из файла.</p>
42
<p>В этом примере мы разделяем ответственности класса ReportGenerator. Теперь он принимает данные напрямую и обрабатывает их для формирования отчета. Загрузка данных теперь происходит вне этого класса. Это делает его более универсальным - он может работать с любыми данными, необязательно загруженными из файла.</p>
43
<h2>Выводы</h2>
43
<h2>Выводы</h2>
44
<p>В этом уроке мы рассмотрели, как использовать абстракции и классы для эффективного структурирования кода. Также мы разобрали важность соблюдения принципа единственной ответственности для обеспечения удобства поддержки и масштабирования кода.</p>
44
<p>В этом уроке мы рассмотрели, как использовать абстракции и классы для эффективного структурирования кода. Также мы разобрали важность соблюдения принципа единственной ответственности для обеспечения удобства поддержки и масштабирования кода.</p>
45
<p>Важно помнить, что здравый смысл и опыт играют ключевую роль в эффективном проектировании кода и правильном применении принципов SOLID.</p>
45
<p>Важно помнить, что здравый смысл и опыт играют ключевую роль в эффективном проектировании кода и правильном применении принципов SOLID.</p>