Занятие №2
ООП в Java
Кернер
Денис
О себе
Кернер Денис
Опыт работы:
Технологии, с которыми работал:
Из личного:
Нам нужно познакомиться со следующими понятиями:
gradlew - обертка над gradle
Unit тесты - обычный Java класс, в котором вы пишете проверки тестируемого вами класса.
Для установки библиотеки Junit ее надо указать в зависимостях build.gradle:
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.5.1')
}
test {
useJUnitPlatform()
}
Чтобы запустить все тесты в проекте: ./gradlew test
Откуда оно появилось?
Класс - статический шаблон, по которому вы можете создавать объекты.
Объекты класса обладают общим поведением этого класса, но своими уникальными данными.
Классификация всегда сложна
Человеческий разум не способен содержать в рабочей памяти больше 5-7 единиц одновременно. (См. статью в вики.)
Реальные же объекты обладают десятками, сотнями параметров.
Как же исследовать сложность этого мира?
Отразить в нашей модели только те свойства и поведение, что отвечают нашей цели. Например, мы ищем оружие:
Два класса "Кошка" с точки зрения бабушки и хирурга ветеринара
Какое, по-вашему, ключевое слово в java отвечает за абстракцию?
Мы разрабатываем программу для кошачьего отеля.
В него хозяева сдают кошек на время отпуска.
Какие характеристики кошки мы должны учитывать?
Представим, что мы написали один класс. Потом второй.
Им нужно использовать друг друга. Представим, что первый класс видит все у второго и беззастенчиво этим пользуется.
Возникнут следующие вопросы:
У класса должна появиться публичная часть - контракт, который он исполняет для своих клиентов. (Спецификация)
Реализация спецификации доступна только классу
final для параметров метода
Инкапсуляция глубже, чем публичные методы доступа к приватным свойствам.
Рассмотрим пример из двух классов:
Класс пациента - Patient
Класс приема лекарств - Treatment.
Практика
Интерфейсы в Java и есть спецификация (контракт).
И вы можете делать инкапсуляцию через них.
public interface EmployeeInfoProvider {
EmployeeInfoJSONDTO of(RemoteUserAccount userAccount);
default EmployeeInfoJSONDTO empty() {
return new EmployeeInfoJSONDTO();
}
}
(Иммутабельность - один из важнейших приемов в программировании.)
При моделировании предметной области, мы часто имеем группы похожих друг на друга сущностей.
Некоторые более общие, другие более специфичны. (От общего к частному)
Получается много дублирующегося кода, по управлению состоянием и поведением для этих классов.
Нам нужен механизм переноса состояния и поведения на дочерние классы.
/* * Транспортное средство * */
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.
Причина - перегрузка методов. (статическая)
Бывает, что мы хотим поменять наследуемое поведение. Наследники могут переопределить методы базового класса.
Рассмотрим небольшой пример
public class Tank extends Vehicle, Weapon {
Наследование в java одиночное.
Почему?
Проектировщики Java решили избежать Deadly Diamond Problem
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.
Если мы пишем логику обработки этих классов и опираемся на конкретных наследников, то любые изменения иерархии классов потребуют изменения по всем основным веткам алгоритмов.
Система становится хрупкой.
Мы хотим один раз написать код, опираясь на методы и свойства базового класса.
Наследники же могут потом переопределять поведение по своему усмотрению.
Класс, который будет выполнять нужный нам метод определится на этапе выполнения.
Таким образом, мы можем расширять поведение системы через добавление новых классов, не меняя основной код.
На уровне классов:
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
На уровне интерфейсов:
public void viaInterfaces() {
Armorable armorable = new Tank();
armorable.takeHit();
}
Tank is taking hit!
Можно смело идти и писать код?
Наследование - очень мощный инструмент.
Но если им пользоваться неправильно, то вырастает громадная и хрупкая иерархия классов, которую сложно поддерживать
Почему так бывает?
Как этого избежать?
Создавайте нужное количество иерархий классов с четко очерченными обязанностями и только одной причиной для их изменения.
После чего создайте класс, включающий другие классы в себя как свойства (композирующий).
Нужный вам класс будет обладать всеми способностями своих составляющих.
Транспортное средство может быть на колесах и\или гусеницах, возможно бронированное, возможно вооруженное. Наивная иерархия классов была бы такой:
И все их вставить в объект класса vehicle:
public class Vehicle {
private MovePlatform movePlatform;
}
public class MilitaryVehicle extends Vehicle {
private Weapon weapon;
private Armor armor;
}
Реализовать следующую предметную область:
Оружие. Может быть дальнобойным (лук) и ближнего боя (меч).
Может наносить дополнительный урон огнем или холодом.
Урон - это объект со свойствами (физический урон\ урон огнем\ урон холодом)
*** (необязательно)
Напишите тесты, проверяющие, что получен ожидаемый урон.
Для вас подготовлена заготовка домашнего задания, можете клонировать ее (в репозитории unit-2/homework):
https://github.com/SiberianIntegrationSystems/JavaCourse2019
Прислать письмо со ссылкой на проект нам, на эл. почту:
checkhomework.sis@gmail.com
Формат письма:
Тема: Имя Фамилия, номер домашнего задания.
В тексте письма ссылка на репозиторий
Крайний срок: 15 августа 2019 года.
В презентации использованы иллюстрации из книги Гради Буча 'Объектно ориентированное проектирование и анализ'