HTML Diff
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>