Common Questions Flashcards
- В чем суть ООП и каковы его основные принципы.
Объектно-ориентированное программирование (ООП) — это подход, при котором программа рассматривается как набор объектов, взаимодействующих друг с другом. Основные принципы: инкапсуляция, наследование, абстракция, полиморфизм.
Абстрагирование — это способ выделить набор наиболее важных атрибутов и методов и исключить незначимые.
Инкапсуляция – каждый объект является независимой структурой. Все, что ему нужно для работы, уже есть у него внутри. Если он пользуется какой-то переменной, она будет описана в теле объекта, а не снаружи в коде. Это делает объекты более гибкими. Даже если внешний код перепишут, логика работы не изменится.
Инкапсуляция помогает с легкостью управлять кодом. Для обращения к объекту не нужно понимать, как работают его методы. Внутреннее устройство одного объекта закрыто от других: извне «видны» только значения атрибутов и результаты выполнения методов.
Наследование
Можно создавать классы и объекты, которые похожи друг на друга, но немного отличаются — имеют дополнительные атрибуты и методы. Более общее понятие в таком случае становится «родителем», а более специфичное и подробное — «наследником».
Полиморфизм
Одинаковые методы разных объектов могут выполнять задачи разными способами. Например, у «человека» есть метод «работать». У «программиста» реализация этого метода будет означать написание кода, а у «директора» — рассмотрение управленческих вопросов. Но глобально и то, и другое будет работой.
- В чем суть полиморфизма.
Одинаковые методы разных объектов могут выполнять задачи разными способами. Например, у «человека» есть метод «работать». У «программиста» реализация этого метода будет означать написание кода, а у «директора» — рассмотрение управленческих вопросов. Но глобально и то, и другое будет работой.
- Принцип SOLID.
SOLID — это набор принципов объектно-ориентированного программирования, которые помогают создавать более гибкие, понятные и сопровождаемые системы.
1. Принцип единственной ответственности (Single Responsibility Principle, SRP):
o Суть: Каждый класс должен отвечать за что-то одно.
o Простыми словами: У каждого класса должна быть только одна причина для изменения.
o Пример: Если у нас есть класс, который отвечает за работу с пользователями (User), то он не должен также заниматься отправкой email. Для этого должен быть отдельный класс.
2. Принцип открытости/закрытости (Open/Closed Principle, OCP):
o Суть: Программные сущности должны быть открыты для расширения, но закрыты для изменения.
o Простыми словами: Можно добавлять новое поведение в систему, но нельзя изменять существующий код.
o Пример: Если нужно добавить новый тип фигуры в программу, которая считает площади фигур, не нужно менять существующие классы. Вместо этого добавляется новый класс для новой фигуры.
3. Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP):
o Суть: Объекты наследуемых классов должны быть взаимозаменяемы с объектами базового класса.
o Простыми словами: Дочерний класс должен дополнять, а не изменять поведение базового класса.
o Пример: Если у вас есть класс “Птица” с методом “летать”, то класс “Пингвин” не должен наследоваться от “Птица”, так как пингвины не летают.
4. Принцип разделения интерфейса (Interface Segregation Principle, ISP):
o Суть: Клиенты не должны зависеть от интерфейсов, которые они не используют.
o Простыми словами: Лучше иметь несколько специфических интерфейсов, чем один общий.
o Пример: Вместо одного большого интерфейса, содержащего методы для печати, сканирования и копирования, лучше создать отдельные интерфейсы для каждого действия.
5. Принцип инверсии зависимостей (Dependency Inversion Principle, DIP):
o Суть: Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба типа модулей должны зависеть от абстракций.
o Простыми словами: Зависимости должны быть направлены на абстракции, а не на конкретные реализации.
o Пример: Вместо того чтобы класс использовал конкретный объект базы данных, он должен работать с интерфейсом базы данных. Это позволяет легко заменить одну базу данных на другую.
- Что такое Dependency Injection и зачем он нужен.
Dependency Injection (инъекция зависимостей) — это техника, используемая в программировании для передачи зависимостей объекта извне, а не создавать их внутри объекта. Это помогает сделать код более гибким и тестируемым.
Зачем это нужно?
1. Повышение тестируемости:
o При использовании инъекции зависимостей можно легко заменить реальные зависимости (например, базу данных) на фиктивные (mock-объекты) во время тестирования.
2. Ослабление связи между компонентами:
o Объекты не создают свои зависимости самостоятельно, а получают их извне. Это снижает связность кода, что упрощает его изменение и поддержку.
3. Улучшение гибкости и расширяемости:
o При необходимости изменения зависимости (например, смена типа базы данных), можно просто заменить одну реализацию на другую без изменения самого объекта.
- DI Lifetime
В контексте инъекции зависимостей (Dependency Injection), lifetime (время жизни) зависимостей определяет, как долго объект (зависимость) существует в приложении. Разные фреймворки и контейнеры внедрения зависимостей могут поддерживать различные лайфтаймы. Вот основные типы времени жизни зависимостей:
1. Transient (Кратковременный):
o Описание: Объект создается каждый раз при запросе зависимости.
o Пример: Если класс UserService зависит от класса Logger, то каждый раз при создании нового объекта UserService будет создаваться новый объект Logger.
o Использование: Когда зависимость легковесная и не требует значительных ресурсов для создания. Подходит для объектов, которые не должны разделять состояние.
2. Scoped (Область видимости):
o Описание: Объект создается один раз на каждый запрос (или сессию).
o Пример: В веб-приложениях объект может создаваться один раз на каждый HTTP-запрос.
o Использование: Когда необходимо разделить состояние между объектами в пределах одного запроса или сессии, но не между различными запросами.
3. Singleton (Одиночка):
o Описание: Объект создается один раз и используется на протяжении всего времени жизни приложения.
o Пример: Если класс Configuration содержит настройки приложения, то он может быть создан один раз при старте приложения и использоваться везде.
o Использование: Для зависимостей, которые требуют значительных ресурсов для создания или содержат состояние, которое должно быть разделено между всеми частями приложения.
4. Per-Thread (На поток):
o Описание: Объект создается один раз на поток.
o Пример: В многопоточных приложениях, где нужно иметь отдельные экземпляры зависимости для каждого потока.
o Использование: Когда нужно, чтобы каждый поток имел свою версию зависимости, часто используется в многопоточном программировании.
- Принципы KISS, YAGNI, DRY.
KISS (Keep It Simple, Stupid)
Пояснение:
* Суть: Принцип KISS призывает к тому, чтобы системы, коды и процессы оставались максимально простыми и понятными.
* Почему это важно: Чем проще код, тем легче его читать, понимать, сопровождать и изменять. Сложные решения увеличивают вероятность ошибок и делают поддержку кода трудоемкой.
* Пример: Вместо написания сложного алгоритма, который трудно понять, следует использовать более простое и очевидное решение, даже если оно менее эффективно.
YAGNI (You Aren’t Gonna Need It)
Пояснение:
* Суть: Принцип YAGNI утверждает, что не следует добавлять функциональность до тех пор, пока она действительно не понадобится.
* Почему это важно: Добавление ненужного функционала увеличивает сложность кода, делает его труднее тестируемым и сопровождаемым, а также может привести к техническому долгу.
* Пример: Не стоит заранее добавлять методы или классы, которые могут понадобиться в будущем. Если нет конкретной необходимости здесь и сейчас, лучше отложить их создание до того момента, когда это действительно будет нужно.
DRY (Don’t Repeat Yourself)
Пояснение:
* Суть: Принцип DRY говорит о том, что повторение кода следует избегать. Каждый фрагмент информации должен быть представлен в системе единожды.
* Почему это важно: Повторяющийся код усложняет его поддержку и тестирование. Если в одном месте потребуется изменение, нужно будет найти и изменить все повторяющиеся фрагменты, что увеличивает риск ошибок.
* Пример: Вместо того чтобы копировать и вставлять одинаковый код в разных частях программы, следует вынести этот код в отдельный метод или класс и использовать его повторно.
- Какие существуют паттерны, какие паттерны применял. В чем их суть и польза.
Паттерн Strategy (Стратегия) — это поведенческий паттерн проектирования, который позволяет определять семейство алгоритмов, инкапсулировать каждый из них и делать их взаимозаменяемыми. Паттерн Strategy позволяет изменять алгоритмы независимо от клиентов, которые их используют.
Основная идея
Паттерн Strategy предлагает вынести изменяющиеся алгоритмы и бизнес-логику из основного класса и инкапсулировать их в отдельные классы, которые реализуют общий интерфейс. Это позволяет динамически изменять поведение объекта, не изменяя его код.
Преимущества паттерна Strategy
Замена алгоритмов на лету: Позволяет динамически изменять алгоритмы, не меняя код клиента.
Принцип открытости/закрытости: Позволяет добавлять новые алгоритмы без изменения существующего кода.
Инкапсуляция алгоритмов: Логика различных алгоритмов вынесена в отдельные классы, что упрощает понимание и сопровождение кода.
Недостатки паттерна Strategy
Усложнение кода: Добавление новых классов для каждой стратегии может усложнить код и сделать его менее удобным для чтения.
Выбор стратегии: Клиент должен знать, какие стратегии существуют, и уметь выбирать между ними.
Примеры использования паттерна Strategy
Сортировка: Различные алгоритмы сортировки (быстрая сортировка, сортировка слиянием, сортировка пузырьком) могут быть реализованы как стратегии.
Кеширование: Различные стратегии кеширования (кеширование в памяти, кеширование на диске, распределенное кеширование).
Шифрование: Различные алгоритмы шифрования (AES, RSA, Blowfish) могут быть реализованы как стратегии.
Паттерн Strategy полезен в ситуациях, когда у вас есть несколько алгоритмов для выполнения конкретной задачи, и вы хотите предоставить возможность выбора алгоритма в зависимости от условий выполнения задачи.
Паттерн Facade (Фасад) — это структурный паттерн проектирования, который предоставляет унифицированный интерфейс к группе интерфейсов в подсистеме. Фасад определяет высокоуровневый интерфейс, который упрощает использование подсистемы.
Основная идея
Паттерн Facade предоставляет простой интерфейс для работы с более сложной подсистемой, которая может состоять из множества классов. Это позволяет скрыть сложность и облегчить использование подсистемы.
Применимость
Паттерн Facade используется, когда:
Нужно предоставить простой интерфейс для сложной подсистемы.
Существует множество зависимых классов или сложных взаимодействий между ними.
Нужно уменьшить количество зависимостей между клиентами и подсистемой.
Преимущества паттерна Facade
Упрощение интерфейса: Фасад предоставляет упрощенный интерфейс для взаимодействия с подсистемой, скрывая ее сложность.
Снижение зависимости: Клиенты зависят только от фасада, а не от множества классов подсистемы.
Разделение кода: Позволяет разделить систему на подсистемы, каждая из которых может быть проще и независимой.
Недостатки паттерна Facade
Ограниченная функциональность: Фасад может не предоставить всю функциональность подсистемы, ограничивая возможности клиента.
Единая точка отказа: Фасад становится единой точкой взаимодействия с подсистемой, и если он работает неправильно, это может повлиять на всю систему.
Паттерн Abstract Factory (Абстрактная фабрика) — это порождающий паттерн проектирования, который предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов, не указывая их конкретные классы. Паттерн позволяет инкапсулировать конкретные реализации продуктов и создавать семейства продуктов, которые могут использоваться вместе.
Основная идея
Паттерн Abstract Factory предлагает создать интерфейс (абстрактную фабрику) для создания объектов различных типов. Эти объекты обычно связаны логически и должны использоваться вместе. Конкретные фабрики реализуют интерфейс абстрактной фабрики и создают конкретные продукты.
Преимущества паттерна Abstract Factory
Изоляция конкретных классов: Клиентский код работает с абстрактными интерфейсами и не зависит от конкретных классов продуктов.
Легкость замены семейств продуктов: Можно легко заменить одно семейство продуктов на другое, изменив используемую фабрику.
Согласованность продуктов: Гарантируется, что созданные продукты будут совместимы друг с другом.
Недостатки паттерна Abstract Factory
Сложность в добавлении новых продуктов: Если необходимо добавить новый тип продукта, нужно изменить интерфейс абстрактной фабрики и все конкретные фабрики.
Большое количество классов: Добавление новых фабрик и продуктов может привести к увеличению количества классов, что усложняет структуру кода.
Примеры использования паттерна Abstract Factory
GUI библиотеки: Создание компонентов пользовательского интерфейса для различных платформ (Windows, MacOS, Linux).
Подключение к базам данных: Создание объектов подключения, команд и чтения данных для различных СУБД (SQL Server, MySQL, PostgreSQL).
Системы обработки документов: Создание различных форматов документов (PDF, DOCX, HTML).
Паттерн Abstract Factory полезен в ситуациях, когда система должна быть независимой от способа создания, композиции и представления ее продуктов, и когда нужно обеспечить совместную работу семейств связанных объектов.
- Отличие Abstract Factory от Factory Method.
Abstract Factory – это объект, а Factory Method – это метод. Соответственно, метод можно переопределить в производном классе, чтобы он возвращал что-то другое (например, другую реализацию интерфейса)
- Отличие Adapter от Proxy.
Ключевые отличия:
1. Назначение:
o Adapter: Преобразует интерфейс одного класса в интерфейс другого. Цель — обеспечить совместимость между несовместимыми интерфейсами.
o Proxy: Контролирует доступ к объекту, предоставляя дополнительные операции перед или после вызова реального объекта. Цель — управление доступом к объекту.
2. Использование:
o Adapter: Используется, когда нужно интегрировать старую систему с новой или когда интерфейсы не совпадают.
o Proxy: Используется, когда нужно добавлять функциональность, такую как кэширование, логирование, контроль доступа, без изменения реального объекта.
3. Типы Proxy:
o Remote Proxy: Представляет объект, находящийся в другом адресном пространстве.
o Virtual Proxy: Контролирует доступ к ресурсу, создание которого дорого стоит.
o Protection Proxy: Контролирует доступ к ресурсу на основе прав доступа.
o Smart Proxy: Добавляет дополнительное поведение к реальному объекту.
В итоге, Adapter и Proxy решают разные задачи: Adapter обеспечивает совместимость интерфейсов, а Proxy контролирует доступ и добавляет функциональность к объекту.
- Какие существую антипаттерны. Почему их следует избегать.
- Spaghetti Code (Спагетти-код)
Описание: Код, в котором отсутствует структура и порядок, что делает его трудным для понимания и сопровождения.
Проблемы: Трудности в чтении, понимании и изменении кода. Увеличивается вероятность ошибок. - God Object (Объект-бог)
Описание: Класс, который знает слишком много или делает слишком много. Он нарушает принцип единственной ответственности (SRP).
Проблемы: Трудности в сопровождении и тестировании, высокая связанность, низкая модульность. - Golden Hammer (Золотой молоток)
Описание: Использование одного и того же инструмента или технологии для решения всех задач, независимо от того, подходит ли этот инструмент для конкретной задачи.
Проблемы: Неправильное использование инструментов, снижение эффективности. - Premature Optimization (Преждевременная оптимизация)
Описание: Оптимизация кода до того, как станет понятно, что именно требует оптимизации. Это может усложнить код и уменьшить его читаемость.
Проблемы: Усложнение кода, меньшая гибкость. - Magic Numbers (Магические числа)
Описание: Использование числовых значений непосредственно в коде, вместо использования именованных констант или перечислений.
Проблемы: Непонимание значения чисел, трудности в изменении.
- Какие существуют типы тестов.
По цели тестирования:
1. Функциональное тестирование — проверка того, как приложение выполняет свои функции согласно требованиям.
o Модульное тестирование (Unit Testing) — тестирование отдельных модулей или компонентов системы.
o Интеграционное тестирование (Integration Testing) — проверка взаимодействия между модулями.
o Системное тестирование (System Testing) — тестирование всей системы как единого целого.
o Приемочное тестирование (Acceptance Testing) — проверка соответствия системы требованиям пользователя.
o Регрессионное тестирование (Regression Testing) — проверка того, что изменения в коде не нарушили существующую функциональность.
2. Нефункциональное тестирование — проверка нематериальных аспектов системы.
o Тестирование производительности (Performance Testing) — проверка времени отклика, пропускной способности и других параметров.
Тестирование нагрузки (Load Testing) — проверка работы системы под ожидаемой нагрузкой.
Тестирование стрессов (Stress Testing) — проверка системы при экстремальных условиях.
Тестирование стабильности (Stability Testing) — проверка работы системы в течение длительного времени.
o Тестирование безопасности (Security Testing) — проверка на уязвимости, безопасность данных и защиту от угроз.
o Тестирование удобства использования (Usability Testing) — оценка удобства интерфейса и пользовательского опыта.
o Тестирование совместимости (Compatibility Testing) — проверка работы приложения в различных окружениях, включая операционные системы, браузеры и устройства.
По методу тестирования:
1. Черный ящик (Black-Box Testing) — тестирование без учета внутренней структуры кода. Тестировщик проверяет только входные и выходные данные.
2. Белый ящик (White-Box Testing) — тестирование с учетом внутренней структуры кода. Оценивается работа алгоритмов и логики.
3. Серый ящик (Gray-Box Testing) — комбинация черного и белого ящика. Тестировщик имеет частичное знание о внутренней структуре системы.
- Что такое Mock, Stub.
Основные различия:
* Stub: Используется для предоставления предопределенных данных. Он не проверяет, как объект используется, а просто возвращает данные, которые задаются в тесте.
* Mock: Не только предоставляет данные, но и проверяет, как взаимодействие происходит (например, какие методы были вызваны и с какими параметрами).
- Отличие аутентификации от авторизации.
Аутентификация — это процесс проверки подлинности пользователя. Он позволяет системе убедиться, что пользователь действительно тот, за кого себя выдаёт. В ходе аутентификации пользователь предоставляет свои учетные данные (например, логин и пароль, отпечаток пальца, или одноразовый код из SMS), и система проверяет их на соответствие с ранее зарегистрированными данными. Примеры аутентификации:
* Ввод логина и пароля.
* Использование биометрических данных, таких как отпечаток пальца или распознавание лица.
* Двухфакторная аутентификация (например, пароль + код из SMS).
Авторизация — это процесс определения прав и полномочий пользователя, который уже прошел аутентификацию. Он позволяет системе решить, к каким ресурсам или операциям пользователь имеет доступ. Авторизация отвечает на вопрос: “Что пользователь может делать?”. Примеры авторизации:
* Пользователь имеет право просматривать, редактировать или удалять определенные данные.
* Пользователь может доступаться только к определенным разделам системы.
* Пользователь имеет определенные роли, которые определяют его полномочия (например, администратор, редактор, просмотрщик).
- Какие есть UML диаграммы.
UML (Unified Modeling Language) — это стандартный язык для моделирования программных систем. UML предлагает несколько типов диаграмм, которые можно разделить на две основные категории: диаграммы структуры и диаграммы поведения.
Диаграммы структуры
1. Диаграмма классов (Class Diagram):
o Отображает классы системы и их взаимосвязи.
o Показывает атрибуты и методы классов, а также отношения между ними (наследование, ассоциации, агрегация, композиция).
2. Диаграмма объектов (Object Diagram):
o Показывает экземпляры классов (объекты) в определенный момент времени.
o Визуализирует конкретное состояние системы.
3. Диаграмма компонентов (Component Diagram):
o Моделирует физическую структуру кода в терминах компонентов.
o Показывает зависимости между компонентами.
4. Диаграмма развёртывания (Deployment Diagram):
o Отображает физическое расположение узлов и артефактов в системе.
o Показывает, на каких машинах и как программные компоненты размещены.
5. Диаграмма пакетов (Package Diagram):
o Группирует элементы модели в пакеты и показывает зависимости между ними.
o Упрощает управление большими системами.
6. Диаграмма профилей (Profile Diagram):
o Расширяет UML для специфических доменов.
o Используется для создания стереотипов, тегированных значений и ограничений.
Диаграммы поведения
1. Диаграмма прецедентов (Use Case Diagram):
o Моделирует функциональные требования к системе.
o Показывает акторов (пользователей или внешние системы) и их взаимодействие с системой через прецеденты (use cases).
2. Диаграмма активности (Activity Diagram):
o Моделирует рабочие процессы и действия внутри системы.
o Показывает последовательность действий и их логические ветвления.
3. Диаграмма состояний (State Machine Diagram):
o Описывает состояния объекта и переходы между ними в ответ на события.
o Используется для моделирования поведения объектов в зависимости от их состояния.
4. Диаграмма последовательности (Sequence Diagram):
o Отображает взаимодействие объектов во времени.
o Показывает последовательность обмена сообщениями между объектами для выполнения конкретного функционала.
5. Диаграмма кооперации (Communication Diagram):
o Фокусируется на структурной организации взаимодействия объектов.
o Показывает, какие объекты взаимодействуют и как они связаны.
6. Диаграмма временных характеристик (Timing Diagram):
o Отображает изменения состояния или значений объектов во времени.
o Используется для анализа временных аспектов взаимодействия объектов.
7. Диаграмма компонентов взаимодействия (Interaction Overview Diagram):
o Комбинирует элементы диаграмм активности и диаграмм последовательности.
o Позволяет моделировать сложные взаимодействия и их контрольный поток.
Каждая из этих диаграмм используется для моделирования различных аспектов системы и помогает в разработке, анализе и документировании сложных программных систем.
- Как строится RESTful Api.
Построение RESTful API включает несколько ключевых шагов и принципов:
1. Определение ресурса:
o RESTful API основан на концепции ресурсов. Ресурсами могут быть сущности вашей системы, такие как пользователи, заказы, продукты и т.д.
o Каждый ресурс должен иметь уникальный идентификатор (URI).
2. Проектирование URI:
o URI должны быть логичными и предсказуемыми.
o Используйте множественное число для обозначения коллекций ресурсов (например, /users для коллекции пользователей и /users/{id} для конкретного пользователя).
3. Использование HTTP-методов:
o GET: для получения данных.
o POST: для создания новых ресурсов.
o PUT: для обновления существующих ресурсов.
o DELETE: для удаления ресурсов.
o PATCH: для частичного обновления ресурсов.
4. Формат данных:
o RESTful API обычно использует JSON для передачи данных из-за его легкости и читаемости.
o Обеспечьте поддержку различных форматов данных через заголовки HTTP (например, Content-Type и Accept).
5. Статусы HTTP-ответов:
o Используйте соответствующие коды статусов HTTP для обозначения результатов операций (например, 200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found, 500 Internal Server Error).
6. Обработка ошибок:
o Верните информативные сообщения об ошибках с соответствующими кодами статусов.
o Определите структуру ответа для ошибок, чтобы клиенты могли легко понять и обработать их.
7. Безопасность:
o Используйте HTTPS для защиты данных при передаче.
o Реализуйте механизмы аутентификации и авторизации (например, OAuth2, JWT).
o Управляйте доступом к ресурсам на основе ролей и прав.
8. Версионирование:
o Версионирование API помогает управлять изменениями и обеспечивает обратную совместимость.
o Включите версию в URI (например, /v1/users) или используйте заголовки (например, Accept: application/vnd.yourapi.v1+json).