JAVA. ПУТЬ ОТ УЧЕНИКА ДО ЭКСПЕРТА.
Теоретический материал. Практические задания.
Разбор решений. Комментарии.
Арсентьев П.С. 2016.
Предисловие.
Только идущий пройдет дорогу до конца. Скорее всего Вы уже пробовали
освоить язык программирования Java. Периодически посещаете тематические
формы, продолжаете читать книги, проходить курсы и так по кругу. Но всего
чего Вы добились это пару не доделанных калькуляторов и задуманная новая
социальная сеть. Не отчаивайтесь, у вас есть выход. Перед собой вы держите
книгу, которая позволит вам достичь желаемого уровня компетенции в
программировании. В книге собраны самые популярные технологии и
библиотеки, которые Вы будете использовать в своей дальнейшей работе.
Книга построена по шаговой схеме. Каждая глава — это отдельное тема и
практическое задание. Далее каждое практические задание – это маленький
элемент в главном проекте всего курса.
Полный видео курс доступен по ссылке
Благодарности.
Это мой первый опыт создания книги. Изначально мне казалось, что написать
книгу будет просто и быстро. Составил план и каждый день писать по главе,
так можно закончить книгу за месяц. Оказалось, все не так просто. Переносить
свои мысли на бумагу - медленный и подчас утомительный процесс. Иногда
мысли приходят быстро, и глава пишется за день, а иногда даже тяжело себя
заставить просто открыть редактор и начать писать. Поэтому я особо
благодарен своей семье, которая меня мотивировала завершить книгу. Но
особую благодарность хотел выразить людям, которые отозвались помочь
первыми проверить на себе мой труд и сделать эту книгу в несколько раз
лучше.
Андрей Чернов
Маркус Бутримас
Александр Демин
Андрей Горбунов
Об авторе.
Несколько слов хотел сказать о себе.
Меня зовут Петр Арсентьев.
Коммерческим программированием
я стал заниматься с 2008 года на 5
курсе института. В большинстве
проектов, в которых я участвовал,
использовалась Java, как базовый
язык. С 2014 года я занимаюсь
обучением программированию.
Часто спрашивают, зачем я
занимаются обучением? Ответ
простой. Мне нравится передавать
свои знания людям, смотреть как они
достигают свои цели.
Помогать людям устраиваться на работу и осуществлять свои мечты.
Обратная связь
Так же буду рад получить от вас письма на почту parsentev@[Link] с
отзывами, вопросами и рекомендациями по улучшению книги и самого
курса.
Как правильно проходить курс.
Вначале прохождения курса, я настоятельно рекомендую создать план. Это
очень важный момент. Предисловие этой книги начинается с крылатой фразы.
Смысл фразы, что нужно постоянно заниматься программированием. Но
особый момент стоит уделить срокам этого плана. Так же не надо
перепрыгивать через занятия. Если не сделали задание, нужно любыми
способами его доделать. Пусть оно не будет до конца выполнять условия, но в
каком-то виде оно должно быть сделано.
Весь курс состоит из 27 занятий. Каждое занятие состоит из видео материала
и практического задания. Задания связаны друг с другом. Давайте рассмотрим
пример вашего плана. Минимальный срок прохождения курса – это выполнять
одно задание каждый день. То есть просмотреть видео и выполнить задание.
Следовательно, курс можно пройти за 27 дней. Но такой план является очень
интенсивный и я не рекомендую так делать. Лучше использовать более гибкий
план – в первых день просмотр видео, составление мини конспекта, на
следующий день начать выполнять практическое задание. Я настаиваю делать
мини конспект. Что такое мини конспект? Мини конспект – это ваше
отображение полученного материала. В нем вы кратко излагаете изученную
теорию и вопросы, которые появились у Вас в процессе изучения. Далее Вы
должно найти ответы на эти вопросы самостоятельно. То есть заведите сам
тетрадку. На первой листе распишите свой план, а на последующих веди свой
конспект. План разбейте на две колонки – планируемое время и реально
пройденное. В нем отмечайте. Когда выполнили задание реально. Это нужно.
Что бы Вы могли себя корректировать и мотивировать. Конспект нового
занятия начинайте с нового листа с указанием даты и время, когда вы
приступили к изучению и выполнению практического задания.
Исходные коды
Исходные коды можно получить по адресу
[Link]
Либо сразу клонировать весь репозиторий.
git clone git@[Link]:peterarsentev/[Link]
Занятие 1. Вводная
Видео
Как я уже упомянул выше, первой главной задачей этого курса будет
составления плана. Вообще такой подход к решению применим к любой
задаче. Я, например, при создании этой книги составил подобный план, в
котором расписал что я буду делать каждый день. Выглядел он примерно так.
01.07.2015 – Первое занятие.
02.07.2015 – Второе занятие.
….
31.07.2015 – Финальная проверка книга.
То есть, я решил, что буду завершать по одному занятию каждый день. И на
это мне потребуется примерно месяц. План лучше разместить на видном месте,
чтобы напоминать себе, что вы должны сделать сегодня. Здесь еще очень
важна дисциплина. Если пропустили один день его нужно обязательно
наверстать в следующий, чтобы вернуться в прежний ритм. В случае с курсом
я советую разбить каждое занятие на два дня. Первый день – просмотрит видео,
изучение теории и первый набросок программы(задания). Второй день
Завершение программы(задания). Практиковаться в программировании надо
каждый день. Уделите хотя бы час. Лучше лишить себя сегодня просмотра
телевизора, серфинга в интернете, переписки со знакомыми или друзьями в
социальный сетях и в место этого заняться самообразованием в
программировании. Обязательно будут те, кто скажет, что у меня нет часа в
день на это дело, я лучше будут это делать в выходные по 8 часов. Во-первых,
час времени можно найти в режиме любого человека. Крайний случай, встать
раньше на час. Утренние часы, лучшее время для загрузки мозгов. Самое
сложно начать что-то изменять в себе. Из-за этого и возникают большие
проблемы. Получение новой профессии — это первостепенно изменения в
самом себе. Второй момент, при подходе «8 часов в выходные», Вы будите
забывать, что делали в предыдущие выходные. Сильно большой временной
разрыв. Кто из вас учил английский знает это на себе. Если новое слово не
повторяется каждый день, оно забывается через пару недель.
Обычно при выполнении этого задания ученики совершают ошибку. В
качестве цели выбирают процесс, а не результат. Например, Моя цель — это
изучить Java. Это процесс. У него нет конечного результат. Улучшать знания
можно бесконечно. Или научится программировать на Java. Результат этой
цели достигается после первой программы. Вы научились программировать на
Java. Что бы проверить качество вашей цели нужно выделить какой результат
вы получите от данной цели. Например, цель устройство на работу Java
программистом. Результат – зарабатываете деньги любимым делом. То есть
Вы должны свою цель подтвердить результатом. И конечно это все должно
быть ограниченно временем, сроками реализации вашей цели.
Задания
Какой Вы хотите добиться цели от программирования на Java?
Как Вы можете проверить что цель достигнута?
Какой срок реализации Вашей цели?
Занятие 2. Инструменты разработки
Видео урока
Написание этой главы я просрочил на два дня по своему плану. Поэтому
повторю еще раз. Очень важно дисциплина. Нужно запланировать не только
день, когда вы будите выполнять задание, но и время. У меня второго июля
был загруженный день, и я решил, что буду делать главу вечером. Вечером я
решил, что будут делать ближе часам к 23 00 и когда уже было за полночь я
решил, что сделаю эту задачу завтра. Вот такими поэтапными отговорками я
приступил к этой главе только через три дня.
При выполнении любой работы вам нужны инструменты, которым можно
решить данную задачу.
Список всех необходимых инструментов указан ниже. Я указал библиотеки с
версиями актуальными на текущий момент. Если версии какого-то
инструмента на данный момент нет или у вас не получается ее установить,
можете смело пробовать другую. Идеология Java – полная совместимость
версий - «Код написанный когда-то, должен работать всегда».
JDK1.7 JDK 1.7 (скачать)
Maven Maven 3.3.9 (скачать)
Tomcat Tomcat 7.0 (Скачать)
MsysGit MSysGit 2.8.1 (Скачать)
PostgreSQL PostgreSQL (Скачать)
IDEA JetBrain IDEA (Скачать)
1. JDK 1.7 (скачать) – пакет программ для компиляции и запуска исходного кода
на языка Java. В него входить JRE – виртуальная машины. Все программы на
Java запускаются в виртуальной машине. Виртуальная машина – это среда,
которая позволяет не заботиться на какой операционной системы запускается
программа. За счет этого механизма достигается кроссплатформенность.
Вам нужно скачать инсталляционный пакет для Windows OS или архив для
*nix OS.
Рисунок 2.1. Страница скачивания JDK
После установки данного пакета Вам добавить новую системную переменную.
JAVA_HOME=c:\Program Files\Java\jdk1.7.0_75\
Путь c:\Program Files\Java\jdk1.7.0_75\ может отличаться.
Для Windows OS. Свойства компьютера – Дополнительно – Переменные
среды.
Рисунок 2.2. Переменные среды для Windows OS
Далее нужно добавить путь до JDK в переменную PATH
;% JAVA_HOME %/bin;
На моем компьютере это выглядит так:
C:\ProgramData\Oracle\Java\javapath;%SystemRoot%\system32;%SystemRoot%;
%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPower
Shell\v1.0\;C:\Program Files (x86)\AMD\[Link]\Core-Static;C:\Program Files
(x86)\Skype\Phone\;%M2_HOME%\bin\;%JAVA_HOME%\bin;c:\Program
Files (x86)\Git\cmd\;c:\Tools\scala-2.11.6\bin\;c:\Tools\activator-1.3.2-
minimal\;c:\Tools\sbt\bin\;c:\Users\parsentev\bin\;c:\Tools\apache-ant-
1.9.4\bin\;c:\Tools\curl-7.40.0-rtmp-ssh2-ssl-sspi-zlib-winidn-static-bin-
w64\;C:\Program Files\OpenVPN\bin
Рисунок 2.3. Добавление JAVA_HOME/bin в PATH.
Для Unix OS используются следующие команды
export JAVA_HOME=/home/tools/jdk/
export PATH=$PATH:/home/tools/jdk/bin/
2. Maven 3.3.9 (скачать) – инструмент для автоматической компиляции и сборки
исходного кода. При написании программ вам нужно будет добавлять в проект
библиотеки, выполнять тестирование, сборку проекта. За все эти задача
отвечает инструмент автоматической сборки. Если его не использовать, то все
этапы нужно будет делать руками в командной строке. Что увеличивает время
разработки в несколько раз.
Вам нужно будет скачать архив. Создать отдельную папку c:/Tools/ и
распаковать туда архив.
Рисунок 2.4. Страница загрузки Maven.
Далее нужно создать новую переменную среды
M2_HOME=c:\Tools\apache-maven-3.2.5
Рисунок 2.5. Добавление переменной M2_HOME
Далее нужно добавить эту переменную в PATH аналогично JAVA_HOME
%M2_HOME%/bin
На моем компьютере это выглядит так:
C:\ProgramData\Oracle\Java\javapath;%SystemRoot%\system32;%SystemRoot%;
%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPower
Shell\v1.0\;C:\Program Files (x86)\AMD\[Link]\Core-Static;C:\Program Files
(x86)\Skype\Phone\;%M2_HOME%\bin\;%JAVA_HOME%\bin;c:\Program
Files (x86)\Git\cmd\;c:\Tools\scala-2.11.6\bin\;c:\Tools\activator-1.3.2-
minimal\;c:\Tools\sbt\bin\;c:\Users\parsentev\bin\;c:\Tools\apache-ant-
1.9.4\bin\;c:\Tools\curl-7.40.0-rtmp-ssh2-ssl-sspi-zlib-winidn-static-bin-
w64\;C:\Program Files\OpenVPN\bin
3. MSysGit 2.8.1 (Скачать) – Система для хранения исходных кодой.
Используется для командной разработки.
Рисунок 2.6. Загрузочная страница Git client
Ниже показан процесс установки клиента и пункты, которые необходимо
выбрать.
1. Добавляем команду git в доступные команды с командной строки.
2. Выбор терминала. Советую выбирать второй пункт.
3. Утилита для настройки безопасности. Советую выбирать первый пункт
После скачивания и установки инсталляционного пакет msi он пропишется в
папке. Возможно путь будет другой, если вы указали другие настройки,
поэтому воспользуйтесь поиском – найдите [Link].
c:\Program Files (x86)\Git\
Далее аналогично JDK и Maven нужно добавить эту переменную в Path.
c:\Program Files (x86)\Git\cmd
На моем компьютере это выглядит так:
C:\ProgramData\Oracle\Java\javapath;%SystemRoot%\system32;%SystemRoot%;
%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPower
Shell\v1.0\;C:\Program Files (x86)\AMD\[Link]\Core-Static;C:\Program Files
(x86)\Skype\Phone\;%M2_HOME%\bin\;%JAVA_HOME%\bin;c:\Program
Files (x86)\Git\cmd\;c:\Tools\scala-2.11.6\bin\;c:\Tools\activator-1.3.2-
minimal\;c:\Tools\sbt\bin\;c:\Users\parsentev\bin\;c:\Tools\apache-ant-
1.9.4\bin\;c:\Tools\curl-7.40.0-rtmp-ssh2-ssl-sspi-zlib-winidn-static-bin-
w64\;C:\Program Files\OpenVPN\bin
4. Tomcat 7.0 (Скачать) - Сервер контейнер для разработки web приложений на
языке Java.
После скачивания zip архива. Распаковывайте его в папку c:\Tools\
5. PostgreSQL (Скачать)
Подробно про установку и настройку базы данных будет в главе 19.
6. JetBrain IDEA (Скачать)
После всех шагов необходимо проверить работу каждого инструмента.
4. Проверка java. Нужно открыть командную строку, либо выполнить
команду cmd. В ней набрать команду.
java –version
5. Проверка maven. В командной строке: mvn –version
6. Git. В командной строке: git –version
7. Tomcat. Нужно в командной строке перейти в папке ../tomcat/bin/ и
выполнить команду [Link]
8. PostgreSQL. В командной строке: psql –username=postgres
Задания
Установить все пакеты на своем компьютере.
Прописать необходимые переменные окружения.
Проверить работоспособность каждого элемента из списка.
Занятие 3. Типы
Видео
Давайте перейдем к созданию вашей первой программы. Для этого нужно
создать файл [Link]. Файл создайте в отдельной папке.
C:\projects\JavaLessons\lesson_03\src\
Теперь откроем файл на редактирование в Nodepade++ или любом
аналогичный редактор и добавим туда следующий код.
public class Calculate {
public static void main(String[] arg) {
[Link]("Calculate...");
int first = [Link](arg[0]);
int second = [Link](arg[1]);
int sum = first + second;
[Link]("Sum : " + sum);
}
}
Сохраним файл и откроем командную строку в данной папке. Ниже приведен
пример из решения.
Скомпилируем наш код. Откроем командную строку в папке, где лежит файл
и выполним следующее команду. Важно: java – регистро-зависимый язык.
[Link] и [Link] – это разные файлы, классы.
javac [Link]
После выполнение этой команды рядом с файлом java создастся файл class.
Это скомпилированный класс теперь его можно запустить.
java Calculate 2 2
Файл указываем без расширения. После имени класса указываем входные
параметры.
Имя файла должно быть идентичным имени класс. Важно, имя класса и имя
файла регистр чувствительны. [Link] и public class Calculate
Это разные имена - отличие в первой букве.
public class Calculate – это запись объявляет начало класса. Класс – это базовая
конструкция в языке Java. Весь код должен находить внутри класса в
фигурных скобках. Фигурные скобки определяют начало и конец блока, где
должен находиться код. Следующей базовой конструкцией языка является
метод. Метод – это блок кода, который выполняет какие-либо операции.
В листинги 3.1 приведен метод main
public static void main(String[] arg) {
Ключевое слово public – говорит о том, что данный метод может быть
использовать в любом месте кода. Ключевое слово static – объявляет, что
данный метод принадлежит данному классу. Void – это результат
выполнения метода main. В данном случае метод ничего не возвращает.
Поэтому используется ключевое слово void. Main – это имя метода. В именах
можно использовать латинские буквы, цифры, символы подчёркивания.
В скобках указываются входные параметры данного метода. В данном случае
входным параметром является массив строк String[] args. Тело метода
должно быть заключено в фигурные скобки.
Первый оператор данного метода – это вывод информации на консоль.
[Link]
Далее идет создание двух переменные first, second. Инициируются из
массива. [Link](arg[0]) – Конвертирует строку в число.
Все арифметики операции могут быть выполнены только с численными
типами. К ним относятся short, int, long, float, double.
Отличаются эти типы по диапазону возможных значений.
В приведенном примере используется только один класс. В реальных же
проектах могут тысячи классов. Для того что бы структурировать код в
проекте в языке существует специальная конструкция – пакеты. Пакеты – это
обычные папки в файловой системе, которые регистрируются в теле класса.
Давайте добавим вашу первую программу в папку. Для этого нужно создать
под папки в корневой папке src/ru/parsentev/. В самой программе нужно
указать, что данный класс лежит в указанном пакете. Для этого в начале
файла нужно добавить строчки
package [Link];
При компиляции такого класса нужно указывать полный путь до файла.
Предварительно я создал папку out.
javac –d out src\ru\parsentev\[Link]
После компиляции класса, сгенерированный код располагается в папке
out\ru\parsentev\[Link]
При запуске такого класса нужно указывать полный путь к нему через имя
уже пакета, а не папки.
java –cp out [Link] 1 1
Теперь нужно разобраться, что такое переменная. Переменная – это область
памяти, в которой можно хранить информацию и получать ее. Каждая
переменная имеет тип и имя.
Общий шаблон создания переменной.
Java – строго типизированный язык. Значит, что любая переменная должна
иметь тип. Типов в Java всего восемь: short, int, long, double, float, boolean,
char, byte. Эти типы называются примитивными. С ними можно выполнять
арифметические операции.
Так же есть другой тип данных – Object. Данный тип данных называется
ссылочный. Все объекты наследуют класс Object по умолчанию. Ссылочные
типы данных не могут использоваться в арифметический операциях, зато в
них есть методы.
Методы – часть класса, выполняющая определенный набор операций.
Методы могут возвращать тип и принимать параметры.
Первый метод, который мы с вами познакомились был методы – main.
Данный метод является особенный – с вызова этого метода начинается
каждая программа. Важно, что все ключевые слова должны быть в
соответствии с приведенный примером, любое изменение в имени метода
или в параметрах буду восприняты виртуальной машиной, как обычный
метод и программа не будет запускаться.
Задания
Создать класс Calculate.
Добавить арифметические вычисления + - * / ^
Сделать его адаптивным для типов int, short, long, float, double.
Решение.
Добавить класс Calculate 5 методов для вычисление арифметических операций.
При вычислении степени вводится новая конструкция цикл. В следующей
главе будет подробно рассмотрена данная тема. Главное особенность данного
решения - это использование не статических методов. Это методы, которые
доступны только для объекта. Создание объекта происходит на счет
ключевого слова new.
Схема проекта.
И сразу для данного кода напишем тест.
Весь исходный код вы можете найти в прилагаемом к книге проекте.
Занятие 4. Классы. Объекты, Циклы, Условия
Видео
Как вы уже поняли главной конструкцией в языке является класс. Из класса
можно создать объекты. По сути класс — это шаблон по которому нужно
создавать объекты. Для создания объекта из класса используется ключевое
слово new.
Давайте создадим новый класс Calculator c методом сложения.
В классе появилась новая конструкция – поле
Поля в классе определяют состояния объект.
Теперь создаем объект класса Calculator и вызываем у него метод add.
Вызов метода осуществляется через точку.
Давайте более детально разберемся, что такое объект.
Объект это переменная, имеющая состояния и методы. Это простое, но очень
важное определение, из-за которого большинство в самом начале
программирования имеет проблемы. Связанно это с процедурным
мышлением. Мыслить процедурно – достаточно просто. Весь код
выполняется по линейно. Вам нужно от этого мышления избавляться, а сразу
думать объектами.
Давайте рассмотрим пример класс Point. Это класс описывает точку в
Декартовой системе координат. То есть имеет две координаты x и y.
Теперь создадим две переменные данного класса, то есть создадим два
различный объекта.
Здесь создано два объекта – first, second. Важно осознать, их состояния
различны. То есть первый объект будет содержать координаты 1 и 1, а второй
2 и 2.
Давайте теперь рассчитаем расстояние между двух точек. Здесь я не буду
приводить формулу расчёта, все ее должны знать из школьной программы.
Главный вопрос, где расположить метод вычисления?
Обычно я вижу решения своих учеников такое.
И пример его использования.
Это пример процедурного решения данной задачи. Такой код не проходит
проверку кода.
В самом начале в задаче уже есть каркас кода.
Ученик начинает изменять методы distanceTo – добавляет туда второй
параметр. Аргументирует это тем, что по-другому расстояния между точками
вычислить нельзя т.к. нет второй точки.
Если мы посмотрим на данный код с объектно-ориентированной стороны, то
у нас есть состояние текущего объекта this и данные другого объект
point(входной параметр).
Давайте поправим код в ООП стиле.
И использование метода теперь выглядит лаконично.
Так же стоит особое внимание уделить передаче параметров в методы.
Нужно запомнить, что параметры передаются в виде копий данных (call-by-
value). Это значит, что при передаче параметров мы передаем туда копию
данных. Рассмотрим три примера.
Пример 1.
1. Мы создаем переменную примитивного типа int value = 1;
2. Вызываем метод change и передаем ему копию объекта value;
3. В методе change происходить увеличение копии объекта value на 1.
4. Выходим из метода и печатаем в консоль оригинальное значение value.
Оно будет прежним не измененным.
5. Изменяем оригинальное значение на 1.
6. Печатаем на консоль – оно будет 2.
То есть мы видим, что значение копии мы изменить не можем.
Давайте рассмотрим пример с ссылочным типом.
1. Создаем объект Сlaim
2. Записываем в поле объекта имя – bug.
3. Вызываем метод processClaim и передаем копию ссылки.
4. В методе processClaim изменяем копию ссылки и присваиваем новое
значение имени.
5. Печатаем оригинальное значение – оно будет так же bug.
6. Важно понять, что мы не можем изменить саму копию ссылки.
И третий вариант, который наиболее важен для программирования в ООП
стиле.
1. Это тот же самый код, что и второй вариант, только убрана 21 строка.
2. Создаем объект Claim.
3. Присваиваем полю объекта bug.
4. Вызываем метод processClaim. Передаем в него копию ссылки объекта
claim.
5. Присваиваем новое значение name у копии ссылки.
6. Печатаем на консоль. В этом случае значение измениться.
Чем отличается второй вариант от третьего? Во втором варианты мы
изменяем саму копию ссылку. А в третьем мы не изменяем копию ссылка, а
обращаемся по этому адресу и изменяем данные по этому адресу. В этом
случае данные будет изменены.
Важно запомнить, что мы можем изменить данные по копии ссылки, но
не можем изменить саму ссылку.
Давайте теперь добавим реализацию метода деление. Как мы знаем делить на
ноль нельзя, поэтому в нашей программе мы должны учитывать этот момент.
Здесь появляется новая конструкция – условий оператор (if). Работает он по
следующему принципу. Если условие в скобках истинно выполняем блок
сразу после if, если условие ложно – выполняем блок после else.
Следовательно, мы проверяем, второй аргумент. Если он отличен от нуля. То
выполняем деление. Если нет – то выбрасываем исключение. Про
исключение будет отдельная глава.
Теперь перейдем к реализации метода возведение в степень. По сути
возведение в степень – это повторное умножение числа. Такой алгоритм
проще всего выполнить с помощью конструкции цикла. Циклы применяется
для повторного выполнение операций. В языке java существует три
конструкции циклов: for, while, do. В данном случае мы будет применять
цикл for – т. к. у нас есть заданное количество повторений. Циклы do while
работают только по условию.
/**
* Возведение в степень.
* @param first первый агрумент.
* @param second второй агрумент.
*/
public void expand(double first, int second) {
double temp = first;
for (int index=0;index!=second;++index) {
temp *= first;
}
[Link] = temp;
}
index – счетчик.
index!=second – проверка условия, когда завершить цикл.
++index – инкремент счетчика.
В фигурных скобках – блок цикла, который будет повторятся n – раз пока цикл
не завершиться по условию.
Задания
Создать класс Calculator. Это класс должен выполнять действия
сложения, вычитания, умножения, деление и получение результата.
Класс не должен содержать элементы ввода и вывода данных.
Реализовать класс ArgRunner с методом main. В методе main нужно
продемонстрировать использование объекта Calculator. Ввода данных
происходит из параметров запуска приложения. То есть из массива
String[] args
Реализовать класс InteractRunner с методом main. После запуска
программы пользователю показывается консольное меню с пунктами:
1.Сложить
2. Вычесть
3. Умножить
4. Делить
5. Произвести вычисление с полученным результатом
6. Очистить
7. Выйти из калькулятора
Пользователь может выбирать один из пунктов меню. Далее ему
предлагается ввести два числа и посчитать результат.
Если пользователь выбирал пункт «Произвести вычисление с
полученным результатом» программа запрашивает только одно число и
использует второе из предыдущего вычисления.
Программа должна учитывать корректность ввода пользователя. То есть
если пользователь ввел не существующий пункт меню, либо ввел не
корректные данные нужно по это ему сообщить и повторить операцию
заново, если это возможно.
ВАЖНО. В каждой программе может быть только один static метод
main. Все остальные методы должны быть не static.
Решение.
Создадим класс Calculator.
Класс содержит не статические методы по аналогии с кодом из задание
предыдущей главы. Добавить еще одни метод, который будет распределять
какие операции нужно выполнить в зависимости от ключа операции.
Теперь реализуем консольный калькулятор.
Теперь перейдем к созданию наиболее интересной части задания, реализация
интерактивного калькулятора.
Конструктор принимает два параметра: Calculator, IO (объект,
абстрагирующий от системы ввода-вывода) в дальнейшем будет
использоваться для тестирования.
Теперь добавим метод start() – реализующий опрос пользователя.
И метод запуска.
Занятие 5. Оформление кода
Видео
Когда я только начинал программировать, я думал, что программа должна
выполнять требуемые задачи и больше ничего не надо. Программа работает,
выдает нужный код. Все отлично. Клиент рад. В дальнейшем как оказалось,
что программирование — это не только придумать алгоритм и набрать его в
компьютере, в программе нужно было создавать архитектуру, проектировать,
учитывать возможность расширять программу, тестировать, проверять,
анализировать связанности кода и банально оформлять код в одинаковом
стиле. Одной из первых программ, которой я гордился была игра крестики
нолики написанная на QBasic в графики на спреях. Делал я ее в 10 классе.
Помню весь код у меня бы в одном большой цикл с мега условием на 200 строк.
Условие проверяло шаги. Еще тогда я столкнулся с проблемой поиска ошибок
в таком коде. Проверяя каждую строчку и перезапуская программу, можно
было потратить целый день, чтобы найти неправильное условие или
некорректные данные. Когда я пришел на свою первую работу, я ощутил эти
принципы более жестче. Проект, в который я попал, уже был написан и мне
нужно было править ошибки в логики и дописывать новый функционал.
Одной из первых задач в проекте мне поручили добавить возможность
импорта данных из системы active directory. Я быстро нашел нужный код. Это
оказался класс на 4000 строк. Среда разработки периодически задумывалась,
когда прокручивал ползунок для того чтобы найти нужный код. Мой коллега
шутил по этому поводу, чтобы я развернул монитор, чтобы по вертикале было
больше строк.
Ниже перечислены самые частые ошибки в коде, которые нужно избегать.
Возьмите за правило. Прежде чем заливать код в репозиторий, проверьте
каждый пункт из этого списка на своем коде.
1.Осмысленные названия для классов, методов, переменных.
В языке есть соглашения об оформлении кода.
Имена классов начинаться с заглавной буквы.
Имена методов и переменных со строчной.
1. Порядок объявления
2. Статические поля
3. Не статические поля
4. Конструкторы
5. Публичные статические методы
6. Приватные статические методы
7. Публичные не статические методы
8. Приватные не статические методы
В дополнении к этим пунктам хочу еще добавить несколько условий.
Имена классов и переменных должны нести осмысленное название.
Не стоит объявлять их виде одной буквы. Например, а1, а2.
Не стоит в переменных использовать типы данных, которые
хранят переменные. Например, valueInteger, managerService.
Стараться использовать одно слова для переменной.
2. Документировать код. JavaDoc
В языке существует удобный механизм документирования кода. Называется
он JavaDoc. В коде нужно избегать использовать комментарии. Так как это
свидетельство усложненной логики. Лучше, если вы более детально сделаете
описание JavaDoc.
JavaDoc – это особый комментарий в коде, который располагается перед
именем класса, метода, конструктора переменной.
Комментарий должен описывать поведение данного метода. Так же
желательно описать его работу. Это поможет Вам понять на сколько
правильно вы составили поведение данного класса, метода и переменной и
стоит ли провести рефакторинг данного кода.
В комментарии так же можно использовать теги. В дальнейшем эти теги будут
конвертироваться в html код
@param name – description – Описывает входящий параметр.
@return name – description – описывает возвращаемый тип.
@throws – описывает возможные исключения, которые может кинуть код.
@author – имя и фамилия автора данного кода
@since – дата создания
@version – версия файла.
3. Использовать this, super.
С нарушением этого принципа я столкнулся в проекте, где работали
программисты разного уровня. Были и совсем новички и ребята с мега
знаниями и титулами. После первых изменений в коде и его проверки
руководитель разработки написал мне, что везде в коде нужно использовать
this при обращении к не статическим методам. Среда разработки обязательно
покажет, где используется не статический метод, но бывает случаи, когда не
возможности произвести изменения в среде, либо ситуация, когда разработчик
захотел сделать метод статическим. Если вызов метода будет без указания
объекта, изменения пройдет не замеченными. Аналогичная ситуация с
вызовом конструкторов. Общее правило. Всегда явно вызывать дефолтный
конструктор родителя и всегда пере использовать ранее созданные
конструкторы.
Пример кода.
4. Избегать magic numbers
В коде бывает ситуация, когда нужно использовать коэффициенты или
фиксированные числа. Например, 3.14. Для таких ситуаций нужно создавать
константы и обязательно документировать. Константы должны объявляться
через static final и имен переменной должно быть написано заглавными
буквами.
5. Писать тесты на весь код.
Когда начинающий программист слышит слово тест, он думает, что это
должны делать тестеры, а он же программист и таким не занимается. Другая
плохая мысль, когда программист думает про тест, только со стороны
инструмента для поиска ошибок в коде. С начало стоит разобраться, что такое
тест и какие тесты должен делать программист и зачем.
Тест – это последовательность действий, направленных на выполнения
конкретной функции в программе, по окончанию которых получается
результат, который человек на основании своих ожиданий может проверить.
Программист должен использовать автоматические тесты. В данном курсе
используется библиотека Junit для написания тестов. Существуют два типа
тестирования автоматическое и ручное. Целью автоматического тестирования
является снижения риска возникновения ошибок в коде в уже реализованном
коде, а так проверка качества кода. Каким образом тесты проверяют качество
кода?! Если на код нельзя написать тест, значит программа написана без
возможности расширения, а значит любые изменения в требованиях заказчика
повлекут к изменению уже существующего кода и появлению новых ошибок.
Так же такой код будет горазда сложнее поддерживать.
Как правильно писать тесты. Существуют четкий и достаточно простой
принцип. Он носит названия трех А.
А – Assign – блок для создания и инициализации входящих, ожидаемых и
проверяемых данных.
A – Act – блок выполнения действий.
А – Assert – блок проверки ожидаемых данных и полученных.
Например,
6. Использовать неизменяемые объекты. Immutable.
При создании объектов лучше проектировать их таких образом, чтобы при их
работе мы не изменяли внутреннее состояние объекта.
Например,
7. Всегда возвращать объекты. Избегать возвращать null.
Стараться везде избегать использовать нулевые указатели. Если программа не
может вернуть данные, нужно ли возвращать пустой объект, либо выкидывать
исключение, чтобы выше стоящий код обработал такую ситуацию. Самый
простой пример – это обработка список. Если данных нет, то обычно
возвращают null. Что не удобно для клиента данного кода. Нужно вернуть
пустую коллекцию.
8. Использование композиции предпочтительней, чем наследование.
Наследование позволяет создавать новые классы на основании ранее
созданных. Однако большой недостаток такого механизма – это связывания
кода. При изменении родительского класса, будет изменено поведение
дочернего. Чтобы избежать такого поведение нужно использовать шаблон
проектирования декоратор. Декоратор базируется на композиции.
Пример.
Классе есть поведение от родительского класса Animal, а также добавлено
новое поведение.
9. Количество строк в методе не должно превышать 10 строк.
В начале главы я описал свой случай из жизни, когда мне нужно было искать
ошибки в классе где было 4000 строк. Такой код сложно читать, сложно
поддерживать и невозможно написать на него тесты.
Пример.
Чтобы такого избежать нужно руководствоваться принципами SOLID. Более
подробно в главе про OOD.
10. При создании интерфейса использовать в остальном коде интерфейс,
а не его реализацию.
При создании интерфейс, либо абстрактного класса нужно использовать
именно этот интерфейс и абстрактный класс, а не его реализации. При
использовании конечно реализации, код связывается и не позволяет гибко
задавать поведение.
Пример.
Интерфейс описывающий действие в системе.
Реализация интерфейса.
Использование
11. Нарушения принципов ООП - использование static методов и
переменных.
Здесь нужно запомнить одно простое правило - нигде не
используем static методы и переменные. Чем плохо использование
статическим метод и переменных. Статические методы нельзя наследовать и
нельзя перекрыть их поведение. Эти ограничения не позволяют расширять код
и тестировать его. Важно, в коде можно создать статические классы и
константы на основании комбинации static final.
Пример нарушения кода.
Правильное исполнение данного примера - убрать статик методы
и переменные.
ВАЖНО. Код с использованием статик методы и переменных, не будет
проходить проверку кода. Проверьте сразу этот пункт прежде чем переводить
задачу в выполнено или показывать код наставнику. В коде разрешается
использовать ключевое слово static в сигнатуре класса и при создании
констант static final поля.
12. Использование метода public static void main(String[] args) в качестве
логики основной программы.
Появление в программе кода в методе main является нарушением первого
пункта. Метод main не должен нести никакой логики. В методе должны быть
только этапы запуска программы.
Пример нарушения.
Проблема такого кода - нет возможности его использовать. Код просто
выполняет условие задачи с зафиксированными значениями. То есть такой код
является бесполезным. Так же из-за того, что он написан в статическом методы,
мы не может его наследовать и переопределить его поведение. И у нас нет
возможности протестировать. Нет возможности ввести новые данные или
тестовые.
Правильное исполнение кода.
13. Отсутствие возможности задать входящие данные.
Пример кода.
В данном примере нарушается несколько принципов - главный это сокрытие
информации. Клиента данного кода позволяется задать ширину массива,
но отсутствует возможность определить заполнение.
Правильное решение.
14. Не оттестированный, либо слабо проверенный код.
Часто задаваемый вопрос, как тестировать программу. В любой программе
должны быть входящие и выходящие данные. Тестировать нужно именно эти
данные. У вас есть входящие данные, есть выходящие и ожидаемые - это те
данные, которые вы ожидаете от выполнения свой программы.
Рассмотрим пример с задачей повода квадратного массива.
Для начало нужно определить входящие данные. Здесь нужно всегда начинать
тестирование с минимальных входных параметров. Для проверки этой
программы мы может использовать двух мерный массив с 4 элементами.
00
11
После выполнения программы мы получим выходные данные.
И теперь время определить ожидаемые данные. Ожидаемые данные мы
составляем сами. На основании описание программы. В нашем примере это
будет
10
10
Теперь нам осталось самое малое это сравнить полученный результат с
ожидаемым.
На всех этапах вы должны писать подобные проверки.
Вторая проблема это слабо проверенные программы. Например, задание с
вычислением площади треугольника. В условие требуется написать метод
определение существования треугольника. Большинство добавляют проверку
только по одному условию. Что точки не имеют общих координат, но это не
гарантированное условие. Поэтому нужно продумать все случаи.
15. Конструктор содержит код логики.
Общее правило для всего кода. Объект должен быть создан всегда. Нельзя
выкидывать исключения при создании объекта, нельзя возвращать Null.
Нельзя производить вычисления в коде.
Пример нарушения кода.
Правильная реализация
16. Множественное return.
Метод должен содержать только один return в конце метода. Удобно читать,
очевидный код.
Неправильная реализация.
Правильная реализация
17. Избегать использовании «одноразовых» переменных.
Так как Java использует автоматическое управлением памяти
(Garbage Collection) программисту нет необходимо заботиться самостоятельно
о переменных. Появляется возможность передавать переменные напрямую в
параметры или возвращать. Это увеличивает читаемость кода.
Пример нарушения
Правильное исполнение кода
18. Избегать сложение строк.
В Java объект String является неизменяемой переменной. При складывании
строки виртуальная машины создает копии строк. Это плохо для памяти и
быстродействие.
Нарушение.
Правильное решение – использование для вывода форматированного вывода,
либо использование специальных объектов оберток: StringBuilder,
StringBuffer(Thread safe).
StringBuffer – может складывать строки в многопоточном режиме. Если вы
явно не используете многопоточное сложение строк, предпочтительней
использовать StringBuilder.
19. Не обрабатывать исключительные ситуации.
С этой проблемой сталкиваюсь практически в каждом проекте. Причем на
поиск причины почему код не работает и в логе все чисто уходить не один
час, а иногда и не один день.
Нарушение.
В чем проблема такого кода? При неправильной работе программы у вас не
будет индикации, где происходит исключительная ситуация. Единственный
способ искать такие проблемы – это использовать пошаговую отладку. Если
проект не большой, то проблему можно найти быстро. Но на Java не пишут
маленькие проекты, поэтому запомните раз и навсегда. Никогда не
оставляйте не обработанный блок catch.
Правильное исполнение
20. Избегать использовать исключительные ситуации для выполнения
логики программы.
Механизм исключительных ситуаций используются только для индикации
некорректности работы программы. Нельзя использовать этот механизм для
реализации логики программы.
21. Избегать избыточного кода обработки исключительных ситуаций.
Другая крайность обработки исключительных ситуаций – это избыточное
легирование. Если вы используете систему логирования, нужно записывать
информацию об ошибке только в нее. Если нет системы логирования,
информацию нужно записывать в консоль. Не нужно добавлять информацию
и в консоль, и в логгер.
Нарушение.
22. Неправильное использования системы контроля версий.
Одно из самых популярных правил, которые любят нарушать. В систему
контроля версий нужно добавлять только файлы, которые вы сами создали. В
репозитории не должно быть файлов среды .iml, скомпилированных
файлов .class, папки target.
Другая проблема связанно с оставлением старого кода в виде комментария.
Когда код переписывают нужно удалять старый код, а не делать из него
комментарии.
23. Использование относительных путей.
Конфигурационные файлы, файлы данных, тестовые файлы должны
располагаться по относительному пути. Нельзя использовать абсолютный
путь.
Пример нарушения.
Правильное решение использование относительного пути. Для тестовых
данных использовать папку temp.
Либо считывать данные их resources
Задания
Проверить весь проект по пунктам из занятия.
Поправить нарушения этих правил.
Занятие 6. Тестирование. TDD.
Тестирование является одним из этапов разработки ПО. Существует не
правильный стереотип, что тестированием должны заниматься
тестировщики. Это не верно. Тестированием должен заниматься сам
программист. Все типы тестирования можно разделить на два типа:
автоматические и неавтоматические. Программист должен проверять свою
программу, как с помощью первых, так и вторых типов тестирования.
Что же такое тестирование? Тестирования – это процесс выполнения
определенного сценария, после которого будет получен результат, который в
конце должен соответствовать ожидаемому значению.
Схематично каждый тест можно представить следующей схемой.
Входные данные Ожидаемый результат
Сценарий.
Последовательное
выполнение действий.
Результат
Сравнение результат действий от сценария и ожидаемого результата.
1. Определение входных данных. Эти данные должен задавать сам
программист.
2. В зависимости от входных данных, программист должен вычислить
ожидаемое поведение или результат.
3. Составить список действий, сценарий. Он должен выполнять
определенные действия, которые тест должен проверить.
4. После выполнения сценария, в программу будет, выполнено либо
какое-то действие, изменено состояния программы, либо выведен
результат.
5. Завершающий этап. Проверка реального результата работы программы
и ожидаемого. Если они разные, значит программа работает не верно и
тест не может корректно завершится.
Что нужно тестировать? Тестировать нужно весь код, который написал
программист. Что бы написать автоматические тесты дизайн программы
должен учитывать возможность автоматического ввода данных. То есть
тестирование — это не только проверка качества кода, а также проверка
качества дизайна приложения. Запомните простое правило, если программу
нельзя протестировать значит программа написана плохо. Тоже самое
касается и фирм. Если в фирме не используют автоматическое тестирование,
значит проект будет плохой.
Самый простой способ объяснить, как написать тесты – это показать на
наглядном примере. Рассмотрим задание из второго урока.
Задания
Создать класс Calculator. Это класс должен выполнять действия
сложения, вычитания, умножения, деление и получение результата.
Класс не должен содержать элементы ввода и вывода данных.
Реализовать класс ArgRunner с методом main. В методе main нужно
продемонстрировать использование объекта Calculator. Ввода данных
происходит из параметров запуска приложения. То есть из массива
String[] args
Реализовать класс InteractRunner с методом main. После запуска
программы пользователю показывается консольное меню с пунктами:
1.Сложить
2. Вычесть
3. Умножить
4. Делить
5. Произвести вычисление с полученным результатом
6. Очистить
7. Выйти из калькулятора
Пользователь может выбирать один из пунктов меню. Далее ему
предлагается ввести два числа и посчитать результат.
Если пользователь выбирал пункт «Произвести вычисление с
полученным результатом» программа запрашивает только одно число и
использует второе из предыдущего вычисления.
Программа должна учитывать корректность ввода пользователя. То есть
если пользователь ввел не существующий пункт меню, либо ввел не
корректные данные нужно по это ему сообщить и повторить операцию
заново, если это возможно.
Рассмотри по этапам.
В первом пункте идет явное указания на поведение класса Calculator. Давайте
напишем сценарий проверки данного кода.
1. Входные данные 1 и 1 (складываем две единицы).
2. Проверяемые действия – операция сложения.
3. Сценарий. Передать в метод сложения два числа. Получить
сумму двух чисел.
4. Результат. Сумма двух чисел.
5. Ожидаемый результат – число два (2)
Давайте напишем тест.
Второй пункт задания требует написать метод main и продемонстрировать
работу класса Calculator. Тесты на такой метод писать не стоит. Так как это
по сути является теми же тестами, только написанными через метод main.
Наиболее интересное задание находиться в третьей части. В нем требуется
обеспечить интерактивную работу. У пользователя спрашивают данные и в
зависимости от введен данных производиться действия.
Давайте напишем сценарий.
1. Пользователь запускает программу и видит меню.
2. Выбирает пункт меню – сложить.
3. Вводит первое слагаемое – единицу.
4. Вводить второе слагаемое – единицу.
5. Программа выводит результат вычисления – два.
6. Пользователю предагается выйти из программы или начать новое
вычисление.
7. Пользователь выбирает выйти.
Давайте распишем входные параметры. Входными значениями здесь будут.
Ключ меню – сложить, числа для сложения. Ключ меню выйти.
Какие данные можно проверить.
1. Вывод меню. Вывод результата.
2. Состояния объекта [Link]().
Для создания такого теста нам нужно разобраться, как осуществить
автоматический ввода данных. В предыдущем примере, мы явно указывали
данные. В этом программе мы ждем ввод пользователя. Важно запомнить,
что все автоматические тесты должны быть автономными. Это значит, что
тест должен запускаться не зависимо от пользовательского ввода и других
внешних факторов. Что нам мешает написать такой тест? Самая первая
проблема – это обеспечивать автоматический ввод. Существует два
возможных решения данной задачи.
1. Использовать объекты заглушки.
2. Самостоятельно управлять потоком ввода-вывода.
Рассмотрим каждый из них.
Объект заглушка, Mock, Stub – это объект, позволяющий запрограммировать
поведение определенных методов. Например, выдавать в консоль заранее
определенные данные.
Если проанализировать ситуация с консолью(ввод-вывод), то можно
абстрагироваться от конкретных реализаций и заменить ее абстракцией.
Теперь все месте где явно используется система ввода можно заменить
абстракцией Input.
Аналогичным образом можно поступить с системой вывода.
И создадим агрегирующий интерфейс.
Теперь заменим использование этого интерфейса во всей системе.
Теперь мы жестко не привязаны к конкретной реализации системы ввода
вывода и можешь создать два вариант системы ввода вывода.
1. Реальная система.
2. Тестовая система.
Реальная система ввода-вывод должна реализовывать тот же код, что был до
ввода абстракции, то есть использование [Link] и [Link].
Этот объект реализует обычный ввод вывод с консоли. То есть пользователь
должен ввести данные.
Теперь реализуем объект заглушку. Во первых нужно ответить на вопросы,
что мы хотим получить от объекта заглушки.
1. Возможность программировать ответы, запрошенные пользователем.
2. Возможность получать выводимые данные пользователем.
Реализуем эти требования в коде.
1. Конструктор принимает массив – последовательность ответов от ввода
пользователя.
2. Данные вывода записываются в объект StringBuilder, которые
впоследствии можно будет получить в тесте и проверить их с
ожидаемыми значениями.
В нашем коде осталась одна проблема. Весь код цикла у нас происходит в
статическом метода и у нас нет возможность заменить объект ввода вывода.
Нужно перенести этот код в не статический метод и инициализацию нужных
переменных производить через конструктор.
И запуск программы так же осуществляется через main.
Теперь перейдем к написанию нашего теста по тому сценарию, который
указали выше.
Теперь мы можем проверить любое поведение в системе.
Теперь давай рассмотрим реализации через управления системой ввода
вывода. Для этого нужно обратить к АПИ класса System. В нем есть два
метода устанавливающие поток ввода и вывода. Соответственного в тестах
мы можем задать эти потоки ввода и вывода.
В тестах, соответственно, нужно уже проверять объекты out и input. Такой
подход менее предпочтительней.
Каждый программист, который начинает писать тесты, сталкивается с
основной проблемой автоматического тестирования. Что делать, если я не
могу протестировать код? Давайте рассмотрим пример такого кода.
Первое, что бросается в глаза в таком коде – это очень длинный текст. Он
просто не вместился на экран. От такого кода нужно избавляться.
Использовать StringBuilder или библиотеку Guava.
Что делает данный код? Он вывод информацию в консоль. Нет входных
параметров. Но есть выходные.
Мы можем протестировать такой код используя замену консольного потока.
Но такое решение не является правильным. Правильное решение произвести
рефакторинг кода, добиться возможности его тестировать.
1. В коде явно видно копирование частей. Вынесем одинаковые части.
2. Заменить систему ввода вывода на абстракцию.
3. Теперь напишем тесты.
Общее правильно, если мы не можем протестировать код, значит программа
составлена плохо и нужно переписывать дизайн (рефакторинг).
Теперь время рассказать про методологию TDD – Test Driven Development.
Принцип данной методологии хорошо вкладывается в простое
словосочетание – First test (Сперва тесты). Закономерный вопрос, как можно
тестировать кода, которого еще нет? Все очень просто. Прежде чем писать
любую программы, мы должны продумать API взаимодействие компонентов.
API – по сути это интерфейсы, по которым мы можем добавить или получить
данные.
Давайте рассмотрим пример с программой калькулятор.
Создадим интерфейс, описывающий действие калькулятора.
Далее создадим класс, наследующий этот интерфейс, но без реализации.
Завершающим этапом будет создание теста.
Запустим тест и получим результат.
Очевидное, что тест не прошел. Это один из важных этапов TDD.
Программист в тесте создает сценарий верного поведение, но из-за того, что
у нас нет реализации тест должен упасть. Если тест прошел, значит мы
неправильно написали сценарий.
Теперь очередь пришла реализации. Мы пишет реализацию метода и
запускаем наш текст заново. Тест должен пройти успешно.
Такой процесс должен повторяется, то тех под пока все методы не будут
реализованы.
Как я говорил выше, мы пишем сначала тесты, а потом их реализацию.
Теперь стоит сказать несколько слов про философию тестирования, зачем
нужны тесты, пишут ли тесты в больших проектах, что делать если в проекте
нет тестов, стоит ли их писать.
Во всех приведенных мной примерах тесты выглядят лаконично, но в
большинстве случаев код тестов будет на много больше, иногда количества
кода теста будет превышать количество полезного кода. Кажется, что тесты
— это излишняя трата времени и можно обойтись без них проверяя код
руками. Это справедливо только в проектах, где код пишется одним
человеком. В большинстве Java проектов будет командная разработки и вам
придется брать код соседних модулей, изменять его. И если у вас не будет
автоматических тестов на это код, вам придется каждый раз его тестировать
руками. По времени это выглядит так.
Деятельность Время
Реализация кода 8 часов
Реализация тестов 8 часов
Время с тестами увеличивается в два раза. Но давайте теперь представим, что
нам нужно добавить новое поведение без автоматических тестов.
Деятельность Время
Реализация кода 8 часов
Ручное тестирование 1 час
Модификация 1 час
Ручное тестирование 1.15 часа
Модификация 1 час
Ручное тестирование 1.5 часа
То есть на начальных этапах разработки автоматические тесты выглядят
очень затрачивающим ресурсом. Но с увеличением функциональности
возрастает время на ручное тестирование, и оно будет увеличиваться
линейно. В случае с автоматическими тестами, время не изменяется.
Что делать если в проекте нет тестов? Обычно, если проект проверяет идеи
бизнеса (startup) в нем урезают все кроме самого кода, то есть в проекте не
будет документации и тестов. Хотя хочу вас огорчить, что в большинстве
проектов, которые уже вышли из стадии прототипирования, так и не ведут
документацию и тестирования. Решение тут простое. Вы не садитесь за весь
проект и начинаете его документировать и покрывать тестами. Во-первых,
вам никто это не даст сделать, т.к. на это нет бюджета. Во-вторых, это
огромный объем работы, которые выполнить за раз невозможно. Поэтому
нужно действовать поэтапно. Когда Вам дают новую задачи вы пишите на
нее код и документацию. Против этого никто возражать не будет. В
процессе, вы будите изменять уже существующий код или использовать его в
своем новом коде, на него тоже пишите тесты и JavaDoc. Таким образом вы
постепенно сможете покрыть весь код тестами и документацией.
Так же, если код можно протестировать автоматически значит дизайн
приложение составлен верно, его можно расширять. Это простой, но очень
эффективный показатель уровня кода. Нет тестов – плохой код, есть тесты –
хороший.
Задание.
1. Составить таблицу сценария. В таблице должны быть колонки:
входные данные, описание шагов тестирования, ожидаемые данные.
2. Сами тесты написать не нужно. Только составить таблицу.
3. Проверьте покрывают ли ваши сценарии весь возможный функционал
вашего проекта.
4. Каждый сценарий должен проверять только одно поведение
программы.
Занятие 7. UML.
Первые занятие по программированию у меня в школе начались с создания
блок схемы. Самый простой и понятный способ описать программу это
составить блок-схему. Блок-схема отображает ход выполнения программы.
Блок схема позволяет простым способом описать программу и приступить к
реализации ее в коде. В дальнейшем я узнал, что блок-схема относиться к
большому разделу инженерии и имеет специальный язык UML.
Есть стереотип, что программист – это человек, который пишет программы.
На самом деле – это в корне не верно. В самом начале становление
профессии программист, программисты были бородатые дяди, которые
составляли свой код в тетрадях и вообще не пристрагивались к компьютеру.
Переносом программы в компьютер занимались операционистки. Они
вводили специально подготовленный код. То есть основная задача
программиста – это составить алгоритм программы.
Давайте рассмотрим пример задания из занятия 8.
Задания
Создать программу для обслуживания клиники домашних питомцев.
Должна быть возможно добавлять клиентов.
Указывать какой питомец есть у клиента.
Возможность искать по кличке питомца, по имени клиента.
Редактировать имя клиента, имя питомца.
Удалять клиента, питомца.
Проверка корректности введенных данных.
Первоначально сделает начать с модели данных.
Так как нам нужно работать с объектами, нам нужно создать хранилище.
Самой сложной и важной частью данного задания является разработка
действий системы. Действия системы должны связывать систему вводу
вывода и хранилище. Опишем эти действия через абстракцию.
Выше мы описали необходимые модели нашей системы. Теперь перейдем к
схеме описывающий процесс работы.
Теперь на основании этой схемы можно начать создавать программу.
Давайте теперь подведем вывод. Целью UML диаграммой является создание
схемы описывающей модели системы и их взаимодействия. UML диаграмма
позволяет создать программу объектно-ориентированной. В диаграмме у вас
получились объекты, которые отвечают только за свои действия.
Задание.
Создать UML диаграмму классов и хода выполнения для программы
интерактивного калькулятора.
Занятие 6. Наследование. Инкапсуляция. Полиморфизм.
Видео
Базовый принцип языка Java является объектно-ориентированное
программирование (ООП).
ООП состоит из базовых понятий.
- Класс
- Объект
- Метод
И основных концепций
- Инкапсуляций.
- Наследование.
- Полиморфизм.
Самый наглядный вариант объяснения чего-либо – это показать на уже
знакомоvм примере. Представьте детское ведерко. В плане ООП –
Это класс. То есть шаблон по которому будут создавать куличики. Куличики
– это объекты. Из куличиков. Мы можем построить дом, город, машины и т.д.
В программирование тоже самое – объекты служат для реализации логики.
Куличик может состоять из песка, глины, земли. Все эти состояния
описываются полями объекта. В классе Calculate – мы создавали поле int result;
Куличик может разрушится – это действие описывается методом объекта. В
классе Calculate – это все методы add, div, multi.
С помощью этих элементов, можно добиться гибкий решений. Давайте теперь
рассмотрим основные концепции ООП.
- Наследование.
Обратимся к Wiki
Наследование — механизм языка, позволяющий описать новый класс на
основе уже существующего (родительского, базового) класса или интерфейса.
Потомок может добавить собственные методы и свойства, а также
пользоваться родительскими методами и свойствами. Позволяет строить
иерархии.
Рассмотрим это на примере дома. Вначале мы построили одноэтажный дом. В
последствии когда семья увеличивалось, в доме достроили новый этаж. И так
далее. То есть на основании уже существующего класса, мы может получить
новый с расширенными возможностями.
Создадим класс Animal. Этот класс будет описывать всех животных.
В класс есть один метод, который возвращает имя животного.
Создам класс пес, на основании класса Animal.
Теперь в классе Dog доступен метод getName и появился новый метод catchCat.
Наследование в Java осуществляется с помощью ключевого слова extends.
Дословно – пес расширят животное. Такой подход позволяет уменьшить
количество кода, вторично использовать уже существующий код.
- Инкапсуляция.
Инкапсуля́ция — размещение в оболочке, изоляция, закрытие чего-либо
инородного с целью исключения влияния на окружающее. Например,
поместить радиоактивные отходы в капсулу, закрыть кожухом механизм,
убрать мешающее в ящик или шкаф.
Механизм инкапсуляции позволяет избавить код от лишнего шума. Скрыть
избыточные детали реализации. Например, в машине. Водителю доступны
только элементы управления. Все остальное скрыто под капотом.
В Java механизм инкапсуляции достигается за счет модификаторов доступа.
- public – элемент доступен всем.
- protected – доступен только в пакете и наследникам.
- default – выставляется по умолчанию, доступен только внутри пакета.
- private – доступен только внутри класса.
Добавить в класс Dog внутренний метод – isHungry – который будет
определять голодный пес или нет. Как и в реальной жизни определить
голодное животное или нет, мы можем только по внешним факторам. Это
информация от нас скрыта. Поэтому метод обозначим как private.
- Полиморфизм.
Общее прототипическое значение — возможность существования чего-либо в
различных формах.
Наиболее наглядный пример – это кулинария. Из одних их тех же продуктов,
можно сделать разнообразные блюда. Тоже самое и в программирование. Мы
делаем определение, что данный метод принимает параметры А и возвращает
данные типа В. Мы задаем общее правило для работы этого метода. Теперь мы
можем создать много вариантов реализации этого правила. И все они могут
быть использованы на счет полиморфизма.
В Java полиморфизм может быть достигнуть за счет двух механизмов.
- Перекрытия методов в наследуемых классах.
В классе Dog переопределим метод getName из родительного класс Animal.
Реализуется это следующим образом. Создаем полностью идентичный метод
из родительного класса Animal. Перед методом добавляем аннотацию -
@Override – она говорит компилятору, что метод перекрыт из родительного
класса. Если метода не существует, или вы ошибетесь в синтаксисе,
компилятор сообщим вам об этом. Внутри метода реализуем свою логику. В
данном случае, я использовал вызов родительного метода через обращение по
ключевому слову super.
-
Использовании интерфейсов.
Интерфейсы в Java – это механизм описания, что должен содержать класс,
какие методы. Без описания, как эти методы должны работать. По сути это
соглашение, что класс должен делать.
Создается интерфейс аналогично обычному классу, только вместо ключевого
слова class нужно указать interface.
Теперь нам нужно указать что классы Animal должны работать по интерфейсы
Pet. Для этого нужно после имени класса указать ключевое слово impelements
и указать какие интерфейсы реализует данный класс.
В Java есть ключевая особенность языка – нет множественного наследования.
Задания
Создать программу для обслуживания клиники домашних питомцев.
Должна быть возможно добавлять клиентов.
Указывать какой питомец есть у клиента.
Возможность искать по кличке питомца, по имени клиента.
Редактировать имя клиента, имя питомца.
Удалять клиента, питомца.
Возможность валидации.
Решение
Вначале нарисуем общую схему взаимодействия элементов в программе.
Т.к. эта программа уже более сложная, нам необходимо продумать базовые
классы до начал программирования.
1. Основной класс – Clinic – содержит методы управления клиникой. Как и в
реальной жизни клиника обеспечивает сохранение, добавление, удаление
информации по клиентам.
2. Client – класс описывает поведение клиента. Содержит информацию о
клиенте и его питомцах.
3. Validator – класс обертка для обеспечение корректного ввода от
пользователя. В качестве аргумента принимает Scanner.
4. Action – интерфейс, описывает действия программы.
5. ClinicUI – описывает пользовательский интерфейс.
Полный код всей программы клиника можно найти в папке java-way-from-
student-to-master/lessons_8
Особое внимание хотел уделить реализации поддерживаемых в программе
действий. Во всех решениях, что присылают подписчики общее решение для
такой задачи – это использование множественного условия, либо оператора
switch. Из-за такого подхода метод становится не читаемый и плохо
тестируемым.
Наиболее правильное решение – использование полиморфизма и карты
(коллекция Map). Так как в курсе еще не было темы про коллекции,
реализацию Map можно заменить на обычный массив и проходить по нему
по циклу.
В данном программе я определил интерфейс
Этот интерфейс реализует все события системы. Например – добавление
нового клиента.
В классе ClinicUI создаем карту, в который содержит все действия системы.
Таким образом, если в программе понадобиться добавить новое действие,
нам не нужно будет изменять структуру самой программы, мы просто
добавить новой тип действия и загрузим его в карту.
Теперь перейдем к выбору действия пользователя. В данном месте обычно
используют множественное условие. Но в нашем случаи, мы используем
особенности коллекции карты. В качестве ключа используем ввод от
пользовали и получаем нужное нам событие.
Проводя тестирования такого кода. Мы может реализовать тестовое событие
и проверять данные именно тестового события. Не затрагивая реальные
действия.
Занятие 7. Подключение Maven, IDEA, JUnit
Важным момент при разработке ПО является скорость разработки. Для
увеличения скорости разработки были созданы инструменты упрощающие
этапы написания кода, компиляции, сборки и тестирования. Основным
инструмент при разработке является инструменты сборки. В данном случае,
мы используем Maven. Maven – позволяет скомпилировать проект, собрать его
в нужный нам пакет, проводить тестирование, динамически подключать
нужные библиотеки, Фреймворки.
Для подключения Maven в проект нам нужно создать типовую структуру
проекта. Все проекты в Maven имеют одну фиксированную структуру. Это
позволяет быстро разобраться в новом проекте.
src/main/java Исходный код
src/main/resources Ресурсы
src/main/webapp Исходный код для web приложения
src/test/java Тесты
[Link] Заметки
[Link] Описание проекта
После создания такой структуры проекта, необходимо добавить файл [Link],
в котором будет находиться конфигурационные данные проекта.
Важный блоков в этом файле является элемент dependencies – в нем
описываются необходимые библиотеки проекта. Maven автоматически
подключить данные библиотеки к проекту.
Данный файл должен находить в корне проекта.
Вторым по значимости инструментом разработки является среда разработки.
Почему вторым, а не первым? Иногда вам будет хватать только блокнота для
редактирования исходного кода. А вот собрать проект руками без
инструментов сборки будет проблематично.
В данном курсе я использую JetBrain IDEA. Если у Вас уже имеется опыт с
другой средой разработки, можете смело использовать вам уже знакомый
инструмент. Больших отличий в средах разработки для Java нет. В
большинстве они реализуют одни и те же функции.
Для открытия вашего проекта в среде разработки нам нужно.
1. Запустить среду.
2. Открыть File – Import project …
3. Выбрать корень проекта, где располагается [Link]
4. Выбрать тип проекта Maven.
5. После того как среда сделай индексацию проекта с правой стороны
появится закладка Maven.
Давайте теперь воспользуемся зависимостью Junit. Это библиотека
используется для тестирования приложение по типу черного ящика.
Тестирования черный ящиком подразумевает вид тестирования, когда мы
оцениваем результат работы программы по входным параметра.
В псевдокоде это будет выглядеть - Calculate(2 + 2) expect 4
Рассмотрим тест калькулятора.
Создаем новый файл в папке src/test/java/
На закладке maven – test – двойной клик запустить выполнение тестирования.
Особое внимание хотел уделить возможности Maven разделять код на
модули. В данном курсе каждый новое занятие должно быть в отдельном
модуле. Модули можно удобно подключать между собой. Это позволяет
избежать дублирования кода.
Для того что бы создать модульный проект нужно в корне основного проекта
создать файл [Link]. В разделе packaging указать pom. Это говорит о том,
что проект будет состоять из отдельных модулей.
После заголовка нужно добавить раздел modules. Этот раздел должен
содержать ваши модули. Модули – это подпапки в корневые папки,
содержащие свой [Link] и структуру, описанную в начале главы. То есть
корневой каталог должен содержать [Link] и папки. А модули должны
содержать [Link] и структуру src.
Ниже показан пример модуля урока 28.
Здесь хорошо видно, что в корневые папки есть [Link] и в папки модуля
есть свой отдельный [Link].
Теперь поговорим, что должно быть в [Link] для модуля.
Первоначально, в каждом модуле будут использоваться одинаковые
зависимости, их нужно перенести в корневой [Link]. Для того, чтобы
использовать зависимости из коневого проекта нужно указать следующую
настройку.
После этого все зависимости из корневого проекта будут доступны в модуле.
Делается это для сокрушения кода.
Теперь рассмотрим пример, когда нам нужно использовать код одно модуля
другим. Например, код из третьего урока использовать в четверном.
[Link] третьего модуля выглядит так.
Для того, чтобы подключить данный модуль к четвёртому модулю нужно в
[Link] четвёртого модуля добавить зависимость.
Нужно по максимуму избежать копирования кода. Используйте подключение
модулей.
Теперь давайте познакомимся с удобными инструментами, позволяющие
искать плохой код.
Раздел analyze.
Поиск дублирования кода – Locate Duplicates. В меню Analyze выбирать
пункт Locate Duplicates. Среда автоматически анализирует весь код проекта и
находить одинаковые куски кода, которые следуют переписать. Ниже
показан пример.
Поиск циклических зависимостей – Analyze Dependencies Matrix. Если в коде
есть циклические значимости значит вы не можете заменить реализацию
классов. От циклических зависимостей нужно избавляться. Циклический код
– жестко связанный код с проблемами в расширение и тестировании.
На данном рисунке видно красные квадраты – это и есть циклические
зависимости. Нужно постараться, чтобы весь код находился в нижнем левой
углу диагонали.
Задания
Добавить maven для проекта Клиника. Калькулятор.
Добавить тесты в эти проекты.
Решение
См. проект.
Занятие 8. Исключительные ситуации. Exception, Error
В момент написание данной книги, я активно изучал Scala. И мне очень
понравился подход, принятый для решение исключительный ситуаций в этом
языке.
Вначале следует разобраться, что же такое исключённые ситуации и почему
они могут возникнуть в программе.
Обратимся к Wiki
Обрабо́тка исключи́тельных ситуа́ций (англ. exception handling) —
механизм языков программирования, предназначенный для описания реакции
программы на ошибки времени выполнения и другие возможные проблемы
(исключения), которые могут возникнуть при выполнении программы и
приводят к невозможности (бессмысленности) дальнейшей отработки
программой её базового алгоритма. В русском языке также применяется более
короткая форма термина: «обработка исключений».
Особое внимание хочу уделить слову – бессмысленности дальнейшей
отработки программы.
В этих словах заключен смысл всего механизма. Но в большинстве случаев
исключительные ситуации используют не по назначению. Это усложняет
сопровождение кода, его вторичное использование.
В Java механизм исключительных ситуаций реализуется с помощью
интерфейса Throwble. И разделяется на три основные группы- Error. Unhandle
exception, Handle Exception.
Рассмотрим каждую из них.
Error – проблемы связанные с виртуальной машиной. Например, Загрузка в
память дубликат класса (библиотеки). В такой ситуации виртуальная машина
прекращает свою работу.
Unhandle exception – Ситуации связанные с внешними факторами. Например,
оборвалось соединение с базой данных, Нарушение арифметический операций
(деление на ноль).
Handle exception – Ситуации нарушение логики программы. Например,
Отсутствие данных, не корректные данные. Пользователь пытается получить
доступ к информации, которой уже нет.
Error. Unhandle – исключительные ситуация, которые не обрабатываются.
Потому что в этом нет смысла.
Handle – обрабатываются с помощью двух конструкций.
В данном примере обработка ввода данных будет выполняться до тех пор, пока
пользователь не введет корректные данные. Если он ввел не корректные
данные, выполняется блок catch и цикл выполняется заново.
Другой способ работы с исключительными ситуациями – это передача
обработки исключения на код клиента. Давайте рассмотрим пример.
Создадим новый тип исключительной ситуации. UserException.
И используем его в новом методе вычисляющим проценты.
В данном случаи, мы передаем всю обработку ошибкой на код клиента (тот,
кто использует данный класс). Теперь клиенту нужно будет добавлять
обработку этой ситуации или передавать ее выше стоящему коду.
Данная сигнатура говорит, о том, что при выполнении данного метода может
возникнуть исключительная ситуация UserException и ее следует обработать.
В сигнатуру через запятую можно указать все исключения, которые могут
возникнуть в данном методе.
В данном примере идет проверка, что процент должен быть больше нуля, в
противном случае нам нужно выкинуть исключения. Для этого используется
следующее ключевое слово и конструкция.
Используется ключевое слово throw, а дальше указывается объект исключения.
В данном примере мы сразу создаем новый объект и его передаем в
конструкция throw.
То есть в большинстве случаев все ситуации связаны с невозможностью
получить какие-либо данные и нужно возвращать либо null, либо использовать
исключительную ситуацию.
Я предлагаю воспользоваться другим походом, который я позаимствовал в
Scala. Забавный получился момент с этой конструкцией. Книгу я начал писать
в 2015 году, а завершаю ее в 2016. На тот момент я не особо уделял внимание
новый введениям в Java а был полностью поглощён изучением Scala. Как
оказалась идея с Option понравилась не только мне, но и создателям Java. В
Java 8 добавили новый класс [Link], который в точности реализует
идею, которую создана в Scala и которая описана ниже. Я не стал удалять этот
текст и код, т.к. она раскрывает проблему передачу null ссылочный объектов
и показывает одно из возможных решений.
Это решение использовать универсальную обертку класс.
Давайте посмотрим, как можно переписать метод percent c использованием
этого класса.
и сравним код тестов.
При использовании метода, который может выкинуть исключительную
ситуацию. Нам нужно обрабатывать ее за счет конструкции try, что ухудшает
читаемость кода и его гибкость.
Так же нужно отметить, что теперь при написании тестов нужно проверять
поведение, которое выбрасывает исключительные ситуации тоже.
Давайте рассмотрим пример с методов вычисляющим проценты.
Первый случай проверяет корректное выполнение метод. Давайте теперь
напишем тест, который будет проверять выбрасывание исключение.
Главное изменения – в аннотацию @Test добавлено свойство expected. Теперь
если метод не выкинет исключение тест не выполниться успешно.
Задания
Заменить сообщения о нарушении логики в приложении на
перебрасывания исключительных ситуаций.
Добавить тесты, которые проверяют эти исключения.
Занятие 9. Создание исполняемого файла. Manifest
После первой написанной мной программы я стал искать, как можно ее
показать друзьям. Чтобы они попробовали ее в деле. Первое мое решение было
сделать ее исполняемой. Двойной клик и программа запушена. После
некотороо времени исследования. Я понял, что большого смысла в этом нет.
Для запуска программы на Java нужна виртуальная машина. Что бы создать
имполняемый файл все равно нужно добавлять целую пачку библиотек для
запуска. Поэтому от идеи создания запускаемого файла я отказался. Другое
решение это сделать обычный jar (пакет скомпилированных файлов) и указать
в нем какой файл нужно запускать, когда пользователь двойным кликом
пытается открыть этот файл. По сути это будет тот же самый исполняемый
файл только с расширением jar.
Как вы уже поняли основным элементов в программах в java являются
скопилированные файлы. Для более удобной организации таких файлов
используются пакеты Java ARchive (JAR). Создать пакет jar можно через
командную строку с помошью утилит поставляемых в JDK.
Но такой подход требудет много писаны руками, что уменьшает скорость,
увеличивает количество ошибок.
В предыдущих занятиях мы уже подключили инструмент сборки. Давайте
решим эту задачу с помошью данного инструмента.
Во первых. Нам нужно указать какой пакет нужно собирать после компиляции.
Это указывается в файле [Link] в голове файла.
<groupId>JavaWayFromStudentToMaster</groupId>
<artifactId>Lessons</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
Варинты укаповки могут быть jar(библиотеки), war(веб приложение), ear
(enterprise app).
В следующих главах мы будет использовать war сборку.
При выполнении команды
mvn clean install
Будет выполнена очистка проекта, компиляция, тесты. Сборка.
Выходной файл target/(artifactId)-(version).jar.
Этим файлом можно пользоваться, в качестве библиотеки. Теперь давайте
сделаем его запускаемым. Для этого нужно в [Link] добавить новый плагин.
И в нем указать какой файл является точкой входа в программу.
<plugin>
<groupId>[Link]</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>lesson_6.ClinicRunner</mainClass>
<addClasspath>true</addClasspath>
</manifest>
</archive>
</configuration>
</plugin>
Заново пересоберем наш проект и запустим jar.
java -jar [Link]
Welcome to clinic
0 - show clients
1 - create client
2 - create pet
3 - update client
4 - update pet
5 - delete client
6 - delete pet
7 - search client
8 - search pet
Enter operation :
Задания
Сделать программу клиники исполняемой.
Добавить файл [Link] и описать в нем значение программы,
возможности.
Попросить своих друзей попробовать использовать программу.
Получить впечатление, отзывы от друзей. Провести работу по
исправлению.
Решение
См. файл проекта.
Занятие 10. Коллекции. List, Set, Map, Tree.
Основная цель всех программа – это манипуляция с данными. Для хранения
однотипный данных используются массивы. Массивы в Java имеют
фиксированную длину. В реальной ситуации нам часто заранее неизвестна
длина массива. Поэтому в Java добавили специальный пакет, в котором
собраны большинство структур для хранения данных.
Рассмотрим иерархию классов коллекций.
Из рисунка видно, что в коллекциях есть особый тип – карты. Карты это
множества, где в качестве основного элемента используется ключ, у которого
есть значение. Другое название карт – это ассоциативные таблицы. То есть
ключ ассоциируется со с значением.
В коллекциях повсеместно используются генерики. Генерики – это
обобщеннный тип данных, который задает общие правила для данных
хранимых в коллекциях. Данных механизм используется только для
статического анализа. В процессе компиляции все конструкции с генериками
убираются.
Как вы уже поняли, генерики задаются угловыми скобками и имеют
обозначение в виде заглавной буквы.
Рассмотрим наглядный пример.
Создадим коллекцию типа ArrayList и не обозначим хранимый тип.
List list = new ArrayList();
Теперь, когда мы будет использовать этот список в коде нам нужно в
документации узнать какой тип хранится в этой коллекции. В дальнейшем
этот код может вызвать ошибки приведения типов.
С генериками мы можем изначать задать тип хранимых данных.
List<Pet> list = new ArrayList<Pet>();
Теперь наша коллекция ограничена манипуляциями только с типом Pet.
Основные интерфейсы коллекций
List – Список. Может содержать дублирующие элементы. Сохраняет порядок
элементов. Элементы ассоцируются с индексом.
Set – Список. Не может содержать дубликаты. Элементы проверяются по
hashCode, equals. Упорядочивает значение по ключу hashCode. Нет
возможности получить элемент по индексу.
Queue – Очереди. Может содержать дубли. Использует механизм очередей
LIFO, FIFO для получения и добавление элементов.
Map – Ассоциативная таблица или карта. Использует ключ-значение в
качестве элемента. Доступ к значению осуществляется через ключ.
Существуют реализации интерфейса Map с упорядоченными и не
упорядоченными ключами.
Обобщающих интерфейс для коллекций является интерфейс Collection.
Он содержит базовые методы для работы с коллекциями.
- Добавление.
- Удаление.
- Размер.
А также очень удобный механизм манипуляции с коллекцией через итератор.
Итератор – это шаблон проектирования, которые используется для работы с
множствами. Основное преимужество данного механизма, он неиспользует
дополнительные структры для проходжения по множеству. За счет этого
обеспечивается максимальная гибкость и быстродейтсвия.
Базовый механизм данного шаблона описываем простой интерфейс.
public interface Iterator<E> {
boolean hasNext();
E next();
Метод next – должен возвращать следующий элемент в коллекции.
То есть если у вас есть коллекция [1, 2, 3]. При троекратном вызове метода
next – он должен вернуть 3.
Метод hasNext – проверяет существования следующего элемента. Важный
момент вторичный вызов метода не должен изменять положение указателя. То
есть hasNext = true, при повторных вызовах должен всегда возвращать true.
Изменения указателя вызывает метод next.
На одном собеседовании у меня была подобная задача. И этот момент я
упустил из виду. В документации четко сказано, что метод hasNext не должен
влиять на положение указателя в итераторе.
Для практике можете выполнить задание самостоятельно. Нужно реализовать
метод
Iterator<T> convert(Iterator<Iterator<T>>)
Входной параметр — это список списков, а на выходе надо сделать один
список.
Механизм итераторов используется в конструкции циклов for-each
List<Pet> pet = new ArrayList<Pet>();
Для прохода по коллекции можно использовать счетчик по индексу.
for (int index=0;index!=[Link]();++index)
Такая конструкция может содержать багу в интексе, что приведет к
обращению к несуществующему элементы и выбораса RuntimeException.
Вместо этой конструкции можно использвать for-each
for (Pet pet : pets)
Перейдем к рассмотрению основных реализаций интерфейсов коллеций.
Важно отметить, что на собеседовании вопросы относительно коллекций
являются основными. Поэтому советую очень тщательно разобраться в этом
вопросе.
ArrayList – Реализует интерфейс List – внутри использует массив.
LinkedList – Реализует интейфейс List – внутри использует механизм связных
списков. Получение элементов только через итератор. Главное отличие от
ArrayList, быстрое вставление новых элементов внутрь коллекции. Если в
ArrayList нужно сдвигать элементы на одну позицию, то в LinkedList
происходить изменение соседних ссылок. Недостатком данной коллекции
является объект занимаемой памяти. Каждый элемент должен хранить ссылку
на заголовок, конец коллекции. Ссылку на предыдущей элемент и
последующих.
Set
TreeSet – забируется на деревьях.
HashSet – базируется на карте.
Map
HashMap – базируется на hash таблицах.
LinkedHashMap – аналогична HashMap, но не изменяет порядок добавления
элементов.
//TODO дописать примеры использования.
Задания
1. Создать свою реализацию коллекций ArrayList - на базе массива.
2. Создать свою реализацию LinkedList - на базе связанных списков.
3. Заменить массивы в проекте клиника на вашу реализацию.
Решение.
Данное задание составлено с целью разобраться, как работают разные
структуры данных. Код решения должен быть очевидным, если вы
разобрались с самой структурой.
Метод добавления должен проверять размер массив и в случае его
завершения сделать пересоздания массива в два раза.
Метод получения данных работает по индексу, который уже реализован в
самом массиве.
В связанном списке основная идея перелинковки элементов при добавлении.
Сейчас я вижу, что метод можно переписать проще. Сделать его лаконичней.
И итератор.
Для замены массивов в проекте клиники я использовал интерфейс.
И теперь в коде можно заменить реализация Map на наше хранилище.
Занятие 13. Коллекции. Equals. hashCode
При работе с данными важным фактором является скорость обработки.
Поэтому очень важно понимать, какие механизмы заложены в основе каждой
коллекции и как с ними нужно правильно работать, чтобы обеспечить
максимальное быстродействие.
В Java все объекты неявно наследуются от базового класс Object.
Этот класс имеет очень важные методы. На базе этих методов строится
множество алгоритмов обработки данных.
Первый метод это
Этот метод сравнивает два объекта, если в ответ приходит истина, значит
текущий объект равен проверяемому.
Посмотрим его реализацию. Как мы видим код метода элементарный. Два
объекта сравниваются через оператор == , который обеспечивает сравнивание
двух ссылок. Поэтому всегда переопределяйте метод equals в ново созданном
классе.
Другой важный метод – это hashCode.
Метод имеет сигнатуру native, что говорит, о том, что данный метод
реализован на уровне виртуальной машины.
По умолчанию этот метод генерирует уникальный ключ для созданного
объекта.
Ниже описаны правила переопределения этих методов.
Давайте рассмотрим класс User
Переопределим методы equals.
В методы мы поэтапно проверяем равенство всех полей, отвечающих на
бизнес логику.
Существует 5 правил проверки корректности переопределения метода equals
1. It is reflexive: for any non-null reference value x, [Link](x) should
return true.
2. It is symmetric: for any non-null reference
values x and y, [Link](y) should return true if and only
if [Link](x) returns true.
3. It is transitive: for any non-null reference values x, y, and z,
if [Link](y) returns true and [Link](z) returns true,
then [Link](z) should return true.
4. It is consistent: for any non-null reference values x and y, multiple
invocations of [Link](y) consistently return true or consistently
return false, provided no information used in equals comparisons on the
objects is modified.
5. For any non-null reference value x, [Link](null) should return false.
Давайте напишем тест, проверяющий данные правила.
Такое соглашение есть и для методы hasCode
Whenever it is invoked on the same object more than once during an
execution of a Java application, the hashCode method must consistently
return the same integer, provided no information used in equals comparisons
on the object is modified. This integer need not remain consistent from one
execution of an application to another execution of the same application.
If two objects are equal according to the equals(Object) method, then calling
the hashCode method on each of the two objects must produce the same
integer result.
It is not required that if two objects are unequal according to
the equals([Link]) method, then calling the hashCode method on
each of the two objects must produce distinct integer results. However, the
programmer should be aware that producing distinct integer results for
unequal objects may improve the performance of hash tables.
Задания
Создать класс пользователь.
Провести эксперимент с коллекциями ArrayList, HashSet, HashMap
Перекрыть equals, hashCode.
Перекрыть только equals
Перекрыть только hashCode
Не перекрывать
Объяснить результаты работы коллекций. Методов contains, add, size,
put
Решение.
Создадим тест и в нем продемонстрируем поведение работы методы
[Link] и size
И тесты
Занятие 12. Multithreading.
Видео
С начало нужно разобраться, что такое многопоточность и для чего она нужна.
Многопоточность – это способность выполнять операции внутри одной
программы параллельно. Задачи всей многопоточность можно разделить на
две группы. Задачи, которые не связаны с выполнением друг друга. Например,
проверка орфографии в тексте. Два процесса не связаны друг с другом. Мы
можем вводить текст. Это одна нить. А другая нить будет проверять этот текст.
И вторая группа задач – это процесс увеличение скорости работы алгоритма
программы за счет выполнения отдельных ее частей одновременно. Например,
поиск данных в упорядоченных структурах данных или вычисления сложных
арифметических формул.
Теперь нужно разобраться в терминологии. Что такое нить? Нить – это блок
операций, который выполняется независимо от хода выполнения основной
программы, то есть параллельно. Если рассматривать архитектуру процессора,
то процессор может состоять из нескольких процессоров. Каждый процессор
может запускать одновременно только выполнение одного процесса. А вот
процесс уже может запускать бесконечное число нитей.
Уметь программировать в многопоточном режиме очень важный и
востребованный навык. Сейчас архитектура процессоров не позволяет
увеличивать частоту процессора, можно только добавить больше процессоров.
Поэтому очень важно научиться распределять выполнение задач на отдельные
процессоры.
Одной из самых сложных тем в программировании является многопоточное
программирование. Основная проблема такого программирования
заключается в том, что программа работает не детерминировано. То есть
выполнение операций идет не последовательно, а параллельно. Давайте
рассмотрим простой пример инкремент int в параллельном режиме.
В случае, если у вас нет общих ресурсов между нитями, проблем нет. По сути
каждая нить (Thread) – это отдельная программа. Проблема возникает, когда
между нитями есть общий ресурс. В примере выше, в нитях есть общий ресурс
это переменная i. Как вы думаете какое значение будет на выходе таких
операций? Оно может быть любым из возможных: 1 или 2. Я думаю вы
удивлены откуда там может появиться один? Давайте разберемся.
Выполнение операция в самом thread детерминировано. То есть каждая
операция будет выполнена последовательно, но операции между тредами не
детерминированы. В данном примере может произойти ситуация, когда первая
нить прочитает переменную i, до того, как вторая них обновит переменую i.
Такая ситуация называется race condition (состояние гонки), другой вариант
проблемы, когда первый тред записал данные в кеш процессора, а второй тред
считал данные из памяти или напрямую из регистра и обновленные данные не
увидел. Такая ситуация называется visibility of share object (видимость
переменных).
Давайте теперь более детальней рассмотрим эти проблемы и как они решаются
в Java.
Все данные в Java храняться в специальной области памяти, называемой Heap.
Когда в программе создается нить все ее локальные переменные храняться в
отдельном пространстве памяти, называемом Thread Stack. Этот раздел памяти
является кешом для треда. Все запиши в этот кеш для треда являются
детерминированными. То есть обращением внутри треда идет
последовательно. Два треда не могут видеть кеши соседних тредов. На уровне
памяти компьютера данные могут записаться, в различную область пямяти:
RAM, CPU Cache, CPU Registers. Проблемы возникают, когда две нити имеют
общую ссылку на переменную. При обновлении данных или считывании нить,
без специальных инструкций, нить не может гарантировать, что считает
данные из правильной области памяти. И выполнение программы может быть
аналогичным, как описано выше с инкрементом целочисленной переменной.
Давайте перейдем к практике.
Первым элементом необходимым для многопоточность программирования
является создание нити. Для того что бы создать нить. Нужно, либо создать
новый класс и реализовать интерфейс Runnable или унаследовать класс Thread
и переопределить метод run.
Для того, что бы запустить выполнения нити нужно создать объект и
выполнить метод start(). Важно, метод run – это блок операций, которые будут
выполняться параллельно. Метод start – используется для запуска нити. Если
вы вызовите напрямую метод run, то нить не будет работать в параллельном
режиме.
Давайте реализуем пример с инкрементом int.
Создадим класс Count.
Далее создадим нить, которая будет инкрементировать счетчик.
И запустим данных код.
В данном случае мы можем увидеть на консоли
12
11
Это может произойти из-за того, что мы не использовали специальные
инструкции. Самое просто решение – это разрешить работать с переменной
одновременно только одной ните. Для этого нужно создать критическую
секцию. Эта секция, в которую может зайти только одна нить. Все остальные
нити будут ждать пока нить, которая заняла работу выполнит свою работу и
выйдет из критической секции. Это создании секции нужно использовать
ключевое слово synchronized.
Блок начинается с фигурной скобки и ей же заканчивается. Параметр this – это
объект монитор. По нему виртуальная машина определяет занят ли тред или
нет. У данной записи есть упрощенная форма.
Отличие эти двух записей в том, что в первой записи мы явно не указываем на
объект монитора, а во второй указываем явно. В качестве объекта монитора
может быть выбрал любой объект. В случае, если метод является статический,
то объектом монитор является класс. Блокирование всего класса делать
настоятельно не рекомендуется.
Так же стоит сказать несколько слов про методы самой нити и класс Object.
Thread#interrupt – Устанавливает флаг, о том что нить должна быть
остановлена. Программисту необходимо реализовывать собственную логику
остановки программы.
Thread#isInterrupted – Проверяет флаг остановки нити. Используется для
реализации собственного механизма остановки нити. Если механизма не будет
реализовано, то нить будет продолжать свою работу.
Thread#interrupted – Проверяет флаг, но после очищает значение флага.
Thread#join – Принуждает родительскую нить дождаться выполнения
дочернего.
Thread#yelp – Сообщает процессору, что желательно выполнять задачи
именно этой нить в первую очередь. Может быть проигнорирован
процессором.
Object#wait – Переключает нить в режим ожидания. Особенность этого
состояния в том, что все объекты монитора, тоже освобождаются, в отличии
от метода Thread#sleep – объекты монитора остаются заблокированы.
Object#notify – Переключает нить в режим работы.
Object#notifyAll – Переключает все нити из режима ожидания в режим работы.
Задания
Реализовать эмуляцию клиентов в проекте клиника для домашних
питомцев.
Сейчас в вашем приложении должны быть автоматически тесты,
которые проверяют работу. Нужно создать общий объект Клинику и
обеспечить одновременную работу нескольких пользователей. Каждый
пользователь это отдельный Thread. Он должен автоматически
выполнить различные работы. Например, создать нового клиента,
отредактировать и удалить его. Другой пользователь должен только
просмотреть и отредактировать клиента.
Решение
Первое, что необходимо сделать – это определить, какой объект является
общим для пользователей. В случае с клиникой общим ресурсом является
объект класса Clinic. Так как в нем хранится объекты клиентов и питомцев.
Самый простой способ обеспечить ThreadSafe работу данного класса это
обернуть все методы ключевым словом synchronized.
Следующим этапом нужно создать новый Thread – который будет эмулировать
поведение пользователя.
В качестве параметра конструктора передаем объект, управляющий
пользовательским интерфейсом.
Далее нам нужно сделать рефакторинг кода, так как мы не можем указать
входящие параметры.
И нужно использовать потокобезопастную коллекцию для хранения действий.
И после этого мы можем создать класс для запуска пользователь и
демонстрации работы приложения в многопользовательском режиме
Занятие 13. Клиент-сервер. Протоколы передачи.
Видео
Практически все приложения в Java будут по строены на клиент-серверной
архитектуре. Ниже изображена общая схема.
В данном курсе будет рассмотрена клиент серверная архитектура на
основании web приложения. Главной частью такой архитектуры является
протокол взаимодействия клиент и сервера. Для веба приложений
протоколом служит HTTP.
Обратимся к Wiki.
HTTP (англ. HyperText Transfer Protocol — «протокол передачи гипертекста»)
— протокол прикладного уровня передачи данных (изначально — в виде
гипертекстовых документов в формате HTML, в настоящий момент
используется для передачи произвольных данных). Основой HTTP является
технология «клиент-сервер», то есть предполагается существование
потребителей (клиентов), которые инициируют соединение и посылают
запрос, и поставщиков (серверов), которые ожидают соединения для
получения запроса, производят необходимые действия и возвращают обратно
сообщение с результатом.
Важной деталью данного определения является то что всю инициализацию
событий создает клиент, а сервер служит, только источником данных.
Для создания серверного приложения нам нужен сам сервер, который мы
будет расширять. Для этого в курсе используется tomcat.
Давайте запустим его и перейдем на страницу [Link]
Команда
В хроме нужно открыть панель разработчика – Ctrl + Shift + J
Открыть закладку Network. Важно, у меня сервер настроен на адрес 8082, у
вас он будет 8080.
При запросе [Link] браузер формирует GET запрос.
Всего в HTTP есть 8 типов запросов GET, POST, PUT, DELETE, HEAD,
TRACE, CONNECT, OPTIONS.
Каждый запрос служит для своих задач.
Все запросы имеют похожую структуру данных.
Заголовок и тело.
В заголовке указывается информация об отправителе, кодировка, метод и
адрес.
В теле письма указываются параметры.
На каждый запрос клиента приходит ответ. Ответ выглядит аналогичным
образом: заголовок и тело.
В данном случае на наш запрос приходит html текст, который преобразуется
браузером форматированный вид.
Все параметры передаются в текстовом виде по шаблону ключ-значение.
Ниже кратко описаны названия каждого методы http протокола
GET Для получения ресурсов. Параметры передаются в заголовке.
POST Для добавления данных. Параметры передаются в теле.
PUT Для обновления данных. Параметры передаются в теле
DELETE Для удаления данных.
HEAD Аналогичен GET, но не содержит тела ответа.
TRACE Информация для проверки качества соединения
CONNECT Проверяет доступность ресурса
OPTIONS Возвращает список поддерживаемых протоколов.
Теперь перейдем к созданию своих обработчиков запросов. Для этого нужно
добавить зависимость servlet-api.
Далее нужно создать структуру каталогов для веб приложения.
Следующим этапом будет создания дескриптора приложения. Дескриптор
приложения – это по сути сердце вашего приложения. В нем происходит
распределение какие запрос должны обрабатывать какой сервлет. Сервлет –
это класс обработчик запросов.
Давайте создадим тестовый файл [Link]. И заполнил следующим кодом.
Корневой тест должен быть web-app. Его можно оставить без атрибутов.
Сервер автоматически будет использовать схемы, которые он поддерживает.
Далее мы указываем diplay-name – он содержит имя вашего приложения. Оно
нужно только для сервера.
И после всего идет описание вашего обработчика. Оно состоит из двух
частей: Описание класса(servlet) и адрес, по которому будет доступен этот
сервлет (servlet-mapping).
Теперь очередь создать наш класс [Link].
Класс должен расширять класс HttpServlet.
Как вы видите пока наш класс не содержит логику. Для того, чтобы добавить
логику нужно перекрыть методы doGet doPost doPut doDelete. Каждый метод
отвечает на обработку своего метода http протокола.
Я добавил логику только в метод doGet, остальные методы вызывает
передает управление ему же.
Теперь нужно сконфигурировать среду.
Заходим в File – Project Structure – Facets.
Выбираем Web и в диалоге выбираем наш модуль.
Должно появится следующее окно.
Далее переходим в раздел Artifacts.
И создаем новый артефакт на основании созданного модуля.
Далее заходим в настройки сервера. Run – Edit conf.
Переходим на закладку Deployment.
И указываем наш артефакт. Сохраняем и теперь можно его запустить.
Теперь перейдем на страницу и получим ответ от нашего обработчика.
[Link]
Давайте теперь передадим параметры нашему обработчики и попросим их
вывести на странице. Для получение данных в обработчике используется
объект HttpRequestServlet.
Перезапустим сервер и сделаем новый запрос, но уже с указанием параметры
[Link]
Как вы видите, теперь страница изменилось. То есть теперь у нас есть
возможность передавать параметры на сервер.
Во всех примерах выше, мы использовали GET запрос. Для проверки
поведения других запросов в среде есть удобная утилита.
Используя этот инструмент, вам нужно будет выполнить задания.
Задания
Расширить модель User.
Сделать возможность поиска.
Решение.
Первоначально нужно набрать код html страницы и вставить ее в наш
сервлет. Так как страница по сути будет шаблоном, что бы не складывать
строки будет использовать библиотеку Guava и класс Join.
И добавим шаблон в сервлет.
Расширим модель.
В методе doPost добавить код добавления и редактирования пользователя.
Построение таблицы и формы вынесено в отдельные методы
Теперь можно запустить проект.
Занятие 16. JSP, Servlet, JTLS
Видео
В предыдущем уроке вы научились создать обработчики запросов, научились
возвращать html ответ и принимать параметры из запроса. Как вы заметили
писать вид, html код, прямо в обработчике не удобно. Для создания вида
существует другая технология – JSP(Java server page). Давайте создадим файл
– [Link]
И наполним его текстом.
Как бы видите это обычная html страница. Основное отличие в заголовке мы
прописываем тип языка для обработки данной страницы.
Теперь если мы запустим сервер и сделаем запрос
[Link] - мы можем увидеть следующее.
Давайте создадим сервис, где будут содержаться пользователи.
Давайте теперь перейдем к созданию вида.
Первое, что нужно добавить – это форму для добавления нового
пользователя.
Добавить код логики в обработчик.
Теперь давайте сделаем вывод информации на вид.
И загрузим страницу
При создании вида используется скриплеты. Скриплет – это смешивания и
html код и java. Как вы видите такой код не удобно читать. В Java существует
удобная библиотеки в которой содержаться большинство нужных
конструкций для построения вида.
Давайте ее подключим.
И теперь нужно подключить библиотеку скриптов на JSP.
Далее давайте теперь заменить скриплеты на использование JSTL.
Теперь все параметры можно получить только из атрибута запроса. Для этого
нужно их установить и сделать переправку на страницу.
И запрос теперь нужно делать через сервлет [Link]
Теперь осталось убрать возможность получить прямой доступ к нашему виду.
Для этого переместим наш вид в папку WEB-INF/views/
Таким образом мы реализовали популярный шаблон проектирования MVC. У
нас есть Модель(User), View([Link]), Controller(UserActions)
Задания
Реализовать проект клинику домашних животных в Web приложение
Приложение должно иметь два интерфейса.
1. Пользовательский
2. Администратор.
Администратор системы может добавлять, редактировать и удалять
пользователей и их питомцев.
В пользовательском интерфейсе должен присутствовать чат.
Решение
Первоначально сделает продумать, какие оконные интерфейсы будут
существовать в системе и исходя из этих данных, спроектировать базу
данных. Такой подход называется восходящим проектированием.
1. Форма входа в систему.
2. Список всех пользователей и их питомцев.
3. Форма добавления нового пользователя.
4. Форма добавления нового питомца.
5. Форма отображения данных пользователя и чат.
Для организации проверки доступа введем понятие роль.
Если роль администратор, то входим в интерфейс администратора, в
противном случае заходим пользователем.
Для реализации авторизации будем использовать Filter. Этот механизм
позволяет предварительно обрабатывать все запросы.
Создадим класс.
И зарегистрируем его в [Link]
Все запросы проходящие по маске *.do в приложении будут проходить через
этот фильтр. Макса выбрана таким образом, чтобы проверка происходила
только на бизнес запросы и не блокировала таблицы стилей. В нем мы
проверяет, есть ли текущая сессия у пользователя, если нет, то отправляем на
страницу логина.
Теперь перейдем в реализации хранилища данных. Для каждой модели
создадим отдельный класс, где будет хранить его данные.
Давайте рассмотрим пример хранилища для роли.
В качестве базового шаблона используется singleton.
Для поиска роли по id используется Stream API введенное в Java 8
Реализация хранилища достаточно тривиальная. Все данные хранятся в
потокобезопастной коллекции.
Важно отметить, операции между коллекциями являются не атомарными.
Рассмотрим теперь контроллер.
Схема реализации сделана аналогично схеме, показанной в уроке. Точкой
входа в приложения является сервлет, после чего происходит
перенаправление данных на jsp.
Занятие 15. Тестирование Servlet. Mockito
Видео
В этой главе речь пойдет про тестирования Servlet. Давайте создадим
простой тест на UserActions.
Сначала будет тестировать добавления нового пользователя.
Если запустить это текст упадет ошибка NPE. Основная проблема в
написании тестов для servlet - у нас нет возможности создать объекты
запроса и ответа. Для того, чтобы создать эти объекты и добавить в них
данные нужно использовать специальные объекты заглушки, которые
позволяют задавать поведение нужных методов. Для реализации объектов
заглушен нужно использовать специальную библиотеку Mockito.
Давайте теперь создадим объекты запроса и ответа и проинициализируем
нужные нам параметры.
Теперь наш тест прошел успешно. То есть таким образом можно заполнять
нужные нам параметры.
Давайте теперь проверим перенаправлении страницы.
Задания
Добавить тестирование сервлетов.
Добиться площади покрытия больше 80%
Занятие 17. HTML, CSS, JS
Видео
Практически все серверные приложения имеют web клиент. Для создания
клиента используется отдельный язык HTML. HTML не имеет никакого
отношения к Java. Он имеет свой синтаксис и свой процесс интерпретации.
Давайте создадим пустой файл в папке webapp/[Link]. [Link] - это
обычный текстовый файл. Его можно создать через блокнот и редактировать
там же.
Со следующим содержимым.
Вся страница разбита на два главных блока – это head и body. Head – слушай
для конфигурации страницы, прописывания кодировки, скриптов и стилей.
Body – это код отображения. В нем мы будет создавать наш вид. Весь код в
html представляет собой теги. Теги – это текст, обрамленный угловыми
скобками, Теги существуют двух типов: открывающий и закрывающий.
Отличие закрывающего в том, что после угловой скобки идет слешь.
Между тегами вноситься подтеги или текст.
Например,
Перейдем к базовым элемента html.
table – теги используется для отображения таблицы.
Form – элемент для отправки данных на сервер.
Input – элемент для ввода данных. Атрибуты type задает вид отображения –
поля ввода, checkbox, radio.
В теги form – используется атрибуты action – указывает адрес, куда следует
отправить данные и method – метод протокола обычно POST для формы.
Div – формирование блока.
Label – метка.
Теперь эту страницу можно открыть в браузере. Двойной клик и вы должны
увидеть следующее.
Вид конечно не красивый, но он выполняет нужные нам функции. Для того,
чтобы сделать вид красивым нужно использовать таблицы стилей – CSS
(cascade style sheet).
Для этого создадим отдельный файл – [Link].
Добавить в него следующий текст.
И теперь нужно подключить стили в нашу страницу.
Теперь обновим страницу в браузере и посмотрим на изменения.
Теперь вид отображения выглядит лучше. То есть у нас появилась
возможность изменять вид.
Формирования таблицы стиля происходит по следующему принцип.
1. Вначале указывает элемент, к которому должен применять стиль. Это
может быть тег, значение атрибута class, id элемента.
2. В тела стиля указываем ключ-значение.
Теперь нам осталось добавить еще проверку введенных значений. Для этого
будет использовать JavaScript. Это язык программирования, который не
имеет отношения к Java. Добавим следующий код в заголовок.
Данный код проверят заполнено ли поле name или нет. Если нет, то форма не
будет отправляться.
Для использования этого скрипта нужно подключить его в форму в атрибут
onsubmit.
Задания
Добавить стили.
Добавить проверку введенных значений за счет js.
Добавить разнообразные формы.
Решение.
В качестве основы дизайна этого приложения лучше всего взять готовые
решение. В данном проекте будут использоваться bootstrap.
Расположим папку в webapp
Начнем со страницы логина.
Я нашел готовый шаблон и просто скопировал html код.
Аналогичным образом я составил остальные интерфейсы
Как вы видите интерфейс сразу выглядит привлекательно.
В bootstrap сразу встроена проверка корректности ввода.
Загрузка и отправка сообщений на странице пользователя происходит по
средствам ajax jquery.
Как вы видите в код не используются специфические библиотеки тегов и как
вы увидите дальше, я больше не буду изменять данный, то есть он легко
интегрируется под любой серверный язык или Фреймворк (Например, Spring
MVC).
Занятие 18. SQL
Видео
Первоначально необходимо установить и настростроить базу данных. Во
втором занятии я пропустил этот пункт, так как посчитал, что процесс не
вызовет больших проблем. Одноко, большинство учеником имеет проблему
имеенно с установкой и настройкой. Ниже показана серия картинок с
процессом установки базы данных PostgreSQL 9 в Windows 10.
1. Начало установки
2. Путь куда установить.
3. Путь для хранения данных базы.
4. Важный пункт. Установка пароля. Обязательно запомните, что вы
вводите тут. Вводите поначалу простой пароль. Его в дальнейшем
можно будет изменить.
5. Начало установки.
6. Процесс.
7. Установка порта.
8. Кодировка.
9. Завершение установки.
10. Установка дополнительных приложений. Нужно выбрать PGAdmin.
После завершения установки сервер базы данных автоматически запуститься,
и вы сразу можете начать работать. В установленных программах находим
PgAdmin и запускаем ее.
Первоначально необходимо настроить соединение с базой данных.
В настройках нужно указать хост, пароль и порт, которые вы указывали при
инсталляции.
База данных представляет группу связанных таблиц. Каждая таблица имеет
колонки и строки. Колонки задаются именами и типами хранимых данных.
Ниже описаны основные типы данных.
integer Целочисленный тип
varchar Символьный тип
timestamp Тип для хранения даты и времени
boolean Логический тип
text Тестовое поле
Для работы с базой данных используется специальный язык SQL. С
помощью SQL можно осуществить все операции.
Для создания новой базы данных можно использовать PgAdmin.
Либо с помощью скрипта.
Psql – это консольный инструмент для работы с базов, аналог pgadmin.
Давайте создадим новую базу с именем clinic.
Следующим этапом будет создания структуры или схемы таблиц.
Для создания новой схемы я использовал следующий скрипт.
Имя таблицы может быть произвольным, за исключением ключевых слов,
которые зарезервированы в самой базе данных.
После имени таблицы открываются скобки в который указываются колонки
таблицы с указанием типов данных.
Важно, что каждая таблица должна иметь первичный ключ. Первичный ключ
– это уникальная колонка в таблице по которой определяется, что данные в
строке являются уникальными.
Давайте теперь создадим вторую таблицу – pets.
В этом скрипте появилось новое ключевое слово references. Оно
используется для связывания одной таблицу к другой. При создании новых
записей в таблицу pets, вам нужно указать к какому пользователю вы их
хотите привязать. Если пользователя не указать, то вы не сможете вставить
данные.
Теперь перейдем к рассмотрению 4 базовых операций.
Добавление данных.
Обновление данных.
Для обновления данных необходимо использовать первичный ключ и
указывать его в блоке where.
Удаление.
Скрипт удаление тоже использует блок where.
Получение данных.
Получение данные по условию.
Задания
Создать структуры базы данных для хранения данных клиники
Создать записи для клиентов, питомцев.
Редактировать записи для клиентов, питомцев.
Удалить записи для клиентов, питомцев.
Получение записей о клиентах, питомцах.
Решение.
Решение находиться в папке lessons_20/src/main/resources/[Link]
Занятие 20. JDBC
Видео
Для работы с базой данных в Java используется универсальный способ. Есть
общий интерфейс работы с базами данных – JDBC. Ключевая особенность
этой технологии в том, что там нет необходимости знать специфику работы с
конкретной базы, программа абстрагируется за счет использования
интерфейс.
В данном курсе мы используем PostgreSQL, поэтому нам нужно подключить
именно этот драйвер JDBC.
Давайте напишем пример подключения к базе данных и извлечём из данные.
Создадим модель данных описывающий табличное представление users.
И аналогично для модели питомец.
Создадим класс хранилище данных, которое будет выполнять основные
операции: создание, обновления, удаления и редактирование.
Входящие параметры являются параметрами для подключения к базе
данных.
Добавление новой запиши в таблицу users.
Предварительно напишем тест по TDD, который будет падать.
Здесь мы указываем JDBC URL – он будет у вас идентичный, если в не
меняли порт подключения.
Теперь реализуем необходимые методы для данного теста.
Первое, что нужно сделать это создать объект Connect – 46 строка. В нее
передаются параметры соединения. Далее нужно создать объект
PreparedStatement. Этот объект служит для преобразования запроса и его в
поленения. Важно отметить, что данные в запросы мы передаем через метод
объекта PreparedStatement, а не делаем сложение строк с нужными нам
параметрами. Это наиболее часто ошибка начинающих программистов.
Далее мы получаем объект ResultSet – это объект служит для получение
данных из базы. Так как мы задали первичный ключ serial – это значит мы
просим базу данных инкрементировать нам первичный ключи при
добавлении новых данных. Поэтому нам нужно получить этот
сгенерированный ключ. Вся работа с базой данных должна происходить в
try-resource конструкции для того, чтобы не было не закрытых соединений.
Рассмотрим код получения объекта по первичному ключу.
Схема работы данного метода аналогично. Нам нужно создать объект
Connect, далее из него получить объект PrepatedStatement. В него проставить
скрипт и через специальные метод заполнить параметры запроса.
Здесь используется двойной try-resources. ResultSet реализован на шаблоне
итератора. Для движения указателя нужно вызывать метод next. Важно
отметить, чтобы получить нужные данные мы используем методы get с
указанием типа данных и входящим параметром имени колонки.
Обновление данных.
Удаление.
Получение списка пользователей.
Задания
Заменить хранение данных в клиенте на хранение в базе через jdbc
Решение.
Архитектура данного проекта построена слоями. В предыдущем уроке весь
проект уже был создал, но в нем все данные хранятся в памяти и после
остановке сервера будут пропадать. Поэтому в этой задаче нам нужно просто
реализовать хранилища на базе jdbc без изменения интерфейсов их
использования.
Так как у нас многопользовательское приложение, то стоит использовать пул
соединений. В качестве реализации в проекте подключен c3p0.
И сделан класс для получения соединения.
И рассмотрим реализацию хранилища для роли.
Важно отметить, что при получении данных по id, объекта может не
существовать в базе. По этой причине используем класс Optional из Java 8.
Теперь перейдем к тестированию. Так как все тесты должны быть
автономными и быстрыми, то стоит рассмотреть использование базы данных
в памяти – HyperSQL.
Создадим альтернативный класс для настроек.
В нем пропишем соединения для hsqldb.
При старте каждого теста в памяти будет создаваться новая схема базы.
И рассмотри тестирование хранилища роли.
Занятие 21. Hibernate, Config
Видео
При работе с базами данных через стандартные средства JDBC возникает
основная проблема не соответствии табличных данных и моделей объектов в
Java. Эту проблему решают ORM Фреймворки. Наиболее популярный из них
hibernate.
Перейдем сразу в практике. Подключим необходимые библиотеки и сделаем
нужную конфигурацию.
Следующим этапом будет создания конфигурационного файла. Создадим
файл /resources/[Link]
Настройки соединения аналогичная как в JDBC, только еще добавился тип
диалекта.
Теперь создадим модель данных.
Теперь важный этап создания связи нашей модели и таблицы в базе данных.
Для этого создадим файл /resource/ru/parsentev/models/[Link]
Завершающим этапом конфигурации является регистрации связи в
конфигурационном файле Hibernate.
И теперь можно начать использовать Hibernate. В начале стоит реализовать
стандартные операции: создание, редактирование, удаление, получение.
Следуя принципу TDD начнем с тестов. Создадим класс UserRepository без
реализации.
И напишем на него тесты.
Запустим тесты и убедимся, что они у нас падают.
Теперь займемся реализацией.
Главным объектом здесь будет SessionFactory, через него будет происходит
вся остальная работа.
Метод получения данных.
Как вы видите методы вглядят на много лаконичней, нам нет необходимости
выбирать нужные поля и создавать объект.
Задания
Создать новый тип хранилища на основе Hibernate.
Решение.
Код исходного решения находиться в папку /lessons_23/.
Создадим класс HibernateFactory для хранения SessionFactory.
Обратите внимание, я не изменяю сигнатуры методов хранилищ. Это
позволим не менять код для сервисов и контроллеров.
Занятие 22. Hibernate, Mapping
Видео
В предыдущей главе вы познакомились с конфигурированием Hibernate и
научились делать базовые операции: создание, редактирование, удаление и
получение данных. Однако, эти действия не отображают всей мощи этого
Фреймворка. Давайте дальше рассмотрим пример с моделями данных.
В данных моделях осуществляется два типа связей: агрегация и композиция.
Схема связи данных.
Теперь перейдем к конфигурированию связей.
И напишем тест проверки такого поведения.
Для реализации композиции можно использовать теги one-to-one и many-to-
one. Для тега one-to-one нужно использовать общий идентификатор, который
накладывает много ограничений. В данном примере реализована many-to-
one. Ее следует рассматривать как to-one. То есть один к одному.
Важный момент, при создании или обновлении такого объекта нужно
указывать связанный объект. У начинающих программистов с этих моментов
возникают проблемы. Здесь нужно просто понять, что для базы данных
нужен только id. Поэтому в примере создается конструктор, который
инициализирует user id.
Для реализации агрегации существует много вариаций: bag, set, list, map. Все
эти теги позволяют использовать все многообразие коллекций в языке Java. В
данном примере используется bag, т.к. у нас нет привязки к индексу.
Про атрибуты будет рассказано в следующей главе.
Задания
Добавить связи в моделях клиники.
Решение.
Файлы модели находятся в папку /lessons_023/
Занятие 22. Hibernate, Transaction, Fetch strategy, Query
Видео
Наиболее важная функция базы данных – это возможность извлекать из нее
данные. Поэтому очень важно научиться правильно строить запросы и
конфигурировать модели данных.
Первое, с чем стоит разобраться при написании запросов это понятия
транзакции. Транзакции – это процесс выполнения операций, при котором
должно быть гарантированно выполнения всех операций, либо если хотя бы
одна операция не выполнилась успешно, действия других операций
отменяется. Это одно из свойств базы данных, которая называется –
атомарность.
Ключевым требованием к конфигурации транзакция является влияние
действий изменения данных одной транзакции на другую. Данной свойство
описывает другую характеристику баз данных – справедливость данных.
Сначала, следует рассмотреть проблемы, которые могут возникнуть при
работе в базе данных.
1. Потерянное обновление.
Ситуация, когда транзакции A выполняет действия обновления
одновременно с транзакцией B. Но транзакция B отменяет свои изменения и
все изменения транзакции А будут тоже отменены.
2. Грязное чтение.
Ситуация, когда транзакция А выполняет чтения данных,
одновременное транзакция В выполняет обновления данных.
Транзакция А считывает изменения, которые не записаны в базу.
Транзакция В отменяется. Получается, что транзакция А считала
данные которых нет в базе.
3. Неповторяемое чтение
Ситуация возникает, когда транзация чтения из базы очень долгая, в
это время другая транзакция может выполнить изменения, и
получается, что первая транзакции считает данных, которые нельзя
будет повторить.
4. Затирания изменений
При одновременном чтении и записи двух транзакций, может
возникнуть ситуация – гонки. Когда данные запишет, та транзакция,
которая будет последней.
5. Фантомное чтение
Ситуация аналогичная не повторяемому чтению. Только в этом случаи
данные будут добавлены.
Все эти ситуации, можно избежать или учесть при работе программы. То
есть нужно учитывать скорость работы программы и критичность
справедливости данных.
Для настройки транзакции используются уровни изоляции. В базовой
реализации из 4.
1. Read uncommitted isolation. Самый быстрый, но самый не стабильный
уровень. В нем возможен случай потурённого обновления, что является
критичным для большинства система.
2. Read committed isolation. Уровень реализует механизм Read Write Lock.
Когда все могут одновременно читать, но только один может
записывать. Возможны случае неповторяемого чтения и фантомного.
3. Repeatable read isolation. Аналогичен второму. Только в этом уровне
транзакция записи должна дождаться завершения транзакции чтения.
4. Serializable isolation. Самая строгий уровень. Все транзакции
выполняются последовательно. Нет проблем, описанных выше. Но
очень низкая производительность.
Для настройки уровня изоляции используется ключ
[Link]
и его параметры
1 – Red uncommitted, 2 – read committed, 4 – repeatable read, 8 – serial
Выше описывается процессы, которые управляется на уровне базы данных.
Ниже будет описан процесс, который уже настраиваться программистом.
В предыдущей главы, мы создавали объекты User, Role, Comment. Объект
User содержит композиционные данные роль и агрегационные. Общее
правило при работе с базой данных уменьшить количество запросов.
В описание связи программист может выбрать различное поведения для
извлечения связанных объектов.
Композиционный объект роль связан через mane-to-one, по умолчанию
загрузка такого объекта происходить через select. То есть для загрузки нужно
выполнить дополнительный вопрос.
Hibernate: select user0_.uid as uid1_2_0_, user0_.login as login2_2_0_,
user0_.email as email3_2_0_, user0_.role_id as role_id4_2_0_ from users user0_
where user0_.uid=?
Hibernate: select role0_.uid as uid1_1_0_, role0_.name as name2_1_0_ from
roles role0_ where role0_.uid=?
Такая стратегия называется ленивая инициализация. Что это значит, если мы
не обращаем к объекту роль, явно, то объект не будет загружен. Это удобно,
когда нам нужно выгрузить данные только о самом пользователе. Важно,
объект можно загрузить только пока у нас открыта сессия. Сессия создает
проксирования для данной модели и заграет ее. Таком механизм реализует
шаблон проектирование – Proxy.
Существует так же обратный мезанизм, позволяющий загрузать
композиционный объект сразу при загрузки родителя. Для этого нужно
добавить аттрибут fetsh=”join”. Загрузка объекта в этом случае будет сразу с
загрузкой осного объекта.
Hibernate: select user0_.uid as uid1_2_0_, user0_.login as login2_2_0_,
user0_.email as email3_2_0_, user0_.role_id as role_id4_2_0_, role1_.uid as
uid1_1_1_, role1_.name as name2_1_1_ from users user0_ left outer join roles
role1_ on user0_.role_id=role1_.uid where user0_.uid=?
В этом случае мы не привязаны к состоянию сессии. Объект
инициализируется сразу. Минут такой стратегии – это увеличение времени
запроса. Но с другой стороны, у нас сразу загружен объект. То есть всегда
нужно оценивать пользу и вред. Такая стратегия будет быстрее, если нам
нужно работать с композиционными объектами. А не только с родителем.
Аналогичная ситуация касается и агрегационных объектов. В нашем случае
это список комментариев. Здесь в любом случае выполняется
дополнительный запрос, но мы можем явно указать, когда это делать.
Добавлением ключа lazy=”true” – создает прокси объект, который загружает
данные только при явном обращении к ним.
Hibernate: select user0_.uid as uid1_2_0_, user0_.login as login2_2_0_,
user0_.email as email3_2_0_, user0_.role_id as role_id4_2_0_, role1_.uid as
uid1_1_1_, role1_.name as name2_1_1_ from users user0_ left outer join roles
role1_ on user0_.role_id=role1_.uid where user0_.uid=?
Hibernate: select messages0_.user_id as user_id2_2_0_, messages0_.uid as
uid1_0_0_, messages0_.uid as uid1_0_1_, messages0_.user_id as user_id2_0_1_,
messages0_.text as text3_0_1_ from messages messages0_ where
messages0_.user_id=?
В первой главе использовался самый простой пример извлечения данных
через [Link]([Link], ID)
Hibernate преобразует такой код в вывоз запроса в базу через select. Для того,
чтобы писать более сложные запросы нужно использовать объект Query.
Получение пользователя по ID можно переписать через прямой запрос.
В качестве запроса используется синтаксис языка Hibernate Query Language
(HQL). Это язык имеет аналогичный синтаксис SQL, а также свои
дополнительные конструкции.
Аналогичным образом, как и в JDBC, происходит установка параметров в
запрос. Важно, все параметры устанавливаются через метод setParameter.
Нельзя составлять запрос из сложения строк. Метод setParameter имеет
перегруженную реализацию с явным указанием типа объекта.
Для реализации порционного вывода используется указатели начала и конца
выборки.
При выполнении любого запроса через Hibernate все объекты загружаются в
первичный кеш. Иногда нам нет необходимости повторно работать с ними
как с персистентными объектами. Для этого можно явно указать, что запрос
должен только считать данные без загрузки их в кеш.
[Link](true);
Ниже рассмотрим основные команды HQL.
1. Select.
2. Where
3. Ordering
4. Projection. Получение конкретных колонок.
Задания
Добавить сложные поисковые запросы в клинику через Hibernate
Query.
Решение.
Исходный код находится в папке /lessons_23/
При получении композиционных данных в запросу нужно явно указывать
какие данные нужно извлечь – join fetch [Link].
Занятие 23. Hibernate, Интеграционное тестирование
В каждой код в данной книге содержит проворящий код или тесты.
В интернете вы можете прочитать такую мысль, что тестировать
слой DAO нет необходимости. На этот счет у меня одно простое
правило – весь код, написанный вами должен быть протестирован.
Зачем нужны тесты для тестирования прослойки персистенции.
1. Проверяем корректность моделей.
2. Проверяем корректность запросов.
Как вы уже знаете Junit тесты должны быть автономными. Для
тестирования прослойки базы данных нам необходима сама база
данных. В этом случае тесты уже получаются не автономными.
Они называются интеграционными. Однако, есть инструменты,
которые позволяют проводить интеграционные тесты тоже
автономно.
Для этой цели мы будет использовать встроенную базу данных
HyperSonic. Данная база написана на Java и имеет решим работы с
памятью.
Первое, что нужно сделать, это подключить необходимые
зависимости.
Так как у нас уже создана настройка для рабочей базы, нам нужна
новый конфигурационный файл для наших тестов.
В разделе src/test/resources/ создаем новый файл [Link]
В файле указываем необходимые настройки для базы HyperSonic.
Важно, интеграционные тесты должны работать максимально
быстро. Для этой цели мы используем базу, которая будет
существовать только в памяти.
Для этого нужно указать JDBC url - jdbc:hsqldb:mem:clinic
<property name="[Link]">jdbc:hsqldb:mem:clinic</property>
Так же, для поддержания схемы базы данных в актуальном
состоянии нужно использовать ключ
<property name="[Link]">update</property>
При загрузки тестов схема базы данных будет автоматически
обновляться.
Теперь можно приступить к написанию тестов.
В тестах нам не нужно указывать новый конфигурационный файл.
При загрузки тестов maven сначала проверяет папку с
src/test/resources/, если в нем есть файл, он будет использовать
именно его.
Задание.
1. Произвести интеграционное тестирование.
Решение.
Исходный код находиться в папке /lessons-23/
Занятие 25. Spring, IoC
Видео
Практически во всех вакансиях можно найти упоминание этого
замечательного Фреймворка. Так что же такое Spring? В официальной
документации вы можете найти определение, что Spring – это набор модулей,
которые могут решить любые задачи для JEE. Для новичков такое
определение будет пустым звуком. Когда я стал проводить первые занятие по
этим библиотекам, мне необходимо было доступными словами объяснить,
что такое Spring. Мое определение этого Фреймворка такое. Spring – это
огромная коробка с инструментами (Например, молотов, пила, зубило),
которые вы можете как конструктор составлять вместе или добавлять новые.
Каждый инструмент можно использовать отдельно либо совместно с
другими инструментами. Базовый принцип Spring основан на принципах
loosing couple. Смысл его близок принципам SOLID. Кратко, можно сказать.
Связывать код через абстракции.
Давайте рассмотрим простой пример. Доспустим у нас есть абстракция,
которая рисует фигуру и лист бумаги, который можно очистить.
Для создания объекта Sheet нам нужно получить зависимый объект –
реализацию объекта Paint. Данных шаблон называется инверсией
зависимости (IoC, DI). В Spring – это базовый модуль. Создадим две
реализации интерфейса Paint.
Теперь при создании объекта Sheet мы можем указать нашу зависимость.
Теперь давайте посмотрим, как это можно реализовать через Spring.
Первоначально необходимо добавить зависимости в [Link].
Все конфигурирование Spring сводиться к создания специального файла –
контекста. Он может быть создан через аннотации или через xml. В данном
курсе я буду показывать только создания контекста через xml. Контекст – это
по сути абстрактное хранилище всех объектов. Если сравнивать с не
компьютерной тематикой, то контекст – это ящик с инструментами. Мы
может достать из ящика нужный нам инструмент и воспользоваться им.
Создадим пустой класс [Link]. Он должен лежать в папке
resources. Добавим в него следующий шаблон.
Это и есть контекст нашего Spring. Пока в нем нет объектов или как их
принято называть в Spring – Beans. Давайте зарегистрируем наши объекты в
контексте. Для этого в корневой тег добавить под теги - <bean>
И напишем тест.
Для получения контекста мы используем специальную конструкцию.
После создания контекста в нем происходит инициализация всех объектов.
Если вы сравните код со спрингом и без, то вы можете заменить, что в коде
мы больше не создаем явно переменные. Все это за нас выполняет Spring.
Это очень важная и простая концепция.
Стоит отметить, что в данном случае, мы явно указывали какой бин
использовать, то есть код со Spring не испольно отличается от кода без него.
В Spring есть возможность автоматически подбирать необходимые
зависимости. Для этого нам нужно воспользоваться аннотациями.
Отметим все наши классы аннотацией @Component.
Далее попросим Spring автоматически просканировать исходный код проекта
и автоматически зарегистрировать все компоненты. Для его добавить
следующую строчку в xml.
Важно добавить необходимые теги в корневой тег.
Зарегистрированные ранее объекты теперь можно удалить.
Теперь можно отметить еще одну дополнительную аннотацию, которая
автоматически попытается найти нужный объект и подставить его в
конструктор при создании объекта.
Следует четко понимать, как работает аннотация @Autowired. Spring
проверяет все объекты и подставляет, только в то случае, если объект
единственный, если объектов больше чем один, будет выброшено
исключение.
Что бы избежать такой проблемы, можно либо отметить первенствующий
объект, через аннотацию @Primary
Либо использовать аннотацию @Qualifier с явным указанием имени объекта.
Если у объекта явно неуказано имя нужно прописывать имя файла с
прописной буквы.
Так же стоит уделить внимание времени жизни объекта. Существует 4 типа.
1. Singleton – выставляется по умолчанию. Объект создает один на весь
контейнер.
2. Prototype – объект создается каждый раз при получении его из
контекста.
3. Session – объект создается для каждой новое HttpSession.
4. Request – объект создается для каждого нового запроса.
Задания
Реализовать dbtool. Использую Spring IoC.
Решение.
Исходные кода находятся в папке /lessons_028/
Так как использование консольного ввода в данном случае будет не удобно,
загружать необходимые данные через xml.
Создадим файл описывающий данные нашего проекта.
Для считывания и преобразования данных в объекты будем использовать
JAXB, который позволяет автоматически преобразовать данные xml в нашу
модель.
И дальше считаем этот файл и загрузим его в базу данных.
Занятие 24. Spring Template, Hibernate
Видео
Теперь, когда вы познакомились с базовой концепцией Spring пока начать
его использовать в полном объеме и показать все его возможности. Все
корпоративные приложения используют хранилища данных. Это либо SQL
либо NoSQL базы данных. В Spring используется общая концепция работы с
любые типами баз данных.
Ниже проиллюстрирована схема.
Существует слой сервисов, который зависит от хранилищ данных. Мы
абстрагируемся от конкретной реализации за счет использования интерфейса.
Инъекции зависимостей, а нас выполняет Spring.
В данном курсе мы уже рассмотрели JDBC и Hibernate. Spring имеет удобные
классе обертки для работы с этими технологиями.
Рассмотрим JdbcTemplate.
Для реализации интеграции нужно подключить новые зависимости.
Так же добавить драйвер базы данных.
Главный объектом для работы с базой данных будет DataSource. Его
необходимо зарегистрировать в контексте.
Настройки остались аналогичными, как и в главе про jdbc.
Далее нужно зарегистрировать шаблон и указать в нем зависимый объект.
Теперь создадим модель для хранения и интерфейс репозитория.
Модель.
И теперь перейдем в реализации репозитория.
Идея всех запросов аналогичная, как и в простом jdbc.
И теперь напишем проверку.
Аналогичным образом сделаем настройку для HibernateTemplate.
1. Подключаем зависимости.
2. Создать файл mapping.
3. Создать отдельный интерфейс. Он нужен для создания прокси объекта
при работе транзакции.
4. Регистрируем DataSource и SessionFactory.
5. Добавляем поддержку транзакций.
Важно отметить для регистрации транзакции в корневой тег нужно
добавить новые схемы.
6. Регистрация HibernateTemplate
7. Реализация UserHibernateRepository.
8. Пишем тест.
И напоследок рассмотрим наиболее универсальный способ использования
Spring Data.
1. Добавляем нужные зависимости.
2. Регистрируем интерфейс.
3. Конфигурируем соединения с базой данных и транзакции.
4. Конфигурируем модель. В Spring Data не поддерживает xml описание.
5. И тесты.
Из полученных реализаций видно, что самый простой способ использовать
Spring Data.
Задания
Сделать интеграцию Spring и Hibernate в dbtools.
Продемонстрировать использование трех подходов.
Решение.
Исходный код находиться в папке /lessons_28/
В данном проекте я выбрал самое быстрее решение — это использовать
Spring Data.
Все модели теперь будут описываться через аннотации.
И код репозитория.
Как вы видите код репозитория представляет из себя только интерфейс.
Реализация подставляется динамически через рефлексию.
Занятие 25. Spring MVC
Видео
Данный модуль является наиболее интересной частью Spring. На основе
модуля Spring MVC построено множество других модулей REST API,
WebSocket, WebService, Security и т.д.
Общая схема работы Spring MVC показана ниже.
Схема работы аналогичная реализации MVC описанной через Servlet JSP.
Главное отличие, что точкой входа является распределяющий сервлет.
Первоначально нужно подключить необходимые зависимости.
Далее создаем [Link].
Пока он выглядит точно так же, как и в предыдущей главе. Теперь нужно
прописать распределяющий сервлет в [Link].
Далее нужно сконфигурировать бин, который отвечает за расположение
видов.
Все виды должны располагается в папке /WEB-INF/views/
Теперь давайте создадим наш первый вид. Создадим файл [Link]
Здесь точно так же используется jstl.
Теперь рассмотрим контроллеры. Контроллер — это основной элемент при
работе с Spring MVC.
Как вы видите контроллер — это обычный класс. Класс обозначен аннотаций
@Controller. Далее нужно создать обычные методы.
Взависимости от целей метода, вы можете указывать входные параметры. В
данном примере я испольую ModelMap. Этот объект используется по
аналогии с HttpRequestServlet в него можно установить необходимые
параметры. В данном случае устанавливается объект user, который
используется в виде.
Метод возвращает String – это имя с путем до вида, без расширения.
Например, если вид лежит в /WEB-INF/views/admin/[Link] – возвращать
надо admin/users.
Давайте теперь создадим форму добавления.
Добавим модель Item.
Особо хотел уделить внимание в модели есть композиционный объект User.
Ниже будет описание, как его заполнять в форме и контроллере.
Теперь нужно добавить новый метод в контроллер.
Для получение данных из запроса используется новая аннотация
@ModelArrtibute. Spring автоматически собирает объект из запроса и
передает его в метод.
Так же после добавления объекта в коллекцию контроллер делает запрос
редирект.
Давайте теперь добавим в форму список с ответственными.
Для обращения к объекту handler нужно использовать имя метода setHandler
и дальше указываем поля самого объекта User. Spring автоматически создаст
объект User и проставит его в Item. Код контроллера менять не нужно.
Задания
Реализовать MVC через Spring.
Решения.
Исходный код находиться в папке /lessons_28/
Здесь весь процесс аналогичен коду, представленному в уроке.
Хотел только привлечь ваше внимание к этапу сохранения данных, когда в
модели есть композиционная модель роль.
В модели данных пользователя поле роль описывается объектом Role.
Когда происходит сохранения формы мы можем указать какие поля
заполнить.
Когда контроллер видеть параметр [Link] он пробует создать объект роль и
проинициализировать поле id. Аналогично можно указать и другие
параметры: [Link].
В самом контроллере мы ничего не указываем, весь объект будет собран
через Spring MVC.
Занятие 28. Spring Security
Видео
Одной из важных элементов корпоративных приложений является
безопасность. В область безопасности относиться так же идентификация
пользователя. Как вы знаете сервер не может идентифицировать
пользователя. Первое, с чем нужно разобраться это идентификация
пользователя. Общая идея реализации идентификации – это использования
ключа, который привязывается к пользователю. Ключ генерируется на
основании данных от запроса пользователя. При повторном запросе нужно
указывать этот ключ, в этом случае сервер будет считать, что запрос пришел
от пользователя, который уже посещал ресурс. Этот механизм
реализовывается через HttpSession.
Давайте теперь подключим необходимые библиотеки начнем
конфигурирование.
Важно, что Spring Security базируется на Spring MVC и отдельно работать не
может. То есть вам нужно сконфигурировать Spring MVC и добавить все
библиотеки.
Создадим отдельный файл [Link]
Главный тег в данном случае это http. В нем мы прописываем какой
пользователь может иметь доступ по URL. Данных механизм описывается
распределением прав по роли. Каждому пользователю в системе будет
выдана роль. По роли будет определяться уровни доступа.
Следующий блок – это блок данных авторизации. Здесь мы указываем
логины, пароли и роли. В данном случае данные прописаны жестко, но ниже
будут показан пример использования данных из базы данных.
Теперь нужно загрузить данный контекст в основной контекст.
И добавить фильтр обработки.
Общий механизм работ модуля безопасности основан на использовании Filter
и пакета [Link].
Запустим приложение и попробуем обратиться к странице /[Link]
Результат запроса - редирект на страницу ввода логина и пароля. Форма
ввода – это стандартная форма. Давайте сделай пользовательскую форму.
Для этого создадим отдельную страницу [Link].
Важно отметить, что адрес обработки, имена полей задаются в конфигурации
контекста.
Теперь добавим контроллер.
Запустим приложение и перейдем на страницу /[Link].
Если ввести не верный пароль или логин будет ошибка.
В остальном действие приложение осталось прежним.
Теперь перейдем к части, когда в самом приложении нужно получить данные
о текущем пользователе.
Для этого нужно получить доступ к контексту и вызвать специальный метод,
который возвращает сведения о текущем пользователе.
Через этот объект мы можем получить необходимые данные, логин и роль.
В приведенном примере данные о пользователях задаются в контексте
настроек. Такой подход ограниченный и в большинстве случаев не подходит.
Давайте рассмотрим пример, когда данные о пользователе хранятся в базе
данных.
Необходимо проверить, что у нас подключен драйвер.
Далее добавить dataSource в главный контекст.
Создадим необходимую структуру в базе данных.
И последнее припишем настройки проверок.
Так же есть возможность создать свой собственный провайдер. Для этого
нужно расшить интерфейс AuthenticationProvider
Пример использования собственного провайдера.
Задания
Добавить прослойку проверки авторизации и аутентификации.
Решение.
Исходный код решения находиться в папке /lessons_28/
Так как форма логина уже реализована в задании с сервлетами, то весь вид
останется прежним.
Весь код построен на html5 и bootstrap.
Важно в форме логина обязательно должно присутствовать поле с token.
Перейдем к контроллеру, в который перенаправляемся в случае неудачной
авторизации или отсутствие сессии.
Проверять авторизацию и аутентификацию будет только по шаблону *.do.
Это не даст блокировать ресурсы bootstrap.
И файл конфигурации [Link].
Как я рассказывал выше в уроке для получения данных о текущем
пользователе нужно использовать SecurityContextHolder. Он будет
использоваться на странице добавления сообщений.
И сам контроллер.
29. Заключение.
Хочется закончить эту книгу со слов поздравления. Теперь перед тобой
открыт новый путь реальных проектов. Первое что нужно будет сделать – это
составить резюме, указав в нем все технологии, используемые в данной книге
и курсе. Дальше схема простая, делаешь рассылку своего резюме с
сопроводительным письмом. В письме следует постараться отобразить свой
опыт, стремления и цели. Скорее всего первые собеседования не дадут
нужного результата, не стоит расстраиваться. Нужно продолжать улучшать
свои знания. Так же советую каждому начать писать свой проект, который
будет приносить пользу окружающим и вам в том числе. Такой проект
можно будет показать на собеседовании.
Желаю успехов.