Занятие №2

ООП в Java

Кернер

Денис

Кернер

Денис

Занятие №2

ООП в Java

О себе

Кернер Денис

Опыт работы:

Технологии, с которыми работал:

  • Базы данных: PostgreSQL, Intersystems Cache, MongoDB
  • Бэкэнд: Java, Spring framework, COS Cache, Node.js
  • Фронтэнд: TypeScript, Angular (2-7), ActionScript, JavaScript

Из личного:

  • Женат, двое сыновей
  • Люблю фантастику в книгах и фильмах
  • Летом на работу катаюсь на велосипеде, хотя по мне и незаметно
  • с 2009 проработал в СИС от стажера до тимлида

План занятия

  • Предварительные требования
  • Что такое ООП?
  • Основные принципы ООП
  • Реализация ООП в java
  • Примеры
  • Домашнее задание
  • Полезные ссылки
  • IDE (integrated development environment)
  • Cистема сборки (Gradle)
  • Юнит тесты (Unit tests)

Предварительные требования

Мы будем работать со следующими инструментами:

Unit тесты - обычный Java класс, в котором вы пишете проверки тестируемого вами класса.

Для установки библиотеки Junit ее надо указать в зависимостях build.gradle:

Тесты - Junit

dependencies {
    testImplementation('org.junit.jupiter:junit-jupiter:5.5.1')
}

test {
    useJUnitPlatform()
}

Как запустить тесты  - Gradle

Чтобы запустить все тесты

в проекте: ./gradlew test

Что такое объект в реальном мире?

Кот Пушок, 2015 г.р., вес 3.5 кг, любит сметану и спать по 12 часов.

Машина хозяина Пушка, пробег 100т.км, нуждаются в замене подушки двигателя. Умеет разгоняться до 100 км/ч за 11 секунд

ООП парадигма

ООП предлагает строить программу на основе объектов и взаимодействия между ними с помощью сообщений.

 

Этот способ отличается от алгоритмической декомпозиции и функциональной парадигмы.

 

В основе объект - с состоянием и

методами

Автор идеи ООП и языка SmallTalk Алан Кей

Всё можно представить объектом

  • Мы должны построить модель на основе объектов реального мира.
  • Данную модель мы назовем классом.
  • В модели мы определим типы данных и операции над ними.
  • Экземпляр этой модели мы будем называть объектом (класса).

Класс - шаблон объекта

Объект - это экземпляр класса. Он обладает уникальностью, а также состоянием.

Состояние - это значения типов данных, заложенных в класс.

 

Класс, чертеж, шаблон

Уникальная скульптура по шаблону

Взаимосвязь классов и объектов

Классы пишут программисты

Объекты создаются во время работы программы

Как написать свой класс?

Чтобы построить модель реального мира, нужно правильно применить абстракцию.

 

Абстракция - выделение нужного.

 

В чем сложность? Как понять, что нужно?

 

 

Цель определяет правильную абстракцию

Два класса "Кошка" с точки зрения бабушки и хирурга ветеринара

Пример абстракции #1. Отель для кошек

Мы разрабатываем программу для кошачьего отеля.

Владельцы сдают кошек в отель на время отпуска.

 

Что мы должны учитывать?

Пример абстракции #2. Магазин кошек

Мы разрабатываем сайт для продажи породистых кошек.

 

Что мы должны учитывать?

Реализуем абстракцию - создаем Java класс.

Из чего состоит класс породистой кошки на продажу?

 

public class Cat {
    String[] awards;
    String passportCode;
    String name;
    String color;
    double weight;
    String[] owners;
    Long cost;
    LocalDate birthDay;
}

Создаем объект Java

  • Объект - это экземпляр класса.
  • Для его создания нужно применить оператор new к конструктору класса.
  • Конструктор - метод, отвечающий за подготовку объекта к использованию.
