## Объектно-ориентирование программирование ### Абстракция Абстрагирование — это способ выделить набор наиболее важных атрибутов и методов и исключить незначимые. Соответственно, абстракция — это использование всех таких характеристик для описания объекта. Важно представить объект минимальным набором полей и методов без ущерба для решаемой задачи. Пример: объекту класса «программист» вряд ли понадобятся свойства «умение готовить еду» или «любимый цвет». Они не влияют на его особенности как программиста. А вот «основной язык программирования» и «рабочие навыки» — важные свойства, без которых программиста не опишешь. Набор атрибутов и методов, доступный извне, работает как интерфейс для доступа к объекту. Через них к нему могут обращаться другие структуры данных, причем им не обязательно знать, как именно объект устроен внутри. ### Инкапсуляция Каждый объект — независимая структура. Все, что ему нужно для работы, уже есть у него внутри. Если он пользуется какой-то переменной, она будет описана в теле объекта, а не снаружи в коде. Это делает объекты более гибкими. Даже если внешний код перепишут, логика работы не изменится. Инкапсуляция помогает с легкостью управлять кодом. Выше мы сказали, что для обращения к объекту не нужно понимать, как работают его методы. Начальнику разработчика Ивана не обязательно знать, как именно он программирует: главное — чтобы выполнялись поставленные задачи. Внутреннее устройство одного объекта закрыто от других: извне «видны» только значения атрибутов и результаты выполнения методов. ### Наследование Можно создавать классы и объекты, которые похожи друг на друга, но немного отличаются — имеют дополнительные атрибуты и методы. Более общее понятие в таком случае становится «родителем», а более специфичное и подробное — «наследником». Упомянутый программист Иван — это человек. Но «человек» — более общее определение, которое не описывает свойства, важные именно для программиста. Можно сказать, что класс «программист» унаследован от класса «человек»: программист тоже является человеком, но у него есть дополнительные свойства. В таком случае разработчик Иван будет и человеком, и программистом одновременно. У него будут наборы свойств от обоих классов. У одного «родителя» может быть несколько дочерних структур. Например, от «человека» можно наследовать не только «программиста», но и «директора». Наследование позволяет реализовывать сложные схемы с четкой иерархией «от общего к частному». Это облегчает понимание и масштабирование кода. Не нужно много раз переписывать в разных объектах одни и те же свойства. Достаточно унаследовать эти объекты от одного «родителя», и «родительские» свойства применятся автоматически. ### Полиморфизм Одинаковые методы разных объектов могут выполнять задачи разными способами. Например, у «человека» есть метод «работать». У «программиста» реализация этого метода будет означать написание кода, а у «директора» — рассмотрение управленческих вопросов. Но глобально и то, и другое будет работой. Тут важны единый подход и договоренности между специалистами. Если метод называется delete, то он должен что-то удалять. Как именно — зависит от объекта, но заниматься такой метод должен именно удалением. Более того: если оговорено, что «удаляющий» метод называется delete, то не нужно для какого-то объекта называть его remove или иначе. Это вносит путаницу в код. ## SOLID ### Single Responsibility Principle (Принцип единственной ответственности) Это значит, что каждый класс должен выполнять только **одну** четко определенную функцию. Если он решает более одной задачи, это может привести к сложностям в поддержке и расширении кода. **Как следовать принципу SRP** Сначала общую задачу нужно декомпозировать на несколько подзадач. Затем каждую подзадачу реализовать в отдельном классе. ### Open/Closed Principle (Принцип открытости/закрытости) **Программные сущности (классы, модули, функции и т.д.) должны быть открыты для расширения, но закрыты для модификации.** Это значит, что программные компоненты нужно спроектировать таким образом, чтобы их можно было расширять новым функционалом, не меняя исходный код. Изменения при добавлении нового кода могут сломать уже работающую логику. **Как следовать принципу OCP** В этом помогут абстракции, интерфейсы и наследование. Это позволит добавить новый функционал через расширение, а не модификацию существующего кода. **Задача** У ресторана есть система управления заказами. Нужно сделать так, чтобы она поддерживала различные виды платежей: наличные, кредитные карты и мобильные платежи. ### Liskov Substitution Principle (Принцип подстановки Барбары Лисков) **Производные классы должны заменять свои базовые классы.** Это значит, что объекты базовых классов должны быть заменяемы объектами производных классов без изменения ожидаемого поведения программы. **_Базовый класс_** — это класс, от которого производные классы наследуют свойства и методы. **Как следовать принципу LSP** Производный класс должен сохранять все свойства базового класса и не изменять их семантику. Нужно добиться того, чтобы объекты производных классов могли безопасно заменять друг друга и базовый класс. **Задача** Предположим, что есть система для работы с геометрическими фигурами, в которой есть базовый класс **Shape** и производные классы **Circle** и **Square**. ### Interface Segregation Principle (Принцип разделения интерфейса) **Клиенты не должны зависеть от интерфейсов, которые они не используют.** Это значит, что нужно создавать только небольшие и узконаправленные интерфейсы, не перегруженные ненужными методами. **Как следовать принципу ISP** Каждый интерфейс должен существовать для определенных задач и содержать только те методы, которые эти задачи решают. **Задача** Предположим, у нас есть система управления документами с интерфейсом **Document,** содержащим методы для работы с документами, такие как **open()**, **save()** и **close().** Однако одним клиентам требуется только возможность открывать и закрывать документы, а другим — только сохранять и закрывать их. ### Dependency Inversion Principle (Принцип инверсии зависимостей) **Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей, но детали должны зависеть от абстракций.** Это значит, что код должен быть организован таким образом, чтобы зависимости между компонентами программы были основаны на абстракциях, а не на конкретных реализациях. Таким образом, компоненты легко заменить или изменить без воздействия на другие части системы. **Как следовать принципу DIP** — Использовать интерфейсы или абстрактные классы для определения зависимостей между компонентами. — Следовать остальным принципам SOLID, чтобы создавать хорошо структурированные и модульные системы. — Применять шаблоны проектирования, такие как **Dependency Injection** (Внедрение зависимостей) или **Inversion of Control** (Инверсия управления), чтобы передавать зависимости извне вместо того, чтобы создавать их внутри компонентов. **Внедрение зависимостей**: объект не создает свои зависимости самостоятельно, они предоставляются ему извне, например через конструктор, методы или свойства. **Инверсия управления**: управление частью приложения переносится на внешний фреймворк или контейнер, управляющий жизненным циклом объектов. **Задача** Нужно разработать приложение для работы с базой данных студентов. Каждый студент имеет имя, возраст и список предметов, которые он изучает.