article-spots
article-carousel-spots
programs
Материалы

Основные принципы ООП

14 янв. 2020

Предлагаем ознакомиться с основными принципами объектно-ориентированного программирования – одной из важнейших методологий разработки, которая основана на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определённого класса, а классы образуют иерархию наследования.

Инкапсуляция

Одним из самых важных факторов при проектировании компонентов приложения является сокрытие внутренних данных компонента и деталей его реализации от других компонентов приложения и предоставление набора методов для взаимодействия с ним (API). Этот принцип является одним из четырёх фундаментальных принципов ООП и называется инкапсуляцией.

Правильная инкапсуляция важна по многим причинам:

1. Она способствует переиспользованию компонентов: поскольку в этом случае компоненты взаимодействуют друг с другом только посредством их API и безразличны к изменениям внутренней структуры, они могут использоваться в более широком контексте.

2. Инкапсуляция ускоряет процесс разработки: слабо связанные друг с другом компоненты (то есть компоненты, чей код как можно меньше обращается или использует код других компонентов) могут разрабатываться, тестироваться и дополняться независимо.

3. Правильно инкапсулированные компоненты более легки для понимания и процесса отладки, что упрощает поддержку приложения.

В языке Java инкапсуляция реализована с помощью системы классов, которые позволяют собрать информацию об объекте в одном месте; пакетов, которые группируют классы по какому-либо критерию, и модификаторов доступа, которыми можно пометить весь класс или его поле или метод.

Всего модификаторов доступа четыре:

  • public – полный доступ к сущности (полю или методу класса) из любого пакета;
  • protected – доступ к сущности только для классов своего пакета и наследников класса;
  • неявный модификатор по умолчанию (при отсутствии трёх явных) – доступ к сущности только для классов своего пакета;
  • private – доступ только внутри класса, в котором объявлена сущность.

Для достижения правильной инкапсуляции также необходимо предоставить корректный API для работы с компонентом. Например, в сеттер для переменной можно включить логику по проверке передаваемых значений либо не предоставлять сеттеры в классе вовсе, если класс должен быть доступен только для чтения.

Пример корректной инкапсуляции класса:

В примере выше значение переменной name задаётся при создании объекта и не может быть изменено извне, так как сеттер для переменной отсутствует. В сеттере для переменной age реализована проверка на корректность передаваемого параметра и выброс исключения при неверном значении.

Наследование

Наследование является одним из важнейших принципов объектно-ориентированного программирования, поскольку оно позволяет создавать иерархические структуры объектов. Используя наследование, можно создать общий класс, который будет определять характеристики и поведение, свойственные какому-то набору связанных объектов. В дальнейшем этот класс может быть унаследован другими, более частными классами, каждый из которых будет добавлять уникальные, свойственные только ему характеристики и дополнять или изменять поведение базового класса. В терминах Java такой общий класс называется суперклассом (superclass), или базовым классом (base class), или классом-родителем (parent class), а класс, его наследующий, - подклассом (subclass), или дочерним классом (child class), или классом-потомком (derived class).

Наследование реализует отношение «является» (“is-a”) между суперклассом и подклассом. Пусть, например, классы Employee и Manager представляют собой абстракцию понятий «Сотрудник» и «Менеджер». Каждый менеджер также является сотрудником компании, в которой он работает, следовательно, класс Manager находится в отношении “is-a” с классом Employee. Таким образом, с точки зрения наследования, при выстраивании иерархии классов класс Employee будет являться суперклассом, а класс Manager – дочерним классом. При этом класс, который является наследником какого-либо класса, может быть суперклассом для одного или нескольких других классов. Также в отличие, например, от C++, в Java в строгом смысле отсутствует множественное наследование, то есть любой класс может иметь не более одного класса-родителя. А все классы, суперкласс у которых явно не указан, по умолчанию наследуются от класса Object.

Класс Employee в примере выше является суперклассом не потому, что он главнее класса Manager или содержит больше функциональности. На самом деле верно обратное: функциональность подклассов не ýже, а зачастую существенно шире, чем функциональность их классов-родителей. Приставки «супер-» и «под-» пришли в Java из математики: множество всех менеджеров содержится во множестве всех сотрудников и, таким образом, является подмножеством множества сотрудников.

