Занятие №2
ООП в Java
Кернер
Денис
О себе
Кернер Денис
Опыт работы:
- с 2009 проработал в СИС от стажера до ведущего разработчика (тимлид)
Технологии, с которыми работал:
- Базы данных: PostgreSQL, Intersystems Cache, MongoDB
- Бэкэнд: COS Cache, Java, Spring framework, немного Node.js
- Фронтэнд: TypeScript, Angular (2-7), ActionScript, JavaScript
Из личного:
- Женат, двое сыновей
- Люблю фантастику в книгах и фильмах
- Летом на работу катаюсь на велосипеде, хотя по мне и незаметно
План занятия
- Предварительные требования
- Почему ООП?
-
Основные принципы ООП
- Абстракция
- Инкапсуляция
- Наследование
- Полиморфизм
-
Реализация ООП в java
- Классы и интерфейсы
- Наследование и композиция
- Итоги - мы выучили ООП?
- Домашнее задание
- Полезные ссылки
- IDE (integrated development environment)
- Cистема сборки (Gradle)
- Юнит тесты (Unit tests)
Предварительные требования
Нам нужно познакомиться со следующими понятиями:
- редактировать код
- выполнять рефакторинг
- запускать программу в вашем окружении
IDE - Idea community edition
Система сборки - Gradle
gradlew - обертка над gradle
- gradlew assemble - сборка проекта
- gradlew test - запуск тестов
- gradlew jar - собрать jar архив с вашим проектом
Основные операции Gradle
Unit тесты - обычный Java класс, в котором вы пишете проверки тестируемого вами класса.
Для установки библиотеки Junit ее надо указать в зависимостях build.gradle:
Тесты - Junit
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.5.1')
}
test {
useJUnitPlatform()
}
Чтобы запустить все тесты в проекте: ./gradlew test
Приступим к изучению ООП!
Откуда оно появилось?
Как раньше писали программы?
Как писать с точки зрения ООП?
Преимущества ООП
- Позволяет описывать сложные предметные области
- Абстрагируется от деталей, моделируя задачу как набор объектов, связанных друг с другом отношениями и сообщениями
Классы и объекты
Класс - статический шаблон, по которому вы можете создавать объекты.
Объекты класса обладают общим поведением этого класса, но своими уникальными данными.
Структура классов и объектов
В чем сложность ООП?
Классификация всегда сложна
Как преодолеть эту сложность?
- Итеративное проектирование
- Знание основных принципов ООП
Абстракция - проблема
Человеческий разум не способен содержать в рабочей памяти больше 5-7 единиц одновременно. (См. статью в вики.)
Реальные же объекты обладают десятками, сотнями параметров.
Как же исследовать сложность этого мира?
Абстракция - решение
Отразить в нашей модели только те свойства и поведение, что отвечают нашей цели. Например, мы ищем оружие:
Абстракция - пример
Два класса "Кошка" с точки зрения бабушки и хирурга ветеринара
Абстракция - вопрос 1
Какое, по-вашему, ключевое слово в java отвечает за абстракцию?
Абстракция - вопрос 2
Мы разрабатываем программу для кошачьего отеля.
В него хозяева сдают кошек на время отпуска.
Какие характеристики кошки мы должны учитывать?
Осталось три главных слова, какие?
Инкапсуляция - проблематика 1
Представим, что мы написали один класс. Потом второй.
Им нужно использовать друг друга. Представим, что первый класс видит все у второго и беззастенчиво этим пользуется.
Возникнут следующие вопросы:
- Как первый класс использует второй?
- На уровне переменных?
- Видит ли он реализацию?
- Что будет, когда классов станет очень много, а связей между ними еще больше?
Пример плохой инкапсуляции:
Инкапсуляция - цели
- Уменьшить сложность взаимодействия между классами
- Сохранение целостности внутреннего состояния класса
Инкапсуляция - решение
У класса должна появиться публичная часть - контракт, который он исполняет для своих клиентов. (Спецификация)
Реализация спецификации доступна только классу
Инкапсуляция - через модификаторы доступа
final для параметров метода
Инкапсуляция - типовые ошибки
Инкапсуляция глубже, чем публичные методы доступа к приватным свойствам.
Рассмотрим пример из двух классов:
Класс пациента - Patient
Класс приема лекарств - Treatment.
Практика
Инкапсуляция - через интерфейсы
Интерфейсы в Java и есть спецификация (контракт).
И вы можете делать инкапсуляцию через них.
public interface EmployeeInfoProvider {
EmployeeInfoJSONDTO of(RemoteUserAccount userAccount);
default EmployeeInfoJSONDTO empty() {
return new EmployeeInfoJSONDTO();
}
}
Инкапсуляция - итоги:
- Отделяем публичную часть (спецификацию) от реализации.
- Не даем реализации просочиться наружу.
- Сохраняем внутреннее состояние класса.
- Если даем доступ к внутреннему состоянию извне, делаем его иммутабельным.
(Иммутабельность - один из важнейших приемов в программировании.)
Наследование - проблема
При моделировании предметной области, мы часто имеем группы похожих друг на друга сущностей.
Некоторые более общие, другие более специфичны. (От общего к частному)
Получается много дублирующегося кода, по управлению состоянием и поведением для этих классов.
Наследование - решение
Нам нужен механизм переноса состояния и поведения на дочерние классы.
Наследование - решение Java
/* * Транспортное средство * */
public abstract class Vehicle {
protected String destination;
protected abstract void move();
public void travel(String destination) {
this.destination = destination;
move(); } }
public class Bike extends Vehicle {
@Override
public void move() {
System.out.println("Driving fast on my bike to "
+ destination); } }
public class Plane extends Vehicle {
@Override
public void move() {
System.out.println("Flying high to " + destination); } }
Наследование - переопределение методов.
При этом обязательно надо применять аннотацию @Override.
Причина - перегрузка методов. (статическая)
Бывает, что мы хотим поменять наследуемое поведение. Наследники могут переопределить методы базового класса.
Рассмотрим небольшой пример
Наследование - ограничения Java
public class Tank extends Vehicle, Weapon {
Наследование в java одиночное.
Почему?
Проектировщики Java решили избежать Deadly Diamond Problem
Наследование - ограничения Java. Как преодолеть?
public interface Weaponable {
default int getAttackLevel() {
System.out.println("Can shoot with " + " 100 attack level");
return 100; }
}
public interface Armorable {
default int getAttackLevel() {
System.out.println("Can defence from 100 attack level");
return 100; }
}
public class Tank extends Vehicle implements Weaponable, Armorable {
...
@Override
public int getAttackLevel() {
return Weaponable.super.getAttackLevel();
} ... }
Наследование - преимущества интерфейсов
Получаем возможность определения нескольких интерфейсов и дополнительный слой абстракции.
Что же с Diamond Problem?
Мы должны явно определить метод, который используем.
@Override
public int getAttackLevel() {
return Weaponable.super.getAttackLevel();
}
Полиморфизм - проблематика
Допустим, у нас есть базовый класс и наследники.
Vehicle и Bike.
Если мы пишем логику обработки этих классов и опираемся на конкретных наследников, то любые изменения иерархии классов потребуют изменения по всем основным веткам алгоритмов.
Система становится хрупкой.
Полиморфизм - решение
Мы хотим один раз написать код, опираясь на методы и свойства базового класса.
Наследники же могут потом переопределять поведение по своему усмотрению.
Класс, который будет выполнять нужный нам метод определится на этапе выполнения.
Таким образом, мы можем расширять поведение системы через добавление новых классов, не меняя основной код.
Полиморфизм - решение java, классы
На уровне классов:
public void viaClasses() {
Vehicle vehicle = getVehicle("Paris");
vehicle.travel("Paris");
vehicle = getVehicle("berlin");
vehicle.travel("Berlin");
vehicle = getVehicle("moscow");
vehicle.travel("moscow");
}
Driving fast on my bike to Paris
Crawling to Berlin
Flying high to moscow
downcasting
Полиморфизм - решение java, интерфейсы
На уровне интерфейсов:
public void viaInterfaces() {
Armorable armorable = new Tank();
armorable.takeHit();
}
Tank is taking hit!
Мы рассмотрели абстракцию и основные принципы ООП
- Инкапсуляция
- Наследование
- Полиморфизм
Можно смело идти и писать код?
Наследование - чего остерегаться
Наследование - очень мощный инструмент.
Но если им пользоваться неправильно, то вырастает громадная и хрупкая иерархия классов, которую сложно поддерживать
Почему так бывает?
Как этого избежать?
Композиция - решение проблемы наследования
Создавайте нужное количество иерархий классов с четко очерченными обязанностями и только одной причиной для их изменения.
После чего создайте класс, включающий другие классы в себя как свойства (композирующий).
Нужный вам класс будет обладать всеми способностями своих составляющих.
Композиция -пример
Транспортное средство может быть на колесах и\или гусеницах, возможно бронированное, возможно вооруженное. Наивная иерархия классов была бы такой:
Композиция -пример
А можно создать три иерархии классов:
Композиция - пример ч2
И все их вставить в объект класса vehicle:
public class Vehicle {
private MovePlatform movePlatform;
}
public class MilitaryVehicle extends Vehicle {
private Weapon weapon;
private Armor armor;
}
Домашнее задание (ч.1)
Реализовать следующую предметную область:
Оружие. Может быть дальнобойным (лук) и ближнего боя (меч).
Может наносить дополнительный урон огнем или холодом.
Урон - это объект со свойствами (физический урон\ урон огнем\ урон холодом)
*** (необязательно)
Напишите тесты, проверяющие, что получен ожидаемый урон.
Для вас подготовлена заготовка домашнего задания, можете клонировать ее (в репозитории unit-2/homework):
https://github.com/SiberianIntegrationSystems/JavaCourse2019
Домашнее задание (ч.2)
Прислать письмо со ссылкой на проект нам, на эл. почту:
checkhomework.sis@gmail.com
Формат письма:
Тема: Имя Фамилия, номер домашнего задания.
В тексте письма ссылка на репозиторий
Крайний срок: 15 августа 2019 года.
Полезные ссылки
Спасибо за внимание!
В презентации использованы иллюстрации из книги Гради Буча 'Объектно ориентированное проектирование и анализ'
JavaSIS#19 unit 2
By Dennis Kerner
JavaSIS#19 unit 2
- 375