ООП. Объектно-ориентированное программирование среднего уровня
Каждый разработчик должен понимать принципы ООП, чтобы проектировать качественный код. Эти принципы лежат в основе создания гибких и масштабируемых приложений. В статье мы подробно разберем каждый из принципов, проиллюстрируем их примерами и предложим решения тестовых задач с hh.ru для закрепления материала.
Вопрос 1
- Объект — это функция, класс — это значение, возвращаемое этой функцией
- Класс — это шаблон, определяющий состояние и поведение объектов, объект — это экземпляр класса
- Объект — это шаблон, определяющий состояние и поведение классов, класс — это экземпляр объекта
- Класс — это функция, объект — это значение, возвращаемое этой функцией
- Класс — это имя переменной, объект — это значение, которое присваивается этой переменной
Обоснование:
Объектно-ориентированное программирование основывается на следующих ключевых понятиях:
- Класс — это шаблон или схема, определяющая свойства (атрибуты) и методы (функции), которыми обладают объекты.
- Объект — это конкретный экземпляр класса, который содержит данные и поведение, определенные в классе.
- Объект — это функция, класс — это значение, возвращаемое этой функцией — неверно, поскольку объект и класс не имеют прямой связи с функцией, если не говорить о фабричных функциях.
- Класс — это шаблон, определяющий состояние и поведение объектов, объект — это экземпляр класса — верно, так как класс используется для создания объектов, а объект является конкретной реализацией класса.
- Объект — это шаблон, определяющий состояние и поведение классов, класс — это экземпляр объекта — неверно, так как объект не определяет классы, а классы создают объекты.
- Класс — это функция, объект — это значение, возвращаемое этой функцией — неверно, классы могут быть реализованы через функции в некоторых языках (например, JavaScript), но это не базовое определение.
- Класс — это имя переменной, объект — это значение, которое присваивается этой переменной — неверно, классы не являются просто переменными.
📌Правильный ответ:
2. Класс — это шаблон, определяющий состояние и поведение объектов, объект — это экземпляр класса
Вопрос 2
Что из перечисленного — пример классического антипаттерна в ООП?
- Использование условных операторов с множественным ветвлением
- Использование индексации массива за пределами его диапазона
- Использование класса с чрезмерно большим количеством методов, имеющих разное предназначение
- Использование большого количества вложенных циклов в коде
- Использование чрезмерно длинных имен методов в классах
Обоснование:
Антипаттерны в ООП описывают плохо спроектированные структуры или решения, которые снижают читаемость, поддерживаемость и эффективность кода.
- Использование условных операторов с множественным ветвлением — это проблема структурного программирования, а не ООП.
- Использование индексации массива за пределами его диапазона — это ошибка реализации, а не антипаттерн проектирования.
- Использование класса с чрезмерно большим количеством методов, имеющих разное предназначение — это антипаттерн под названием God Object или Big Ball of Mud. Такие классы слишком сложны, нарушают принцип единственной ответственности (SRP) и затрудняют тестирование и поддержку.
- Использование большого количества вложенных циклов в коде — это проблема алгоритмической сложности, а не антипаттерн ООП.
- Использование чрезмерно длинных имен методов в классах — это влияет на читаемость, но не относится к классическим антипаттернам.
📌Правильный ответ:
3. Использование класса с чрезмерно большим количеством методов, имеющих разное предназначение
Вопрос 3
- Абстракция и инкапсуляция — это синонимичные понятия
- Абстракция может достигаться путем использования интерфейсов, а инкапсуляция — за счет модификаторов доступа
- Принцип абстракции относится к объектно-ориентированному программированию, а инкапсуляции — к процедурному
- Переопределение метода — это прием реализации инкапсуляции, а виртуальные методы — прием реализации абстракции
- Абстракция — это конкретный механизм реализации принципа инкапсуляции
Обоснование:
Разберем ключевые понятия:
- Абстракция: это процесс выделения общих характеристик объектов, сокрытие деталей реализации и работа только с необходимыми аспектами. Например, это может быть реализовано через интерфейсы или абстрактные классы.
- Инкапсуляция: это механизм сокрытия деталей реализации и управления доступом к данным с помощью модификаторов доступа (private, protected, public).
- Абстракция и инкапсуляция — это синонимичные понятия — неверно, это разные принципы с разными целями: абстракция фокусируется на общем, а инкапсуляция — на сокрытии деталей.
- Абстракция может достигаться путем использования интерфейсов, а инкапсуляция — за счет модификаторов доступа — верно, это корректно описывает механизмы реализации абстракции и инкапсуляции.
- Принцип абстракции относится к объектно-ориентированному программированию, а инкапсуляции — к процедурному — неверно, оба принципа относятся к ООП.
- Переопределение метода — это прием реализации инкапсуляции, а виртуальные методы — прием реализации абстракции — неверно, так как переопределение связано с полиморфизмом, а виртуальные методы — с поддержкой полиморфизма.
- Абстракция — это конкретный механизм реализации принципа инкапсуляции — неверно, абстракция и инкапсуляция реализуются разными средствами.
📌Правильный ответ:
2. Абстракция может достигаться путем использования интерфейсов, а инкапсуляция — за счет модификаторов доступа
Вопрос 4
Что из перечисленного — пример реализации статического полиморфизма?
- Когда необходимо создать утилитарный класс — класс-помощник, содержащий статические переменные и статические методы
- Когда необходимо, чтобы производные классы могли переопределять методы базового класса
- Когда в классе есть несколько методов, имеющих одинаковое имя, но разное число параметров одного типа
- Когда базовый класс имеет виртуальные методы, а дочерние классы переопределяют эти методы
- Во всех вышеперечисленных случаях
Обоснование:
Статический полиморфизм (compile-time polymorphism) достигается за счет перегрузки методов или операторов. Это означает, что в классе можно иметь несколько методов с одинаковым именем, но с разными наборами параметров (количество или тип). Это позволяет определить, какой метод вызывать, на этапе компиляции.
- Когда необходимо создать утилитарный класс... — это связано с организацией классов и не имеет отношения к полиморфизму.
- Когда необходимо, чтобы производные классы могли переопределять методы базового класса — это пример динамического полиморфизма (runtime polymorphism), реализуемого через виртуальные методы.
- Когда в классе есть несколько методов, имеющих одинаковое имя, но разное число параметров одного типа — это именно статический полиморфизм, так как метод определяется на этапе компиляции.
- Когда базовый класс имеет виртуальные методы... — это пример динамического полиморфизма.
- Во всех вышеперечисленных случаях — неверно, так как только третий вариант описывает статический полиморфизм.
📌Правильный ответ:
3. Когда в классе есть несколько методов, имеющих одинаковое имя, но разное число параметров одного типа
Вопрос 5
В каком из перечисленных случаев соблюден принцип единственной ответственности?
- Класс Order содержит методы для работы с оплатой, доставкой и управлением статусом заказа
- Класс OrderStatusControl содержит методы для управления статусом заказа
- Класс PaymentDelivery содержит методы для работы с оплатой и доставкой заказа
- Класс Payment содержит методы для работы с оплатой заказа, а класс DeliveryOrderStatusControl — для работы с доставкой и управлением статусом заказа
- Класс Order содержит основной метод для работы с доставкой заказа и дополнительные методы — для управления статусом заказа
Обоснование:
Принцип единственной ответственности (Single Responsibility Principle, SRP) из SOLID утверждает, что класс должен иметь только одну причину для изменения, то есть быть ответственным за одну конкретную задачу.
- Класс Order содержит методы для работы с оплатой, доставкой и управлением статусом заказа — нарушает SRP, так как класс выполняет несколько задач: управление оплатой, доставкой и статусом.
- Класс OrderStatusControl содержит методы для управления статусом заказа — соблюдает SRP, так как класс выполняет только одну задачу: управление статусом заказа.
- Класс PaymentDelivery содержит методы для работы с оплатой и доставкой заказа — нарушает SRP, так как класс объединяет две задачи: оплату и доставку.
- Класс Payment содержит методы для работы с оплатой заказа, а класс DeliveryOrderStatusControl — для работы с доставкой и управлением статусом заказа — нарушает SRP, так как DeliveryOrderStatusControl объединяет задачи доставки и управления статусом.
- Класс Order содержит основной метод для работы с доставкой заказа и дополнительные методы — для управления статусом заказа — нарушает SRP, так как объединяет две задачи: доставку и статус заказа.
📌Правильный ответ:
2. Класс OrderStatusControl содержит методы для управления статусом заказа
Вопрос 6
В каком случае НЕ выполняется блок finally при обработке исключений?
- Если возникло исключение в блоке try, но оно не было обработано
- Если возникло исключение в блоке try, и оно было обработано
- Если возникло исключение в блоке catch
- Если программа была принудительно завершена
- Если исключение не возникло
Обоснование:
Блок finally предназначен для выполнения кода, который должен быть выполнен независимо от того, произошло исключение или нет. Обычно он используется для освобождения ресурсов, закрытия файлов, подключения к базе данных и т. д.
Когда блок finally выполняется:
- При отсутствии исключений.
- Если исключение произошло и было обработано (catch).
- Если исключение произошло, но не было обработано.
Когда блок finally НЕ выполняется:
- Если программа была принудительно завершена (например, вызовом System.exit() в Java или аналогичных методов).
- Если возникло исключение в блоке try, но оно не было обработано — блок finally выполняется.
- Если возникло исключение в блоке try, и оно было обработано — блок finally выполняется.
- Если возникло исключение в блоке catch — блок finally выполняется, так как он запускается после завершения обработки в блоке catch.
- Если программа была принудительно завершена — блок finally НЕ выполняется, так как программа завершает свое выполнение немедленно.
- Если исключение не возникло — блок finally выполняется.
📌Правильный ответ:
4. Если программа была принудительно завершена
Вопрос 7
Вы хотите обеспечить доступность полей и методов класса из самого класса, из производного класса, но не из любого места программы. Какой из модификаторов доступа вы будете использовать?
- Только private
- Можно использовать любой модификатор
- Только public
- Protected или private
- Только protected
Обоснование:
Разберем модификаторы доступа:
- private: Поля и методы доступны только внутри самого класса. Они недоступны из производного класса, поэтому этот модификатор не подходит.
- public: Поля и методы доступны из любого места программы, что нарушает указанное требование.
- protected: Поля и методы доступны внутри самого класса и его производных классов, но недоступны из внешнего кода. Это полностью соответствует задаче.
- Можно использовать любой модификатор — неверно, так как требования явно исключают использование public и private.
- Protected или private — частично неверно, так как использование private нарушит требование доступности из производного класса.
📌Правильный ответ:
5. Только protected
Вопрос 9
Вы разрабатываете систему для управления грузоперевозками. У вас есть базовый класс Transport, от которого наследуются классы Truck и Ship. В классе Transport есть метод calculateCost(), который рассчитывает стоимость доставки.
При использовании этого метода в классе Ship возникает ошибка, и программа перестает работать. Какой принцип ООП мог быть нарушен при разработке этой системы?
- Принцип открытости/закрытости
- Принцип подстановки Барбары Лисков
- Принцип инверсии зависимостей
- Принцип разделения интерфейса
- Принцип единственной ответственности
Обоснование:
Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP) гласит, что любой объект класса-наследника должен быть полностью заменяем объектом базового класса без нарушения логики программы.
Если при использовании метода calculateCost() в классе Ship возникает ошибка, это означает, что класс Ship не соответствует ожиданиям, заложенным в базовом классе Transport. Возможно, метод в классе-наследнике реализован с нарушением, либо метод базового класса не учел специфику наследников.
- Принцип открытости/закрытости — относится к модификации кода, но не к корректной работе наследников.
- Принцип инверсии зависимостей — касается проектирования зависимостей между классами, но не поведения наследников.
- Принцип разделения интерфейса — касается дробления интерфейсов на специализированные, что не относится к ситуации.
- Принцип единственной ответственности — относится к тому, чтобы класс выполнял только одну задачу, что здесь не является проблемой.
📌Правильный ответ:
2. Принцип подстановки Барбары Лисков
Вопрос №19:
В какой из ситуаций корректно применить абстрактные классы, а не интерфейсы?
- Когда необходимо уменьшить связанность кода
- Когда нужно создать разноплановые по состоянию классы, но с общим действием (методом)
- Когда нужно использовать множественное наследование
- Когда необходимо хранить общее состояние классов-наследников
- Во всех перечисленных выше случаях
Обоснование:
Абстрактные классы применяются, когда необходимо:
- Реализовать общий функционал для группы классов с возможностью изменения или добавления методов;
- Хранить общее состояние (поля, переменные) для всех классов-наследников;
- Обеспечить наследование общего поведения, оставив часть функционала для реализации в подклассах.
Интерфейсы же предназначены для описания контракта поведения (набора методов), который должен реализовываться классами, но они не содержат состояния или реализации.
- Когда необходимо уменьшить связанность кода — абстрактные классы уменьшают связанность, так как позволяют переиспользовать общий функционал в наследниках.
- Когда нужно создать разноплановые по состоянию классы, но с общим действием (методом) — абстрактный класс позволяет объединить общие методы и свойства, реализуя конкретное поведение в подклассах.
- Когда нужно использовать множественное наследование — это спорное утверждение, так как в языках вроде Java множественное наследование классов запрещено, но допускается множественная реализация интерфейсов. Однако абстрактные классы могут быть полезны для частичного решения этой задачи.
- Когда необходимо хранить общее состояние классов-наследников — это ключевое преимущество абстрактных классов перед интерфейсами, которые не позволяют хранить состояние.
📌Правильный ответ:
5. Во всех перечисленных выше случаях
Вопрос 10
У вас есть иерархия классов для работы с фигурами: базовый класс Figure и два его дочерних класса, Rectangle и Circle. Класс Rectangle имеет дополнительный метод get_area(), который возвращает площадь прямоугольника.
В языках со строгой типизацией, каким образом можно использовать приведение типов для получения площади прямоугольника, используя метод get_area()?
- Создать объект класса Figure, передав в качестве аргумента объект класса Rectangle, и вызвать метод get_area() на объекте класса *Figure`
- Приведение типов в данном случае невозможно
- Создать объект класса Rectangle, передав в качестве аргумента объект класса Figure, и вызвать метод get_area() на объекте класса *Rectangle`
- Привести объект класса Figure к типу Rectangle и вызвать метод get_area() на объекте класса *Rectangle`
- Привести объект класса Rectangle к типу Figure и вызвать метод get_area() на объекте класса *Figure`
Обоснование:
Приведение типов в языках со строгой типизацией, таких как Java или C++, возможно, если объект действительно является экземпляром производного класса. Если у вас есть объект базового класса (Figure), который фактически является экземпляром Rectangle, вы можете привести его к типу Rectangle и использовать специфичный метод get_area().
- **Создать объект класса Figure, передав в качестве аргумента объект класса Rectangle, и вызвать метод get_area() на объекте класса *Figure** — невозможно, так как метод get_area()` не определен в базовом классе Figure.
- Приведение типов в данном случае невозможно — неверно, приведение типов возможно, если объект на самом деле является экземпляром Rectangle.
- *Создать объект класса Rectangle, передав в качестве аргумента объект класса Figure, и вызвать метод get_area() на объекте класса Rectangle` — это неверно, так как Figure не может быть приведен к Rectangle.
- *Привести объект класса Figure к типу Rectangle и вызвать метод get_area() на объекте класса Rectangle` — верно, если объект Figure фактически является экземпляром Rectangle, такое приведение возможно.
- **Привести объект класса Rectangle к типу Figure и вызвать метод get_area() на объекте класса *Figure** — неверно, так как метод get_area()` недоступен для типа Figure.
📌Правильный ответ:
4. Привести объект класса Figure к типу Rectangle и вызвать метод get_area() на объекте класса *Rectangle`.
Вопрос 11
Параметрический полиморфизм осуществляется, в первую очередь, через использование...
Обоснование:
Параметрический полиморфизм — это способность функции или класса работать с различными типами данных, при этом тип определяется параметром. Он реализуется с использованием обобщений (generics). Например, в языках программирования, таких как Java, C++, и C#, для реализации параметрического полиморфизма используются обобщенные классы и методы.
- Множественное наследование — это относится к структурной организации классов и не связано с параметрическим полиморфизмом.
- Виртуальные методы — это механизм реализации динамического полиморфизма, а не параметрического.
- Ковариантность — это относится к совместимости типов при наследовании, но не напрямую к параметрическому полиморфизму.
- Обобщенные классы — это правильный ответ, так как именно обобщения (generics) позволяют работать с параметрическим полиморфизмом.
- Сужение класса — это не относится к параметрическому полиморфизму.
📌Правильный ответ:
4. Обобщенных классов
Вопрос 12
У вас есть модули, которые зависят друг от друга: если вы меняете один модуль, вы должны внести изменения в зависимые модули.
Какой термин используется для описания этой проблемы?
- Проблема модульности
- Проблема связанности
- Проблема сопряжения
- Проблема полиморфизма
- Проблема иерархии модулей
Обоснование:
Сопряжение (coupling) — это мера зависимости между модулями. Чем выше сопряжение, тем больше один модуль зависит от другого. Высокая степень сопряжения приводит к тому, что изменение одного модуля требует изменения других зависимых модулей. Это нарушает принципы модульности и усложняет поддержку и тестирование системы.
- Проблема модульности — модульность связана с разделением системы на независимые модули, но здесь обсуждается не модульность, а взаимозависимость.
- Проблема связанности — связанность (cohesion) касается внутренней согласованности модуля, а не зависимости между модулями.
- Проблема сопряжения — верно, так как это определение описывает зависимость между модулями.
- Проблема полиморфизма — не относится к обсуждаемой ситуации, так как полиморфизм связан с реализацией методов в ООП.
- Проблема иерархии модулей — это не совсем точный термин, так как проблема заключается именно в зависимости, а не в иерархической организации.
📌Правильный ответ:
3. Проблема сопряжения