Для того чтобы унаследовать какой-либо класс в Java, используется ключевое слово extends:

В примере выше класс Employee является базовым классом для класса Manager, а класс Manager – подклассом класса Employee. Класс Employee абстрагирует базовые характеристики для всех сотрудников компании – имя, фамилию, размер оклада и дату приёма на работу, а класс Manager дополняет эти характеристики процентом премии для менеджеров и меняет поведение метода getSalary() базового класса, используя полиморфизм.

Полиморфизм

При рассмотрении полиморфизма необходимо помнить, что этот принцип неразрывно связан с другим принципом ООП – наследованием, которое помогает реализовать полиморфизм.

Возьмем для примера абстрактный класс «Автомобиль», который наследуют два конкретных класса – «Спортивный автомобиль» и «Грузовой автомобиль».

И спортивные, и грузовые автомобили будут обладать общими характеристиками и будут иметь возможность выполнять общие для всех автомобилей действия, которые указаны в абстрактном классе-родителе, но конкретная реализация этих действий может быть разной.

Например, общее для всех автомобилей действие «завестись» у спортивного автомобиля может быть реализовано путем нажатия кнопки, а у грузового - с помощью ключа. Один результат – разные решения. В этом и состоит полиморфизм.

Более строго, полиморфизм - один из принципов ООП, позволяющий вызовом переопределённого метода через переменную класса-родителя получить поведение, которое будет соответствовать реальному классу-потомку, на который ссылается эта переменная.

Код выше представляет пример полиморфизма. Сначала переменной родительского класса Vehicle присваивается объект дочернего класса SportCar. При вызове метода start() на консоль будет выведено: "Starting my fancy sport car!"

При дальнейшем присвоении этой же переменной объекта дочернего класса Truck и вызове того же метода start() на консоль будет выведено: "Starting my heavy truck!"

Абстракция

Относительно недавно в качестве самостоятельного четвёртого принципа начали выделять абстракцию.

Одно из определений слова «абстракция», которые можно встретить в современных словарях:

Абстракция (от лат. abstractio — выделение, отвлечение или отделение) — теоретический прием исследования, позволяющий отвлечься от некоторых несущественных в определенном отношении свойств изучаемых явлений и выделить свойства существенные и определяющие.

Все языки программирования предоставляют их пользователю определённые абстракции. Так, языки семейства ассемблер являются в своём роде абстракцией соответствующих микропроцессоров, поскольку позволяют отвлечься от деталей их реализации и общаться с ними через определённый набор более высокоуровневых инструкций. Императивные языки программирования, последовавшие за ассемблером, например Basic, Fortran, C, являются более высоким уровнем абстракции над ассемблерными языками – они дают возможность использовать более привычные человеку синтаксические конструкции за счёт приближения синтаксиса к естественным языкам.

Объектно-ориентированные языки, такие как Java, выводят разработку на ещё более высокий уровень абстракции: объекты в ООП по своей сути представляют собой модели понятий окружающего мира, таких как Работник, Сервер, Запись в дневнике, и выделяют только те свойства этого понятия, которые необходимы в конкретном случае для решения конкретной проблемы.

Например, класс Student в приложении учёта студентов университета, кроме общих полей, таких как имя, фамилия, дата рождения и т.д., будет содержать поля, отражающие информацию о номере зачётной книжки, статусе студента (действующий, академический отпуск, отчислен), факультете, номере его группы, оценках за семестры и т.д. Но для того же класса Student в приложении учёта студентов в тренинг-центре EPAM такая информация будет неактуальна: класс будет содержать поля, отражающие учебный проект, на который был распределён студент, уровень его английского языка по результатам последнего тестирования, количество посещаемых мероприятий и т.д.

В этом и состоит абстракция: фокусировка разработчика на конкретных свойствах объекта зависит от тех задач, которые призван решать объект. Следствием такого подхода является то, что, если в императивных языках разработчику необходимо думать в терминах компьютерной логики, в объектно-ориентированных языках разработчик думает в терминах проблемной области, в которой он разрабатывает приложения.