Cat leopold = new Cat();
String[] awards = new String[] {"Британец 2016", "Порода 2017г."};
String[] owners = new String[] {"Петр Горевой", "Иван Драго"};
LocalDate birthDay = LocalDate.of(2010, 1, 1);

Cat tom = new Cat(awards, "ID-01121", 
                  "Пушок", "grey", 3.5, owners,
                   5000L, birthDay);
        

Как получить доступ к данным объекта?

Объект живет во время работы программы. Его данные хранятся в области памяти, называемой кучей, или heap.

 

Мы можем обращаться к полям объекта через ссылочную переменную (reference) и оператор точки "."

 

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

Cat tom = new Cat(awards, "ID-01121", 
                  "Пушок", "grey", 3.5, owners,
                   5000L, birthDay);
System.out.println(tom.color);
        

Ссылочные переменные. Иллюстрация

 tom = leopold;

Действия над объектами

  • Объекты в реальном  мире могут взаимодействовать друг с другом.
  • В ООП объекты могут обмениваться сообщениями.
  • Сообщение - это вызов метода объекта.
  • Нельзя отправлять сообщение, которое объект не может обработать.

*Рекомендуется делить методы на команды и запросы.

шаблон CQS от Б. Мейера.

Пример в Java

Чтобы отправить сообщение объекту, вы вызываете соответствующий метод класса с аргументами.

Синтаксис - ссылочная переменная + точка + метод и аргументы.

 

 Cat cat = new Cat();
 String meowString = cat.speak();

Существуют правила вызова.

Метод класса - это блок кода, отвечающий за обработку сообщений, которые принимает объект.

  String speak() {
        return paint() + meow;
  }

Правило #1. Вас должны понять

  • Метод объекта надо вызвать с правильными аргументами.
  • Аргументы должны соответствовать параметрам метода.
  • Параметры - это список переменных, которые метод принимает на вход. У них есть название и тип.
 void sleep(int duration, String site) {...} 
Cat cat = new Cat();
cat.sleep(120, "Кухня");

Вызов метода с правильными аргументами

Методы должны быть уникальны. Иметь свой "отпечаток", сигнатуру метода.

Компилятор не даст вызвать метод с некорректными аргументами.

Структура метода

Метод состоит из сигнатуры и тела (блок кода)

Сигнатура метода - это модификатор доступа, тип возврата, название метода,  параметры 

 public void sleep(int duration, String site) {
        // some code ...
    }

***Java  class может иметь несколько методов с одним названием, но разным набором параметров.  (перегрузка методов).

***Блок кода есть, если метод не абстрактный.

Правило #2. Вам доступен метод?

Вам должны позволить вызвать метод, т.к. класс может ограничить доступ к своим методам. Если вы попробуете нарушить это правило, то получите ошибку компиляции.

 

Зачем это нужно?

private void lickTummy() {
     ... 
    

Кота можно заставить умываться, но как именно - его дело.

Поэтому часть полей и методов мы закрываем, прячем.

Сокрытие информации необходимо для инкапсуляции

1 принцип ООП. Инкапсуляция

Слово "инкапсуляция" происходит от латинского in capsula — "размещение в оболочке".

 

Цель - сохранение целостности состояния  объекта ("state")

 

Для этого мы можем применить скрытие информации. Тогда мы сможем защитить внутреннее содержимое объекта.

 

Если это не сделать, произойдет..

К чему ведет нарушение инкапсуляции

Как сохранить инкапсуляцию?

Чтобы обеспечить "капсулу", нужен контракт, между нашим классом и клиентом, который его использует (обычно это другой класс).

 

Контракт говорит о том, что можно сделать с объектом и что нельзя.

 

Всё, что вне контракта - мы скрываем, используя модификаторы доступа (information hiding).

 

В Java такой контракт часто оформляют как интерфейс (interface).

Программирование по контракту

  • Пред условие (pre condition)
    • Требования к вызову метода, какие условия должны соблюдаться, чтобы метод можно было выполнить. Иначе - мы возвращаем ошибку (исключение).
  • Пост условие (post condition)
    • Что гарантирует метод после его завершения. Какой результат мы можем ожидать.

 

  

Пример пред/пост условия в контракте

Достаточно ли скрытия данных для инкапсуляции?

Нет. Состояние можно сломать косвенно.

 

 

private String[] owners;
public String[] getOwners() {
   return owners; }
@Test
public void testBirthDayChange() {
  ..
  cat.getOwners()[cat.getOwners().length - 1] = "Пушок";
  System.out.println("Теперь Пушок сам себе хозяин!");
  System.out.println(Arrays.toString(cat.getOwners()));   

***Сохранение состояния одна из самых сложных задач в ООП

Теперь Пушок сам себе хозяин!
[Петр Горевой, Пушок]

Инкапсуляция - итоги:

  • Отделяем публичную часть (контракт) от реализации.
  • Не даем реализации просочиться наружу.
  • Сохраняем внутреннее состояние класса.
  • Если даем доступ к внутреннему состоянию извне, делаем его иммутабельным

***Иммутабельность (неизменямость) - один из важнейших приемов в программировании.

Что если требуется похожий класс?

Хорошие новости!

К нам пришел крупный магазин собак, желающий работать онлайн, когда сможем подключить его к нашему сайту?

Завтра сможем? У меня показ в понедельник, хотелось бы подготовиться заранее и в пятницу попробовать на демо стенде.

Наша архитектура кото-ориентирована, мы не проектировали ее под собак.

***сегодня четверг

Что делать?

  • Выдавать кошку за собаку

 

  • Копировать почти одинаковый код
  • Применить еще один принцип ООП?

Принцип #2. Наследование

Наследование - механизм переноса состояния и поведения на дочерние классы.

Наследование образует иерархию классов

Наследование - сильная связь между классами

Наследование имеет смысл только при сильной связи между классами объектов.

Связь is a, kind of, рода

 

 

Нельзя применять наследование, чтобы просто получить код из базового класса.

Такой код неустойчив  и плохо сопровождается.

Иногда очень сложно понять какого рода класс

Не применять наследование без связи "kind of"

Наследование - синтаксис Java

/**
 * Животное для продажи
 */
public class Animal {
    public Animal() {}
  ...
 }
public class Cat extends Animal {
  @Override
  protected String paint() {
     ...
public class Dog extends Animal {
  @Override
  protected String paint() {
     ...

Как нарисовать "животное"?

ключевое слово abstract

Как наследникам переопределить методы базового класса?

@Override поможет компилятору искать метод с такой же сигнатурой в базовом классе. И выдавать ошибку, если его нет.

 

Иначе вы просто перегрузите метод, а базовое поведение останется не тронуто!

 

Написать свою реализацию с той же сигнатурой и аннотацией @Override

 

У класса может быть только один родитель

public class Lion extends Cat, WildAnimal {

Проектировщики Java

решили избежать

Deadly Diamond Problem

из c++

Как заставить класс исполнять несколько контрактов?

Мы можем создать несколько интерфейсов

public interface Breed {
   /**
     * Награды
     */
    String[] getAwards();
   /**
     * Код родословной
     */
    String getPassportCode();
    String getName();
}
public interface Pet {
    /** список владельцев
     */
    String[] getOwners();
    String getName();
}

Породистое животное (с документом)

Питомец

И реализовать их в одном классе!

Получаем необходимую гибкость + возможность применения default методов.

Что же с Diamond Problem?

 @Override
 public int getName() {
      return Pet.super.getName();
 }  

Как работать с классами наследниками?

Java статически типизирован. Мы заранее указываем тип, с которым работаем.

 

Должны ли мы создавать отдельную обработку для всех наследников базового класса?

 

Как бы нам уметь обращаться с многими наследниками как с одним классом, имеющим множество форм!?

 

 

Полиморфизм - множество форм

Полиморфизм — (греч.) имеющий много форм.

 

Способность переменной /метода/ класса принимать значения различных типов.

Полиморфизм - применение

Мы хотим один раз написать код, опираясь на методы и свойства базового класса.

 

У наследников поведение методов можно изменить.

 

Класс, который будет выполнять нужный нам метод, определится во время выполнения (позднее связывание).

Полиморфизм - возможности 

Таким образом, мы можем расширять поведение системы через добавление новых классов, не меняя основной код, обрабатывающий объекты базового класса.

Upcasting, Downcasting

Upcasting:

Animal animal = (Animal) dog;
Dog dog = (Dog) animal;

Downcasting:

Полиморфизм - пример на java, классы

На уровне классов:

Animal animalCat; 
animalCat = (Animal) cat;
animalCat.paint();
 ,_     _
 |\\_,-~/
 / _  _ |    ,--.
(  @  @ )   / ,-'
 \  _T_/-._( (
 /         `. \
|         _  \ |
 \ \ ,  /      |
  || |-_\__   /
 ((_/`(____,-'

upcasting

  __      _
o'')}____//
 `_/      )
 (_(_/-(_/
Animal animalDog; 
animalDog = (Animal) dog;
animalDog.paint();

Полиморфизм - решение java, интерфейсы

Интерфейсы тоже позволяют использовать полиморфизм

public void viaInterfaces() {
   Breed breedCat = new Cat(/*параметры*/);
   breedCat.getPassportCode();
}
"ID-232323"

Мы рассмотрели принципы ООП

  • Инкапсуляция
  • Наследование
  • Полиморфизм

Можно смело идти и писать код?

Давайте узнаем об еще одном типе связей между классами - композиции

Предпочитайте композицию вместо наследования

  • Композиция позволяет избежать ненужного наследования. Вы просто включаете в ваш класс объект с нужным поведением.
  • На самом деле мы уже применяли композицию - вспомните класс Bargain. Мы не создавали иерархию кошачьих и собачьих сделок.

Как применять композицию?

Создавайте нужное количество иерархий классов с четко очерченными обязанностями и только одной причиной для их изменения.

 

После чего создайте класс, включающий другие классы в себя как свойства (композирующий).

 

Нужный вам класс будет обладать всеми способностями своих составляющих.

 

Композиция - пример

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

 

Теперь мы можем создать класс кошка и собака, со свойствами Breed и Pet, не дублируя код

 

Также мы можем создать льва, который на выставках не выступает и питомцем не состоит

 

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

 

 

 

Домашнее задание (ч.1)

У вас есть:

Оружие. Наносит физический урон и дополнительный урон огнем или холодом.

Урон - это объект со свойствами (физический урон\ урон огнем\ урон холодом)

Ваша задача сделать новое оружие - "меч ночи в якутске". Этот меч наносит дополнительный урон холодом (50)

Так же создайте меч "пылающий асфальт", дающий урон огнем 50.

Потом создайте наследников от класса цели - ледяной великан, иммунитет к холоду. И эфрит - иммунитет к огню.

*** (необязательно) Напишите тесты, проверяющие, что получен ожидаемый урон.

 

Заготовка домашнего задания на github

Домашнее задание (ч.2)

 

Прислать письмо со ссылкой на проект нам, на эл. почту:

checkhomework.sis@gmail.com

Формат письма:

 

Тема: Имя Фамилия, номер домашнего задания.

В тексте письма ссылка на репозиторий

Крайний срок: 12 марта 2020 года.

 

Полезные ссылки

Спасибо за внимание!

В презентации использованы иллюстрации из книги Гради Буча 'Объектно-ориентированное проектирование и анализ'

JavaSIS#3.20 Лекция 2. ООП

By Dennis Kerner

JavaSIS#3.20 Лекция 2. ООП

  • 318