Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
При создании объектов с помощью оператора new
возвращается ссылка на вновь созданный объект. Однако нас никто не обязывает эту ссылку присваивать в качестве значения ссылочной переменной. В таких случаях создается анонимный объект. Другими словами, объект есть, а переменной, которая бы содержала ссылку на этот объект, нет.
С практической точки зрения, это может выглядеть бесполезным, однако, анонимные объекты требуются довольно часто - обычно в тех ситуациях, когда объект класса используется один раз. Рассмотрим пример
В данном случае, нам нужен объект класса PrinterManager только для одного действия - для распечатки файла. То есть, мы создаем объект, вызываем метод, после чего объект нам больше нужен.
В первом случае используется обычный порядок работы с объектами - создаем объект класса PrinterManager, ссылка на объект записывается в ссылочную переменную manager.
Во втором случае мы создаем анонимный объект. Инструкцию
можно условно разбить на две части. Выражениеnew PrinterManager()
создает новый объект класса PrinterManager и возвращает ссылку на объект в качестве результата выражения. Поскольку ссылка не присваивается переменной, то такой объект является анонимным. Но так как выражение new PrinterManager()
возвращает ссылку на объект, мы можем у этой ссылки вызвать метод printFile()
.
Лекция Bob Martin "The Future of Programming"- https://www.youtube.com/watch?v=ecIWPzGEbFc
Мэтт Вайсфельд - страницы 23 - 36.
Шилдт - страницы 37 - 55.
Необязательно! Шилдт - страницы - 58 - 125. (Глава 2 и 3). В 2 и 3 главе дано краткое описание примитивным типам данных, ветвлениям и циклам.
Гради Буч - страницы 33 - 73, 106 - 119.
Эккель, страницы - 40 - 46, 70 - 86.
Зарегистрироваться на сайте https://codingbat.com и пройти следующие уроки:
Warmup 1, 2;
String 1, 2, 3;
Array 1, 2, 3;
Logic 1, 2.
Интерфейсы и абстрактные классы улучшают структуру кода и способствуют отделению интерфейса от реализации.
Вернемся к примеру с классами фигур
Методы базового класса Shape всегда были "фиктивными". Попытка вызова метода из класса Shape привела бы к ошибке в программе. Такая логика написания кода связана с тем, что класс Shape нам нужен лишь для того, чтобы определить общий интерфейс всех классов, производных от него, а уже производные классы переопределяли эти методы и реализовывали их по-своему.
Если в программе определяется такой абстрактный базовый класс вроде Shape, создание объектов такого класса практически всегда бессмысленно. Абстрактный класс создается для работы с набором классов через общий интерфейс. А если Shape только выражает интерфейс, а создание объектов такого класса не имеет смысла, лучше всего запретить пользователю создавать такие объекты, так как попытка работать с этим классом приведет к ошибке в программе.
В языке Java для решения подобных задач применяются абстрактные методы. Абстрактный метод является незавершенным, он состоит только из объявления и не имеет тела. Приведем пример абстрактного метода:
Класс, содержащий один или более абстрактных метода, называется абстрактным классом. Такие классы также должны помечаться ключевым словом abstract (в противном случае, компилятор выдает сообщение об ошибке)
Компилятор запрещает создавать объекты абстрактного класса.
Если вы объявите не абстрактный класс, производный от абстрактного класса, то вы обязаны переопределить все абстрактные методы базового класса. Если это не будет сделано, то производный класс будет считаться абстрактным, и компилятор заставит пометить новый класс ключевым словом abstract.
Объявление класса как abstract не подразумевает, что все его методы должны быть абстрактными.
Класс можно обозначить как абстрактный даже когда в нем нет ни одного абстрактного метода. Это бывает полезно, когда необходимо просто запретить создание экземпляров этого класса.
Создавать абстрактные классы необходимо, так как они подчеркивают абстрактность класса, а также сообщают и пользователю класса и компилятору, как следует с ним обходиться. Кроме этого, абстрактные классы играют полезную роль при рефакторинге программ, потому что они позволяют легко перемещать общие методы вверх по иерархии классов.
Ключевое слово interface становится следующим шагом на пути к абстракции. Ключевое слово interface используется для создания классов, вообще не имеющих реализации. Создатель интерфейса определяет имена методов, списки аргументов и типы возвращаемых значений, но не тела методов. Интерфейс описывает форму, но не реализацию.
Ключевое слово interface фактически означает "Именно так должны выглядеть все классы, которые реализуют данный интерфейс". Поэтому любой код, использующий конкретный интерфейс, знает только то, какие методы вызываются для этого интерфейса, но не более того. Интерфейс определяет своего рода "протокол взаимодействия" между классами.
Кроме этого, в отличие от абстрактного класса, интерфейс позволяет реализовать своего рода, множественное наследование.
Чтобы создать интерфейс, используйте ключевое слово interface вместо class. Как и в случае с классами, перед ключевым словом interface указывается модификатор доступа (public, protected и так далее). Интерфейс также может содержать поля, они автоматически являются статическими (static) константами (final).
Для создания класса, реализующего определенный интерфейс (или несколько интерфейсов), используется ключевое слово implements. Фактически это означает "интерфейс определяет форму, а здесь будет показано, как это работает".
В классе, который реализует интерфейс, реализуемые методы должны быть объявлены как public.
Неважно, приводите ли вы преобразование к "обычному" классу с именем Shape, к абстрактному классу Shape или к интерфейсу Shape - действие будет одинаковым.
Когда метод работает с классом вместо интерфейса, мы ограничены использованием базового класса и его подклассами. Это исключает возможность использовать метод для класса, который не входит в эту иерархию. Интерфейс, в значительной степени, ослабляет это ограничение. В результате код становится более универсальным.
В Java допускается объявлять переменные ссылочного интерфейсного типа, то есть переменные, хранящие ссылки на объекты классов, которые реализуют определенный интерфейс.
Такая переменная может ссылаться на любой объект класса, который реализует интерфейс. При вызове метода для объекта по интерфейсной ссылке выполняется вариант этого метода, реализованный в классе данного объекта. Этот механизм аналогичен тому, когда ссылочная переменная суперкласса ссылается на объект подкласса.
Так как интерфейс по определению не имеет реализации, нет ничего, что могло бы помешать совмещению нескольких интерфейсов. При объявлении класса, который реализует несколько интерфейсов, имена интерфейсов перечисляются вслед за ключевым словом implements и разделяется запятыми.
Механизм обратного вызова (callback) является ключевым в программировании. При обратном вызове, программист задает действия, которые должны выполняться всякий раз, когда происходит некоторое событие. Например, можно задать действие, которое должно быть выполнено после нажатия на кнопку или при выборе определенного пункта меню.
Приведем небольшой пример. В стандартной библиотеке классов Java нам доступен класс javax.swing.Timer, который используется для отсчета интервалов времени.
Устанавливая таймер, мы задаем интервал времени и указываем, что должно произойти по его истечении. Как указать таймеру, что он должен делать по истечении времени?
Для этого в Java существует механизм обратного вызова. Он заключается в том, что программист должен передать объекту таймера объект некоторого типа. После этого таймер вызывает у объекта некоторый метод.
Разумеется, таймер должен знать, какой метод объекта он должен вызвать и этот метод в объекте гарантированно должен быть реализован. Для этого таймеру нужно указать объект класса, который реализует интерфейс ActionListener. Этот интерфейс входит в состав стандартной библиотеки и выглядит следующим образом
По истечении заданного интервала времени таймер вызывает метод actionPerformed() и передает ему объект класса Event (класс Event описывает событие в Java):
Как мы видим, конструктор класса Timer запрашивает задержку и объект, у которого будет вызван метод actionPerformed.
Создадим класс, который будет реализовывать интерфейс ActionListener
Как мы видим, данный класс ничего кроме реализации интерфейса не делает. То есть он нужен только для одной цели - он содержит метод, который будет вызван таймером после задержки. Такие классы и их объекты называют слушателями. Слушатель - это объект, который, как бы, "слушает" события, которые происходят с другим объектом. И когда это "слушаемое" событие происходит, вызывается указанный в интерфейсе метод этого объекта.
Создадим слушатель MessageDialogPoster и передадим этот объект таймеру
Запустим приложение и посмотрим на результат.
Обратите внимание, что метод actionPerformed принимает на вход объект класса ActionEvent. При вызове метода actionPerformed(), таймер передает в метод объект класса ActionEvent, который содержит различную информацию о событии. Таким образом, мы можем запрограммировать те или иные действия, в зависимости от параметров события.
Рассмотрим еще один пример, на этот раз будем использовать кнопку. Создадим объект окна, объект кнопки и добавим кнопку в окно.
По умолчанию, при нажатии на кнопку ничего не происходит. По аналогии с таймером, нам необходимо передать кнопке слушатель, который реализует определенный интерфейс. Кнопка гораздо более сложный объект, чем простой таймер, поэтому событий, которые привязаны к кнопке, может быть гораздо больше. Для каждого типа событий, кнопка принимает свой слушатель, который реализует свой определенный интерфейс.
С помощью такого многообразия интерфейсов, мы можем обработать самые разнообразные события, которые могут случиться с кнопкой.
В данном случае нас интересует метод addActionListener(), который принимает объект слушателя, который реализует интерфейс ActionListener
С помощью этого метода, мы передаем кнопке объект класса MouseClickHandler. Когда произойдет какое-то событие, кнопка возьмет этот переданный объект и вызовет метод actionPerformed() этого объекта.
Запустим приложение и нажмем кнопку.
Та
В рамках данной темы поднимается вопрос сложность программного обеспечения как основного вызова при разработке ПО, понятию ОО программирования, приводится пример использования объектного подхода.
"Если бы строители строили здания так же, как программисты пишут программы, первый залетевший дятел разрушил бы цивилизацию" (с) Второй закон Вейнберга
Одним из основных вызовов при разработке программного обеспечения является сложность программ. Этот вызов иногда называют «кризисом программного обеспечения».
Не всё программное обеспечение является сложным, существует большое количество «простых» программ, которые разрабатываются и поддерживаются одним человеком. Такие программы, как правило, имеют очень ограниченный функционал и используются в течение короткого периода времени. С написанием таких программ не возникает много проблем, поэтому для их написания вы можете использовать практически любые языки программирования, технологии и методы разработки программных продуктов.
Наибольшие проблемы возникают при разработке корпоративного программного обеспечения (enterprise software). Такие системы используются довольно долго (годами и десятилетиями), и от их корректной работы зависят тысячи и даже миллионы людей. Это могут быть, например, системы управления воздушным транспортом, железнодорожными перевозками, банковские системы, системы коммунальных платежей, онлайн-игры, популярные веб-сайты и веб-службы и так далее.
Важнейшей особенностью корпоративной программы является ее высокая сложность. Одному программисту не под силу решить все проблемы, связанные с проектированием такой системы. Грубо говоря, сложность промышленных программ превышает интеллектуальные возможности отдельного человека.
Некоторые специалисты считают что популярные операционные системы являются самой сложной рукотворной системой, которую когда-либо создавало человечество.
Со времени возникновения области разработки программного обеспечения, человечество накопило достаточно знаний, чтобы проектировать даже самые сложные системы программного обеспечения, но мы до сих пор сталкиваемся с огромным количеством проблем. В чем же дело?
При анализе сложных систем, мы обнаруживаем много составных частей, которые взаимодействуют друг с другом разными довольно запутанными способами, причем части и способы их взаимодействия могут быть совершенно разными. При проектировании и организации сложных систем разработчику необходимо думать сразу о многом. Например, система управления воздушным транспортом должны одновременно контролировать состояние многих самолетов, учитывая, например, их местоположение, скорость и курс. К сожалению, один человек не может охватить все эти детали одновременно.
Таким образом, у нас возникает проблема сложности – программное обеспечение становится всё более сложным, а способности справиться с этой сложностью остаются ограниченными. Как же решить эту проблему?
Одним из способов справиться со сложностью программных систем, это декомпозиция. При проектировании сложного программного обеспечения, необходимо разделять его на всё меньшие и меньшие части, каждую из которых можно обрабатывать независимо друг от друга. Таким образом, вместо работы над всей программной системой сразу, мы будем работать с ее отдельными частями.
Одним из методов декомпозиции является алгоритмическая декомпозиция (вы сталкивались с таким видом декомпозиции на первом курсе, когда изучали дисциплину «Алгоритмизация и программирование»). Алгоритмическая декомпозиция выполняется методом «сверху вниз», где каждый модуль системы выполняет один из этапов общего процесса. На рисунке 1.1 приведена часть программы, которая обновляет содержимое основного файла.
Другим видом декомпозиции называется объектно-ориентированная декомпозиция, которая вам пока неизвестна.
При использовании этого вида декомпозиции, вместо разделения системы на этапы, например, «Прочитать отформатированное обновление» и «Добавить контрольную сумму», мы определяем такие объекты, как «Основной файл» и «Контрольная сумма», которые создаются при анализе предметной области. На рисунке 1.2 приведен пример объектно-ориентированной декомпозиции для той же части программы.
В случае объектно-ориентированной декомпозиции, мир представляет собой совокупность автономных агентов, которые взаимодействуют друг с другом и обеспечивают более сложное поведение системы. Действие «Прочитать отформатированное обновление» больше не является независимым алгоритмом, это действие представляет собой операцию, связанную с объектом «Файл обновлений». В результате выполнения этой операции возникает другой объект – «Обновление карты». Таким образом, каждый объект в такой схеме реализует свое собственное поведение, и каждый из них моделирует некоторый объект реального мира.
С такой точки зрения объект является материальной сущностью, обладающей определенным поведением. Получая сообщения, объекты выполняют определенные операции. Такая композиция основана на объектах, поэтому и называется объектно-ориентированной.
Так какой же метод декомпозиции следует использовать? Использовать оба метода одновременно нельзя – сначала следует произвести декомпозицию либо по алгоритмам, либо по объектам.
Многолетний опыт разработчиков программного обеспечения явно показывает, что объектно-ориентированная декомпозиция имеет много чрезвычайно важных преимуществ над алгоритмической. Декомпозицию следует начинать с объектов, поскольку она облегчает упорядочение сложных систем, таких как программное обеспечение, компьютеры, растения, галактики и крупные общественные институты.
Преимущества объектно-ориентированной декомпозиции:
уменьшается размер систем за счет повторного использования общих механизмов;
объектно-ориентированные системы являются более гибкими и легче эволюционируют со временем;
снижается риск, возникающий при создании сложной программной системы;
объектно-ориентированная декомпозиция позволяет лучше справиться со сложностью, характерной для систем программного обеспечения.
Объектно-ориентированная технология основана на использовании так называемой объектной модели проектирования, или просто объектной модели. К основным принципам этой модели относятся абстракция, инкапсуляция, модульность, иерархия, контроль типов, параллелизм и персистентность.
Языки, которые реализуют объектную модель, называют объектными или объектно-ориентированными. Пример структуры программ, написанных на объектно-ориентированных языках программирования, представлен на рисунке 1.3.
Основным элементом в этих языках является модуль, который представляет собой логическую связанную совокупность объектов и классов (понятие класса будет рассмотрено ниже). Такая структура является графом, а не деревом, как в случае использования алгоритмических языков. Кроме того, в ОО языках исключены глобальные данные. Данные и операции объединяются таким образом, что основными логическими конструктивными элементами ОО систем теперь являются объекты и классы, а не алгоритмы.
Объектная модель допускает масштабирование. В крупных системах образуются целые кластеры, образующие слои. Пример структуры крупных систем приведен на рисунке 1.4.
Объектно-ориентированное программирование – это методология программирования, основанная на представлении программы в виде совокупности взаимодействующих объектов, каждый из которых является экземпляром определенного класса, а классы являются членами определенной иерархии наследования.
Объект – структура, которая объединяет данные и методы, которые эти данные обрабатывают. Фактически, объект является основным строительным блоком объектно-ориентированных программ.
Класс – шаблон для объектов. Каждый объект является экземпляром (instance) какого-либо класса («безклассовых» объектов не существует). В рамках класса задается общий шаблон, структура, на основании которой создаются объекты. Данные, относящиеся к классу, называются полями класса, а программный код для их обработки называется методами класса. Поля и методы иногда называют общим термином – члены класса.
Разница между классом и объектом такая же, как между абстрактным понятием и реальным объектом.
Объект состоит из следующих частей:
имя объекта;
состояние (переменные состояния). Данные, содержащиеся в объекте, представляют его состояние. В терминологии ООП эти данные называются атрибутами. Например, атрибутами работника могут быть: имя, фамилия, пол, дата рождения, номер телефона. В разных объектах атрибуты имеют разное значение. Фактически, в объектах определяются конкретные значения тех переменных (полей класса), которые были заявлены при описании класса;
методы (операции) – применяются для выполнения операций с данными, а также для совершения других действий. Методы определяют, как объект взаимодействует с окружающим миром.
Объекты могут отправлять друг другу сообщения. Сообщение (message) - это практически то же самое, что и вызов функции в обычном программировании. В ООП обычно употребляется выражение "послать сообщение" какому-либо объекту. Понятие "сообщение" в ООП можно объяснить с точки зрения ООП: мы не можем напрямую изменить состояние объекта и должны как бы послать сообщение объекту, что мы хотим как-то изменить его состояние. Очень важно понять, что объект сам меняет свое состояние, а мы можем только попросить его об этом с помощью отсылки сообщения.
В объектно-ориентированной программе весь код должен находиться внутри классов!
В классе описываются, какого типа данные относятся к классу, а также то, какие методы применяются к этим данным. Затем, в программе на основе того или иного класса создается экземпляр класса (объект), в котором указываются конкретные значения полей и выполняются необходимые действия над ними.
Давайте попробуем понять, что такое объект и класс с помощью языка программирования C.
Представим себе, что мы пишем программу для книжной лавки на языке C. В какой-то момент мы сталкиваемся с необходимостью хранить информацию о множестве книг: название книги, кто автор книги, год издания и стоимость книги. Как нам это запрограммировать?
Мы можем воспользоваться массивами и хранить данные о книгах в нескольких массивах.
Теперь мы можем обратиться к i-му номеру каждого массива для получения информации об i-ой книге.
Какими недостатками обладает данный способ работы с данными? Такой подход может приводить к многочисленным ошибкам (например, ошибки при работе с индексами массивов), такие данные тяжело модифицировать (удаление книги приводит к необходимости смещать влево часть элементов массивов), такой код неудобно читать, поддерживать и модифицировать.
Но самое главное - мы мыслим в контексте структуры компьютера, а не решаемой задачи. Для нас книга - это некий единый объект, который имеет некоторые параметры (атрибуты): название, количество страниц и так далее. Мы же представляем атрибуты этого объекта в виде отдельных записей в разных массивах, потому что на языке C мы вынуждены мыслить в терминах имеющихся структур данных (массивов, очередей, деревьев), а не в терминах отдельных объектов и их взаимоотношений. Это затрудняет понимание решаемой задачи (управление книжной лавкой и продажей книг), увеличивает количество ошибок в программе и на некотором этапе мы вообще перестаем понимать, что происходит в программе.
Для хотя бы какого-то решения этой проблемы и облегчения труда программиста, на некотором этапе в язык C было введено понятие структуры (ключевое слово struct
), которое вы должны были изучать в курсе "Алгоритмизация и программирование".
Структура в языке C - это тип данных, создаваемый программистом, предназначенный для объединения данных различных типов в единое целое. Таким образом, мы можем сгруппировать данные, которые относятся к одной книге, в одну структуру.
Сначала мы должны описать структуру (новый тип данных).
Таким образом, некоторое понятие реального мира (то есть, книга вообще, как понятие) в программе описан структурой Book
. Экземпляр этого понятия (какая-то конкретная книга) в программе будет представлен экземпляром структуры - переменной типа Book
. Мы объявляем экземпляр структуры, после чего мы сможет заполнить поля структуры и работать с ней дальше в программе.
Использование структур решает проблему лишь частично. Мы сгруппировали данные, но функции для работы с нашей структурой находятся вне структуры.
Объект реального мира обладает не только атрибутами (автор книги, название книги, количество страниц и так далее), но и определенным поведением (книгу можно продать, купить, переместить на склад и так далее). При разработке сложных программных систем необходимо группировать не только данные, но и функции, которые работают с этими данными.
Рассмотрим простой пример объектно-ориентированной программы. Представим, что мы программируем графический редактор, который может рисовать различные фигуры. Подумав над задачей, мы приходим к выводу, что в нашей программе должны быть объекты, которые представляют различные фигуры. Итак приступим.
Сначала мы должны создать классы и описать внутри этих классов данные, которые относятся к фигурам и поведение фигур в виде набора методов (функций).
Опишем класс для фигуры «Треугольник». Какие характеристики могут быть у класса «Треугольник»? От чего зависит набор характеристик? В принципе, любой объект является бесконечно сложным и для его описания понадобится бесконечно большое количество характеристик. Но для нашего простого графического редактора важными будут следующие параметры: координаты трех точек и цвет фигуры.
Здесь вы должны понять очень важную вещь: набор данных в классе зависит от той программы, которую мы собираемся написать. Например, если бы мы писали более мощный графический редактор, в списке полей класса мы добавили бы отдельно цвет заливки и цвет линий фигуры, сам цвет мог быть не только сплошным, но и в виде какого-то узора и так далее.
Для указания класса в Java используется ключевое слово class
, после чего идет название класса, далее мы ставим фигурные скобки и всё, что будет написано внутри фигурных скобок (переменные и функции) будет относиться к этому классу.
Какие переменные и типы данных будут моделировать координаты точек и цвет? Для моделирования цвета воспользуемся обычным целым числом (очень часто цвета моделируются обычным целочисленным значением).
Теперь попробуем подумать, а как нам смоделировать координаты трех точек? Если бы мы программировали на языке C, мы бы написали что-то вроде
Но давайте подумаем - координаты каждой точки логически связаны между собой и когда мы будем, например, перемещать треугольник или будем менять размер треугольника, будут одновременно меняться две переменные, которые моделируют одну точку. С точки зрения объектной модели, лучшим вариантом будет предусмотреть отдельную сущность, отдельный класс, который моделирует точку на двумерной плоскости. Таким образом, мы сгруппируем данные и код, который будет эти данные изменять (например, менять значение X и Y).
Теперь вернемся к классу Triangle
и укажем, что в качестве координат у нас будут выступать три объекта класса Point2D
.
Теперь давайте определимся с методами классов – то есть, к тому поведению, которое будут осуществлять объекты этих классов.
Какое поведение может быть у объекта класса «точка»? Совершенно точно это будет метод «изменить координаты». То есть, наша точка как отдельный объект может вести себя следующим образом – менять свои координаты. Давайте запрограммируем этот метод.
Теперь давайте подумаем, какое поведение будет у треугольника? Что с ним можно сделать? Ну, например, можно его перекрасить, можно его передвинуть в другое место на плоскости, можно его нарисовать на каком-то полотне и так далее. Давайте опишем некоторые из этих методов.
Итак, мы описали треугольник, но мы определили только понятие «треугольник», у нас нет их физически, код класса - это просто описание. Если вы создали java-проект, у вас, по умолчанию, будет присутствовать класс Main
и функция main()
. Пока что мы не будем объяснять, зачем нужен класс Main
, а обратим внимание на метод main()
. Этот метод является «точкой входа» в программу, с вызова этого метода начинается работа программы. Когда мы дойдем и выполним последнюю инструкцию внутри метода main()
, приложение завершится.
Создадим внутри метода main()
несколько объектов класса Triangle
. Создание объектов мы будем рассматривать подробно в следующих лекциях, на данный момент мы просто скажем, что для создания объекта используется ключевое словоnew
. После создания объекта, мы можем обращаться к объекту и вызывать методы у объектов (как мы вызывали функции в C).
Важно! Вы должны понять, что у разных объектов значения переменных будут разные!
То есть, где-то в оперативной памяти у нас создано два разных объекта. Внутри первого есть объект класса Point2D
, внутри этого объекта есть две переменные типа int
, у которых будут значения 140 и 180. Внутри же второго треугольника будет свой объект Point2D
, внутри которого будут совершенно другие две переменные типа int
, у которых будут значения 50 и 80. Это же будет касаться переменных color в двух разных объектах (рис.1.5).
Таким образом, когда мы описываем классы, мы заявляем, что объекты этих классов будут содержать определенные поля (набор переменных) и будут иметь набор методов (функций), которые можно вызвать у объекта. Каждый объект содержит свои переменные, но методы у них общие.
Ссылка на конспект лекции
При разработке языка Java был взят за основу синтаксис языков С и C++, поэтому, многие аспекты синтаксиса языка покажутся вам знакомыми.
В Java, как и в C, существуют однострочные и блоковые комментарии. Однако, кроме этого, согласно конвенции Oracle, существуют другие виды комментариев: copyright-блок вверху, doc-комментарии, TODO-комментарии, комментарии после statement`ов, комментарии для комментирования кода. Ознакомьтесь с принятыми правилами использования комментариев, на защите лабораторных они будут требоваться в обязательном порядке, согласно принятым конвенциям.
Переменные чувствительны к регистру (var и Var – две разные переменные), могут быть бесконечной длины, состоять из букв юникода и цифр юникода, символов _ и $.
Первый символ переменной может быть буквой, символом _ или $ (использовать _ или $ первым символом КРАЙНЕ НЕ РЕКОМЕНДУЕТСЯ, они существуют для специальных ситуаций, которые нас сейчас не интересуют, так что считайте, что начинаться переменная может только с буквы юникода). Крайне не рекомендуется использовать буквы национальных алфавитов, кириллицу, транслит. Только латинские буквы, названия на английском. Также, названия переменных не должны совпадать со списком зарезервированных слов
Приведенный ниже блок кода даст вам общее представление о том, как надо называть переменные
В языке Java существуют примитивные типы (аналогичны типам данных в C) и ссылочные (или объектные) типы данных. На данный момент нас интересуют только примитивные типы данных.
Java – строго типизированный язык программирования. Это значит, что переменная, перед использованием, должна быть объявлена и ей должен быть присвоен тип, который нельзя поменять. Также, при выполнении операций присваивания, компилятор проверяет соответствие типов (какого-то механизма автоматического приведения типов у Java нет).
Всего существуют восемь примитивных типов данных: int, long, short, byte, double, float, char, boolean. Их очень легко запомнить:
4 типа для целых чисел («короткое short», «среднее int», «длинное long» и байт);
2 типа для чисел с плавающей запятой (старая парочка double и float);
2 «специальных» типа – символ и булевый тип.
Операторы ветвления в C и Java практически идентичны
Работа с циклами в Java мало чем отличается от языка C
Работа с массивами в Java несколько отличается от работы с массивами в C, в основном, из-за механизма выделения памяти под массивы.
Так как Java является объектно-ориентированным языком, функции здесь называются методами (на данный момент мы будем считать, что методы и функции выполняют одну и ту же роль, но методы могут находиться только внутри классов).
abstract | continue | for | new | switch |
assert | default | goto | package | synchronized |
boolean | do | if | private | this |
break | double | implements | protected | throw |
byte | else | import | public | throws |
case | enum | instanceof | return | transient |
catch | extends | int | short | try |
char | final | interface | static | void |
class | finally | long | strictfp | volatile |
const | float | native | super | while |
Type | Min | Max | RAM | Default | Объявления и литералы |
byte | -128 | 127 | 8 bit | 0 | byte b = 100; |
short | -32,768 | 32,767 | 16 bit | 0 | short b = 10000; |
int | -2^31 | -2^31-1 | 32 bit | 0 | int a = 15; int aHex = 0xaa; int aBin = 0b0001111; (это же справедливо и для byte,short,long, если соблюдать диапазоны) |
long | -2^63 | -2^63-1 | 64 bit | 0L | long number = 10000L; |
double | 4.9^-324 | ~1.8^308 | 32 bit | 0.0d | double d = 6.6; |
float | ~1.4^-45 | ~3.4^38 | 64 bit | 0.0f | float f = 5.5f; |
char | 0 | 65535 | 16 bit | '\u0000' | char c = ‘f’; char c = 63; char c = '\u2422'; |
boolean | false | true | 1 bit | false | boolean b = true; |
Ссылка на конспект лекции
Ссылка на конспект лекции
Ссылка на конспект лекции
Ссылка на конспект лекции
Обычно при создании класса вы описываете, как объекты этого класса ведут себя и как они выглядят. Объект появляется только после того, как он будет создан ключевым словом new
, и только начиная с этого момента для него выделяется память и появляется возможность вызова методов.
Но есть ситуации, в которых такой подход недостаточен. Первая ситуация - это когда некоторые данные должны храниться "в единственном числе" независимо от того, сколько было создано объектов класса. Вторая - когда вам потребуется метод, не привязанный ни к какому конкретному объекту класса (то есть метод, который можно вызвать даже при полном отсутствии объектов класса).
Такой эффект достигается путем использования ключевого слова static
, делающего элемент класса статическим. Поля или методы, которые объявлены как static
, не привязаны к определенному экземпляру этого класса. Поэтому даже если в программе еще не создавался ни один объект класса, вы можете вызывать статический метод или получить доступ к статическим полям. С обычным объектом вам необходимо сначала создать его и использовать для вызова метода или доступа к полям, так как нестатические данные должны точно знать объект, с которым работают.
Некоторые ОО языки используют термин данные уровня класса и методы уровня класса, подразумевая, что данные и методы существуют только на уровне класса в целом, а не для отдельных объектов этого класса.
Чтобы сделать данные или метод статическими, просто поместите ключевое слово static
перед их определением. Например, следующий код создает статическое поле класса и инициализирует его:
Теперь, даже при создании двух объектов StaticTest
, для элемента StaticTest.val
выделяется единственный блок памяти. Оба объекта совместно используют одно значение val
. Пример:
В данном примере как st1.val
, так и st2.val
имеют одинаковые значения, равные 924, потому что они ссылаются на один блок памяти.
Существует два способа обратиться к статической переменной. Как было видно выше, на нее можно ссылаться по имени объекта, например st2.val
. Также возможно обратиться к ней прямо через имя класса; для нестатических членов класса такая возможность отсутствует.
После выполнения операции, значения st1.val
и st2.val
будут равны 48.
Синтаксис с именем класса является предпочтительным, потому что он не только подчеркивает, что переменная является статической, но и в некоторых случаях предоставляет компилятору больше возможностей для оптимизации.
Если для инициализации статических переменных требуется произвести вычисления, то для этой цели достаточно объявить статический блок, который будет выполняться только один раз при первой загрузке класса. Пример:
Как только загружается класс NetworkConnector
, выполняются все действия в статическом блоке.
Та же логика верна и для статических методов. Вы можете обратиться к такому методу или через объект, как это делается для всех методов, или в специальном синтаксисе ИмяКласса.метод(). Статические методы определяются по аналогии со статическими данными:
Нетрудно заметить, что метод increment()
класса Incrementable
увеличивает значение статического поля val
. Метод можно вызвать стандартно, через объект:
Или, поскольку метод increment() является статическим, можно вызвать его с прямым указанием класса:
Если применительно к полям ключевое слово static
радикально меняет способ определения данных (статические данные существуют на уровне класса, в то время как нестатические данные существуют на уровне объектов), то в отношении методов изменения не столь принципиальны. Одним из важных применений static
является определение методов, которые могут вызываться без объектов.
На статические методы налагаются следующие ограничения:
они могут непосредственно вызывать только другие статические методы;
им непосредственно доступны только статические переменные;
им недоступны ключевые слова this или super.
В языке Java имеется языковое средство, расширяющее возможности ключевого слова import
и называемое статическим импортом. Оператор import
, предваряемый ключевым словом static
, можно применять для импорта статических членов класса или интерфейса. Благодаря статическому импорту появляется возможность ссылаться на статические члены непосредственно по именам, не используя имя класса. Это упрощает и сокращает синтаксис, требующийся для работы со статическими членами.
Рассмотрим пример без использования статического импорта. Представим себе, что мы вычисляем гипотенузу прямоугольного треугольника. Мы будем часто использовать методы Math.pow()
и Math.sqrt()
Как мы видим, строка
получается слишком громоздкой. Подобных неудобств можно избежать, если воспользоваться статическим импортом. Рассмотрим пример ниже
После использования статического импорта нет нужды использовать имя класс Math
для вызова статических методов pow()
и sqrt()
.
Если предполагается применять много статических методов или полей, определенных в классе, то можно импортировать класс Math
полностью
Каким бы удобным ни был статический импорт, очень важно не злоупотреблять им. Не следует забывать, что библиотечные классы Java объединяются в пакеты для того, чтобы избежать конфликтов пространств имен и непреднамеренного сокрытия прочих имен. Если статический член используется в программе один или два раза, то его лучше не импортировать. Статический импорт следует оставить на тот случай, если статический члены применяются многократно, в частности при выполнении математических вычислений.
В Java определены вложенные классы. Вложенным называется такой класс, который объявляется в другом классе.
Вложенный класс не может существовать независимо от класса, в который он вложен. Следовательно, область действия вложенного класса ограничена его внешним классом. Если вложенный класс объявлен в пределах области действия внешнего класса, то он становится членом последнего. Имеется также возможность объявить вложенный класс, который станет локальным в пределах блока
Существуют два типа вложенных классов. Одни вложенные классы объявляются с помощью модификатора доступа static, а другие - без него. Нестатический вариант вложенных классов называются внутренними.
Внутренние классы позволяют вам группировать классы, логически принадлежащие друг другу, и управлять доступом к ним. Однако важно понимать, что внутренние классы заметно отличаются от композиции.
Внутренний класс может взаимодействовать со своим внешним классом, а код, написанный с использованием внутренних классов, получается более элегантным и понятным.
Создать внутренний класс несложно - достаточно разместить определение класса внутри окружающего класса.
Внутренний класс имеет доступ ко всем переменным и методам внешнего класса, в который он вложен, и может обращаться к ним непосредственно, как и все остальные нестатические члены внешнего класса.
Иногда внутренний класс используется для предоставления ряда услуг внешнему классу, в котором он содержится.
Класс можно вложить в область действия блока. В итоге получается локальный класс, недоступный за пределами блока.
Также внутренний класс можно быть анонимным классом (анонимные классы уже были рассмотрены ранее).
Механизм перечислений был добавлен в JDK 5 версии, он позволяет удобно хранить и работать с так называемыми "категориальными данными".
Категориальные данные - это данные с ограниченным числом уникальных значений или категорий. Приведем примеры категориальных данных:
месяцы года (12 значений: январь, февраль и так далее);
пол (мужской, женский, андроген, бесполый и так далее);
вероисповедание (православие, католицизм, ислам и так далее);
день недели (понедельник, вторник, среда и так далее).
Рассмотрим пример. Для информационной системы «Электронный институт» необходимо создать класс сотрудника кафедры «DepartmentMember», в котором, среди прочих атрибутов, есть атрибут «должность» (position). С целью упрощения, предположим, что сотрудники кафедры могут занимать одну из следующих должностей:
«Инженер» (Engineer);
«Ассистент» (Assistant);
«Старший преподаватель» (Lecturer);
«Доцент» (Senior Lecturer);
«Профессор» (Professor).
Каким образом закодировать данное значение? Самый первый и очевидный вариант - просто использовать тип данных String.
Данное решение имеет несколько очень серьезных недостатков. Вы никак не можете обеспечить корректность указания должностей и контролировать создание нового объекта класса.
Должность старшего преподавателя, например, может быть записана как "ст. преп.", "ст. преподаватель", "ст. пр." и так далее, не говоря про возможные орфографические ошибки. С точки зрения формальной логики, это все разные категории должностей и вы не сможете группировать или фильтровать сотрудников кафедры по полю "должность".
Другой вариант - это создать несколько статических целочисленных констант, которые можно использовать вместо строк.
Теперь мы сможем использовать именованные константы вместо строго фиксированных строк.
Использование именованных констант позволяет отчасти избавиться от недостатков предыдущего подхода, но и этот вариант не является приемлемым. Ничто не мешает пользователю класса при создании объекта указать любое целое числа вместо именованной константы, осуществлять проверку ввода – достаточно трудоемкая задача, если в классе предусмотрено более одного категориального значения будет тяжело определять какие конкретно именованные константы необходимо использовать. К тому же, при использовании конструктора или сеттера поля «должность», нет никакого указания на необходимость использования именованных констант, программист сам должен догадаться о наличии нужных констант или читать сопроводительную документацию.
Подход с использованием именованных констант напоминает улучшенную версию использования механизма специальных кодов и его не следует использовать при разработке коммерческих программ.
Корректным вариантом решения данной проблемы является использование специального механизма перечислений (enumeration). По сути, перечисление – это тип, ссылочная переменная которого может принимать одно из нескольких заранее определенных значений. Реализуем класс «DepartmentMember» с помощью механизма перечислений:
Преимущества такого подхода очевидны: поле position может принять только один из заранее определенных значений, которые указаны при создании перечислений. При вызове конструктора или сеттера поля position, пользователю класса сразу будет понятно, что необходимо передать в качестве аргумента метода.
Обратите внимание на синтаксис перечисления. Перечисление является классом, но очень своеобразным. Вместо ключевого слова class используется ключевое слово enum (от слова enumeration – перечисление). Вместо полей и методов у перечисления идет просто набор констант через запятую.
Как уже было сказано, перечисление – это класс, а это значит, что он может иметь методы, и мы можем инкапсулировать перечисление и операции работы с ним в одной оболочке.
В данном примере, экземпляр перечисления хранит строку с цветом.
В Spring имеется набор модулей для интеграции с различными технологиями хранения данных. Spring позволяет избавить разработчика от рутины при разработке программного кода, реализующего доступ к данным. Вместо возни с низкоуровневым доступом к данным можно положиться на Spring, который выполнит эту работу за вас, и сконцентрироваться на управлении данными в самом приложении.
Что такое JDBC, драйвер, JPA, ORM и как это все между собой соотносится?
Как правило, каждая система управления базами данных (MySQL, PostgreSQL и так далее) имеет свой протокол взаимодействия с клиентами. Чтобы работать с базой данных, клиент должен соблюдать протокол взаимодействия с базой данных.
Чтобы программист не тратил время на самостоятельную реализацию протокола при разработке очередного приложения, разработчик сервера баз данных сам предоставляет всем желающим программный код, который общается с базой данных на понятном этой базе протоколе. Такой программный код и называется драйвером базы данных. Драйвер реализует протокол общения с БД и предоставляет API, которое позволяет нам общаться с базой данных, не вдаваясь в детали реализации протокола.
Как раз для этого разработчики Java предоставили стандарт JDBC (Java DataBase Connectivity) – специальное API, которое используется приложениями Java для взаимодействия с базой данных. Стандарт JDBC позволяет отправлять запросы к базе данных для выполнения операций выбора, вставки, обновления и удаления.
Если разработчики СУБД хотят, чтобы их база данных использовалась Java-разработчиками, они предоставляют JDBC-драйвер для их базы данных. Разработчики Java подключат драйвер и используют его для общения с той или иной базой данных. Если, в какой-то момент, разработчики захотят сменить СУБД, они просто меняют драйвер старой базы на драйвер новой. Благодаря стандарту JDBC, ничего менять в коде работы с базой данных не требуется.
Что такое и зачем нужна технология ORM?
При написании объектно-ориентированного кода, который взаимодействует с базой данных, у разработчика возникает несколько проблем:
данные в программе и в базе данных используют разные парадигмы (объектно-ориентированная и реляционная соответственно). Работу по преобразованию данных из одной парадигмы в другую ложатся на плечи программиста, что влечет за собой лишнюю работу и может приводить к ошибкам в процессе преобразования;
программисту желательно абстрагироваться от конкретной схемы хранения данных. То есть, программисту желательно работать не с реляционной базой данных, а просто с некоторым «хранилищем», а конкретная реализация этого «хранилища» может быстро и безболезненно меняться.
Для устранения этих проблем используется технология ORM (Object-Relational Mapping, «объектно-реляционное отображение») — технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования, создавая «виртуальную объектную базу данных».
Проще говоря, ORM – это прослойка, посредник между базой данных и объектным кодом. Используя ORM, программист не занимается формированием SQL-запросов и не думает в терминах «таблица», «записи» и «реляционные отношения», а просто работает с «хранилищем объектов» – он может туда записывать и получать объекты, не заботясь о подробностях их хранения.
В Java предусмотрен специальный стандарт JPA (Java Persistence API), который использует концепцию ORM. Существует несколько реализаций этого интерфейса, например, Hibernate, OpenJPA, EclipseLink и другие.
Spring Data JPA – обертка над JPA в Spring, которая предоставляет много полезных «фишек» разработчику. Она позволяет легче создавать Spring-управляемые приложения, которые используют новые способы доступа к данным, например нереляционные базы данных, map-reduce фреймворки, cloud сервисы, а так же уже хорошо улучшенную поддержку реляционных баз данных.
Терминология JPA
Основное понятие JPA – сущность (Entity). Сущность – это Java-класс, который представляет бизнес-логику приложения и определяет данные, которые будут храниться в базе данных и извлекаться из нее.
Как правило, класс сущности представляет таблицу в базе данных, поля или свойства класса представляют собой колонки в таблице, а объект сущности представляет собой одну запись в таблице.
Важным моментом при работе с JPA являются аннотации, коих здесь будет очень много. Разберемся с некоторыми из них:
@Entity – позволяет серверу узнать, что это не просто какой-то класс, а сущность;
@Id – помечает первичный ключ в таблице. Вопрос составных ключей в данном занятии не рассматривается;
@Table – позволяет настраивать отображение класса в таблицу. В данном случае, мы можем указать, какое имя будет иметь соответствующая таблица в базе данных;
@GeneratedValue – указывает, что данное поле является генерируемым значением. Очень часто этой аннотацией помечают первичные ключи, чтобы они генерировались автоматически при добавлении новых записей в таблицу;
@Column – позволяет настраивать отображение колонки в таблице. В данном случае, мы можем указать, какое имя будет иметь соответствующая колонка в таблицу.
Репозитории. Главными компонентами для взаимодействий с БД в Spring Data являются репозитории. Каждый репозиторий работает со своим классом-сущностью.
В большинстве случаев, структура запросов к репозиторию будет одинаковая: «получить все записи», «получить записи, где столбец равен определенному значению» и так далее.
Spring Data JPA позволяет вам избежать рутинного создания запросов. Для этого вместо класса создадим интерфейс, который будет наследоваться от стандартного generic-интерфейса. Первый параметр означает тип класса-сущности, второй параметр – тип первичного ключа.
Установим СУБД Postgres и запустим pgAdmin 4.
Создадим пользователя ejournal_user, после чего создадим базу данных для нашего приложения.
Добавляем в pom.xml зависимости для работы с Spring Data JPA и JDBC драйвер для Postgres.
Далее необходимо настроить подключение к СУБД и нужной базе данных.
Для настройки приложения Spring воспользуемся языком YAML. Для этого удалим файл resources/application.properties и создадим вместо него файл application.yml.
Создадим класс сущности Student
Для уменьшения количества кода, мы будем использовать плагин Lombok.
Проект Lombok — это плагин компилятора, который добавляет в Java новые «ключевые слова» и превращает аннотации в Java-код, уменьшая усилия на разработку и обеспечивая некоторую дополнительную функциональность.
Lombok преобразует аннотации в исходном коде в Java-операторы до того, как компилятор их обработает: зависимость lombok
отсутствует в рантайме, поэтому использование плагина не увеличит размер сборки.
При использовании Lombok наш исходный код не будет валидным кодом Java. Поэтому потребуется установить плагин для IDE, иначе среда разработки не поймёт, с чем имеет дело. Lombok поддерживает все основные Java IDE. Интеграция бесшовная. Все функции вроде «показать использования» и «перейти к реализации» продолжают работать как и раньше, перемещая вас к соответствующему полю/классу.
Далее подключим библиотеку в pom.xml.
Вернемся в класс Student, добавим аннотацию для геттеров, сеттеров, а также конструктор со всеми параметрами.
Service – это Java класс, который содержит в себе основную бизнес-логику. В основном сервис использует готовые DAO/Repositories или же другие сервисы, для того чтобы предоставить конечные данные для пользовательского интерфейса. Сервисы, как правило, вызываются контроллерами или другими сервисами.
Объект службы создается контейнером Spring, каждая служба является «одиночкой» (синглтоном), который создается в момент запуска приложения и уничтожается в момент закрытия приложения. Обратите внимание на аннотацию @Service. Этой аннотацией мы сообщаем контейнеру Spring, что это не просто класс, а класс сервиса.
Итак, мы создали службу, у которой есть два публичных метода. Первый метод добавляет нового студента, второй метод возвращает список всех студентов. В дальнейшем служба будет обращаться к объекту репозитория за данными, а пока что оставим код таким, какой он есть.
Вернемся к созданию веб-слоя. Создадим класс контроллера, создадим две конечные точки: для добавления студента и для получения списка всех студентов.
Обратите внимание, что мы не создаем объект службы, а получаем его «извне» с помощью аннотации @Autowired. Контейнер Spring «внедрит» ссылку на объект службы в поле service. Подробнее про внедрение зависимостей будет изложено в следующем занятии.
Главными компонентами для взаимодействий с БД в Spring Data являются репозитории. Каждый репозиторий работает со своим классом-сущностью.
В большинстве случаев, структура запросов к репозиторию будет одинаковая: «получить все записи», «получить записи, где столбец равен определенному значению» и так далее.
Spring Data JPA позволяет вам избежать рутинного создания запросов. Для этого вместо класса создадим интерфейс, который будет наследоваться от стандартного generic-интерфейса. Первый параметр означает тип класса-сущности, второй параметр – тип первичного ключа.
Теперь перейдем в класс службы и создадим ссылку на объект репозитория.
Обратите внимание, что мы не создавали класс, который реализует интерфейс StudentRepository, тогда откуда мы его получим объект интерфейсного типа? Дело в том, что Spring сгенерирует класс за нас. Этот сгенерированный класс будет иметь набор стандартных операций для работы с сущностями. В нашем случае, это операция findAll(), которая возвращает все сущности в таблице student.
Запустим сервер и выполним два клиентских запроса - один на создание студента, второй - на получение списка всех студентов.
Добавляем нового студента
Теперь получим список всех студентов.
Как мы знаем, важной составляющей реляционных баз данных является отношения между таблицами "один-к-одному", "один-ко-многим", "многие-ко-многим".
Реализуем отношение "один-ко-многим". Создадим сущность Group - студенческая группа. В студенческой группе может быть от 0 до N студентов.
Прежде всего перейдем в сущность Student. Добавим поле group, который будет ссылаться на студенческую группу, в которой будет состоять студент. Так как в группе может быть много студентов, указываем аннотацию @ManyToOne. Также указываем аннотацию @JoinColumn, которая указывает на имя колонки, которая будет содержать Foreign Key.
Технология ORM позволяет создавать двусторонние связи между таблицами. В этом случае, при выдаче JSON, может возникнуть бесконечный цикл. Чтобы его избежать, укажем аннотацию @JsonIgnore. В этом случае, колонка group будет проигнорирована в процессе сериализации\десериализации.
Далее создадим сущность Group.
Обратите внимание, что отношение один-ко-многим мы моделируем с помощью обычной коллекции. Указываем аннотацию @OneToMany, также в свойстве mappedBy указываем, какое поле "держит" отношение со стороны студента.
Далее модифицируем класс контроллера. Создадим конечные точки для добавления новой группы, а также для получения списка всех групп. Также модифицируем конечную точку для добавления студента, чтобы указать id группы, в которую необходимо добавить студента.
Теперь создадим репозиторий для сущности Group.
Далее модифицируем класс сервиса. Добавим методы для добавления новой группы, а также для получения списка всех групп. Также модифицируем метод добавления новой группы. Метод работает следующим образом: получаем объект группы по id, после чего добавляем ссылку на группу в поле group объекта Student.
Запустим приложение и проверим его работу. Сначала добавим группу, после чего получим список групп.
Добавим новую группу
Получим список групп
Теперь добавим нового студента
Получим список всех групп
Комментарии - стр. 35
Метод format - стр. 36 - 37
Отступы - стр. 42 - 43
Оператор if - стр. 51 - 54
Цикл for - стр. 55 - 57
Функции - стр. 59 - 60
Списки - стр. 79 - 81
Последовательности - стр. 85 - 87
Материал по "срезам" (slices, используйте при работе со списками, для работы с частями списка):
Конспекты с краткой информацией:
В данной лабораторной работе рассматривается принцип инкапсуляции и механизм перегрузки методов.
Инкапсуляция в Java реализована с помощью использования модификаторов доступа.
Язык Java предоставляет несколько уровней защиты, которые позволяет настраивать область видимости данных и методов. В Java имеется четыре категории видимости элементов класса:
private
– члены класса доступны только членам данного класса. Всё что объявлено private
, доступно только конструкторам и методам внутри класса и нигде больше. Они выполняют служебную или вспомогательную роль в пределах класса и их функциональность не предназначена для внешнего пользования. Закрытие (private
) полей обеспечивает инкапсуляцию;
по умолчанию (package-private) – члены класса доступны классам, которые находятся в этом же пакете;
protected
– члены класса доступны классам, находящимся в том же пакете, и подклассам – в других пакетах;
public
– члены класса доступны для всех классов в этом и других пакетах.
Модификатор класса указывается перед остальной частью описания типа отдельного члена класса. Это означает, что именно с него должен начинаться оператор объявления класса.
Когда член класса обозначается модификатором доступа public
, он становится доступным для любого другого кода в программе, включая и методы, определенные в других классах.
Когда член класса обозначается модификатором private
, он может быть доступен только другим членам этого класса. Следовательно, методы из других классов не имеют доступа к закрытому члену класса.
При отсутствии модификатора доступа, члены класса доступны другим членам класса, который находится в этом же пакете.
Модификатор доступа protected
связан с использованием механизма наследования и будет рассмотрен позже.
Модификатор доступа указывается перед остальной частью описания типа отдельного члена класса (то есть, именно с модификатора доступа начинается объявление члена класса).
Член класса (переменная, конструктор, методы), объявленный public
, доступен из любого метода вне класса.
Всё что объявлено private
, доступно только конструкторам и методам внутри класса и нигде больше. Они выполняют служебную или вспомогательную роль в пределах класса и их функциональность не предназначена для внешнего пользования. Закрытие (private
) полей обеспечивает инкапсуляцию.
В подавляющем большинстве случаев, поля класса объявляются как private
(это не касается статических переменных и констант, там ситуация может быть другая). Должны быть веские основания объявить поле класса общедоступным. Манипулирование данными должно осуществляться только с помощью методов.
Для того чтобы дать возможность получить доступ к переменной или дать возможность изменить ее значение, объявляют специальные методы, которые называются "геттерами" и "сеттерами".
Геттер возвращает значение приватного поля, тогда как сеттер меняет значение приватного поля (новое значение передается в качестве аргумента метода).
Хотя сигнатура и имена геттеров и сеттеров могут быть любыми, приучите себя соблюдать строгий шаблон для объявления геттеров и сеттеров.
Геттер должен иметь префикс get
, после которого идет название поля с большой буквы. Геттер, как правило, не имеет входных аргументов.
Сеттер должен иметь префикс set
, после которого идет название поля с большой буквы. Сеттер принимает на вход новое значение поля. Возвращаемый тип, как правило, void
.
Большинство IDE для Java имеют механизм для генерации геттеров и сеттеров. В IntelliJ IDEA нажмите комбинацию Alt+Insert
находясь в окне редактирования java-файла. Откроется контекстное меню Generate, где вы можете выбрать генерацию геттера и сеттера, после чего указать поля, для которых необходимо сгенерировать методы.
Представим, что нам необходимо создать класс «Корзина» (Cart
), который хранит в себе набор объектов класса «Товар» (Item
).
Какие методы «Корзина» должна предоставлять для внешнего использования? Это могут быть, например, методы «Добавить товар», «Убрать последний добавленный товар», «Подсчет суммы цен товаров в корзине», «Повышение цен в корзине на N процентов» и «Снижение цен в корзине на N процентов».
Как вы можете заметить, это публичные методы, а значит, их можно вызвать через оператор-точку имея ссылку ну объект.
Перечень этих публичных методов и составляет интерфейс класса – то есть, с помощью этих методов объект класса будет взаимодействовать с внешним миром.
Эти методы имеют вполне четко определенные входные аргументы и могут возвращать значения четко определенных типов, и никак иначе. По аналогии с этим, поворот колес автомобиля осуществляется четко определенным образом – поворотом руля, и бензин надо заливать в четко определенное отверстие крышки бензобака, а не как-то еще.
То – как будет реализовано хранение товаров в корзине – это внутренняя логика класса и она не должна быть доступна внешнему миру, она должна быть скрыта от внешнего вмешательства. Другие классы, которые будут использовать объекты класса Cart
не должны знать и не должны иметь доступ к тому – как там «внутри» реализовано хранение товаров, подсчет цен и изменение цены на определенный процент и так далее, они могут только лишь использовать предоставленные им публичные методы. Давайте реализуем «Корзину» с помощью структуры «стек», которая, в свою очередь, реализована обычным массивом.
Как мы видим, массив с товарами, указать на вершину стек объявлены как private
члены класса. Это значит, что мы не можем получить к ним доступ извне – они доступны только внутри данного класса.
Программиста, который будет использовать класс Cart
, не должна волновать ситуация с переполнением стека, с попыткой извлечь элемент из пустого стека, он не должен следить за указателем на вершину стека, он даже не должен знать что это стек.
Для него объект класса Cart
это некоторый объект, который предоставляет «услугу» в виде корзины товаров и с этой корзиной можно работать с помощью определенных публичных методов.
В дальнейшем мы можем переделать класс Cart
и поменять внутреннюю реализацию. Мы можем использовать структуру "очередь", мы можем использовать коллекции, мы можем иначе реализовать операции добавления и удаления элемента в стеке, но если мы сохраним интерфейс класса неизменным, то для внешнего мира эти изменения внутренней логики не будут важны и если мы поменяем внутреннюю логику одного небольшого участка программы, то вся остальная программа будет работать так же.
В Java разрешается в одном и том же классе определять два или более метода с одинаковым именем, если только объявления их параметров отличаются. В этом случае методы называются перегружаемыми, а сам процесс – перегрузкой метода (method overloading).
Если у методов одинаковые имена, как Java узнает, какой именно из них вызывается? Ответ прост: перегружаемые методы должны отличаться по типу и/или количеству входных параметров. Даже разного порядка аргументов достаточно для того, чтобы методы считались разными (хотя это не рекомендуется).
Перегрузка по возвращаемым значениям
Логично спросить, почему при перегрузке используются только имена классов и списки аргументов? Почему не идентифицировать методы по их возвращаемым значениям?
Идентифицировать их нельзя, потому что Java в этом случае не может определить, какая версия метода должна выполняться.
При вызове перегружаемого метода для определения нужного варианта в Java используется тип и\или количество аргументов метода. Следовательно, перегружаемые методы должны отличаться по типу и\или количеству их параметров. Возвращаемые типы перегружаемых методов могут отличаться, но самого возвращаемого метода недостаточно, чтобы отличить два разных варианта метода. Когда в исполняющей среде Java встречается вызов перегружаемого метода, в ней просто выполняется тот вариант, параметры которого соответствуют аргументам, указанным в вызове.
Перегрузка методов позволяет поддерживать принцип «один интерфейс, несколько методов».
В языках программирования без перегрузки методов, каждому методу должно быть присвоено однозначное имя. Но зачастую требуется реализовать, по существу, один и тот же метод для разных типов данных.
В таком случае, в языках программирования без перегрузки реализуют несколько методов, которые немного отличаются названиями.
Перегрузка методов ценна тем, что позволяет обращаться к похожим методам по общему имени. Следовательно, имя представляет общее действие, которое должно выполняться. Выбор подходящего варианта метода для конкретной ситуации входит в обязанности компилятора.
Ничто не запрещает вам реализовать несколько перегруженных методов, каждый из которых будет работать совершенно по-разному. Но на практике крайне рекомендуется, чтобы перегруженные методы реализовывали одну и ту же общую операцию.
Перегрузка конструкторов
Наряду с перегрузкой обычных методов можно также выполнять перегрузку конструкторов. Перегружаемые конструкторы – это норма и часто используемый прием.
Соответствующий перегружаемый конструктор вызывается в зависимости от параметров, указываемых при выполнении оператора new.
Если вы пишете для класса несколько конструкторов, иногда бывает удобно вызвать один конструктор из другого, чтобы избежать дублирования кода. Такая операция проводится с использованием ключевого слова this.
Название метода
Описание
public Cart(int capacity)
Конструктор с 1 параметром – максимальным количеством товаров в корзине.
public boolean addItem(Item item)
Добавление товара в корзину. Возвращает успешность операции.
public Item deleteLastAddedItem()
Удаление последнего добавленного товара в корзину. Возвращает удаленный товар.
public double calculateItemPrices()
Подсчет суммы цен всех товаров в корзине.
public void raiseItemPrices(double percent)
Поднять цены товаров в корзине на определенный процент (значение процента передается как аргумент метода).
public void cutItemPrices(double percent)
Снизить цены товаров в корзине на определенный процент (значение процента передается как аргумент метода).
При выполнении задания продумайте следующие аспекты:
имена полей и типы данных;
как реализовать "уникальность" номера зачетной книжки и номера группы в рамках приложения;
модификаторы доступа для полей и методов;
тип методов, которые требуется реализовать;
если надо, реализуйте приватные методы и добавьте приватные поля, которые должны обеспечивать работу публичных методов.
Создайте публичный класс Student
– студента некоторой специальности некоторого университета. Класс не хранит явным образом информацию о специальности, номере группы\потока, предметах, университете.
В классе должны быть следующие поля:
имя;
фамилия;
год поступления;
уникальный шестизначный номер зачетной книжки.
принимает на вход имя и фамилию. При использовании данного конструктора, в номер зачетной книжки записывается 0;
принимает на вход имя, фамилию, номер зачетной книжки.
метод возвращает имя студента;
метод изменяет имя студента;
метод возвращает фамилию студента;
метод изменяет фамилию студента;
метод возвращает номер зачетной книжки;
метод изменяет номер зачетной книжки;
метод возвращает год поступления;
метод изменяет год поступления.
Создайте публичный класс Group
– студенческой группы. Класс не хранит явным образом специальность и название университета.
В классе должны быть следующие поля:
уникальный номер (в пределах специальности);
массив студентов.
принимает на вход номер группы (в этом случае, количество студентов записывается как 0);
принимает на вход номер группы, количество студентов (инициализация массива, но его элементы остаются пустыми);
принимает на вход массив студентов.
метод возвращает номер группы;
метод изменяет номер группы;
метод возвращает общее число студентов группы;
метод возвращает ссылку на студента по номеру зачетной книжки;
метод удаляет студента из группы по номеру зачетной книжки (помните про корректное удаление элемента из массива);
метод добавляет нового студента в группу (принимает на вход ссылку на объект Student, если массив уже полностью заполнен - реализуйте расширение массива);
метод возвращает массив студентов;
метод возвращает массив студентов, отсортированный по фамилиям (подумайте, как это сделать; если фамилии одинаковы - сортируйте по имени; если имена одинаковы - по номеру зачетной книжки).
В классе Main
напишите код, чтобы протестировать функциональность созданных классов и реализованных методов
Класс Group
содержит внутри себя массив студентов. Методы класса Group
требуют увеличения или уменьшения размера массива, вставки элемента внутри массива, вывод данных массива в отсортированном порядке.
Итого, есть данные и операции над данными (вставка, удаление, изменение размера, вывод в отсортированном порядке).
Используя механизм инкапсуляции и принципы ООП, модифицируйте класс Group
так, чтобы программа больше соответствовала принципам ООП.
Зачем необходимо повторное использование кода?
Что такое отношение наследования?
Как реализовано наследование в Java?
Расскажите, что такое цепочка вызовов конструкторов?
Зачем необходимо ключевое слово super
?
В чем заключается механизм переопределения методов? Как он реализуется?
Что такое класс Object
и метод toString()
?
В чем заключается отличие композиции от наследования?
Ссылка на репозиторий с проектом - https://github.com/MykolaHodovychenko/Lab4
Дан класс Point
, который моделирует точку в двумерном пространстве. Класс включает в себя следующие конструкторы и публичные методы:
Создайте класс Point3D
, который расширяет класс Point через наследование. Он должен вести себя как Point
, за исключением того что это должна быть точка в трехмерном пространстве, которая хранит значение координаты Z
.
Вы должны предоставить те же методы, что и суперкласс, а также реализовать дополнительное поведение
Класс Point3D()
должен переопределить требуемые методы, чтобы они работали корректно с учетом третьей координаты. Также класс Point3D
должен вести себя иначе в следующих ситуациях:
при вызове метода setLocation(int x, int y)
, координата z
должна быть выставлена в 0;
при вызове метода toString()
, строка должна выводить три координаты, а не две;
Дан класс GroceryBill
, который моделирует чек и хранит список товаров, который покупает человек в супермаркете. Класс включает в себя следующие конструкторы и публичные методы:
Объект GroceryBill
взаимодействует с объектами класса Item
. Класс Item
включает следующие публичные методы:
К примеру, товар стоит 1.35, а размер скидки 0.25 для постоянных покупателей. Это означает, что постоянный покупатель должен заплатить 1.10. Некоторые товары могут не иметь скидки (размер скидки 0.0). В классе GroceryBill
не предусмотрена логика для учета скидки, то есть учитывается только полная стоимость товара.
Разработайте класс DiscountBill
, который расширяет класс GroceryBill
и добавляет логику для учета скидки для постоянных клиентов. Конструктор класс DiscountBill
должен принимать на вход параметр, который указывает, является ли клиент постоянным.
Класс DiscountBill
должен реализовывать собственную логику метода getTotal()
для постоянных покупателей. Например, если полная сумма равна 80 гривен, а скидка для постоянного клиента составила 20 гривен, метод должен возвращать 60 гривен.
Также, вам необходимо отслеживать количество товаров со скидкой (у которых размер скидки больше 0.0), а также общую скидку, как в гривнах, так и в процентах от суммы в чеке (то есть, насколько в процентах постоянный покупатель заплатил меньше, чем если бы скидки не было).
Помимо переопределенных методов, класс DiscountBill
должен иметь следующие конструкторы и публичные методы:
Если покупатель не является регулярным, класс DiscountBill
должен вести себя как будто общая скидка равна 0 и все товары учтены по их полной стоимости.
метод distanceFromOrigin()
должны учитывать координату z и возвращать расстояние по формуле .
Сигнатура
Описание
public Point()
Создает точку с координатами (0, 0)
public Point(int x, int y)
Создает точку с координатами (x, y)
public void setLocation(int x, int y)
Устанавливает новые координаты точки
public int getX()
Возвращает значение координаты X
public int getY()
Возвращает значение координаты Y
public String toString()
Возвращает строку в виде "(x,y)"
public int distanceFromOrigin()
Возвращает расстояние от начала координат (0, 0) до точки по формуле расстояния Евклида
Сигнатура
Описание
public Point3D()
Создает точку с координатами (0, 0, 0)
public Point3D(int x, int y, int z)
Создает точку с координатами (x, y, z)
public void setLocation(int x, int y, int z)
Устанавливает новые координаты
public int getZ()
Возвращает координату Z
Сигнатура
Описание
public GroceryBill(Employee clerk)
Создает объект GroceryBill
для данного clerk
public void add(Item i)
Добавляет товар в чек
public double getTotal()
Возвращает итоговую стоимость товаров
public void printReceipt()
Распечатывает список товаров
Сигнатура
Описание
public double getPrice()
Возвращает стоимость товара
public double getDiscount()
Возвращает скидку для этого товара
Сигнатура
Описание
public DiscountBill
(Employee clerk, boolean regularCustomer)
Создает объект DiscountBill
для данного clerk
public int getDiscountCount()
Возвращает количество товаров со скидкой
public double getDiscountAmount()
Возвращает общую скидку в гривнах
public double getDiscountPercent()
Возвращает процент скидки для товаров (на сколько процентов покупатель заплатил меньше)
При выполнении задания продумайте следующие аспекты:
имена полей и типы данных;
как реализовать "уникальность" номера зачетной книжки и номера группы в рамках приложения;
модификаторы доступа для полей и методов;
тип методов, которые требуется реализовать;
если надо, реализуйте приватные методы и добавьте приватные поля, которые должны обеспечивать работу публичных методов.
Создайте публичный класс Student
– студента некоторой специальности некоторого университета. Класс не хранит явным образом информацию о специальности, номере группы\потока, предметах, университете.
В классе должны быть следующие поля:
имя;
фамилия;
год поступления;
уникальный шестизначный номер зачетной книжки.
принимает на вход имя и фамилию. При использовании данного конструктора, в номер зачетной книжки записывается 0;
принимает на вход имя, фамилию, номер зачетной книжки.
метод возвращает имя студента;
метод изменяет имя студента;
метод возвращает фамилию студента;
метод изменяет фамилию студента;
метод возвращает номер зачетной книжки;
метод изменяет номер зачетной книжки;
метод возвращает год поступления;
метод изменяет год поступления.
Создайте публичный класс Group
– студенческой группы. Класс не хранит явным образом специальность и название университета.
В классе должны быть следующие поля:
уникальный номер (в пределах специальности);
массив студентов.
принимает на вход номер группы (в этом случае, количество студентов записывается как 0);
принимает на вход номер группы, количество студентов (инициализация массива, но его элементы остаются пустыми);
принимает на вход массив студентов.
метод возвращает номер группы;
метод изменяет номер группы;
метод возвращает общее число студентов группы;
метод возвращает ссылку на студента по номеру зачетной книжки;
метод удаляет студента из группы по номеру зачетной книжки (помните про корректное удаление элемента из массива);
метод добавляет нового студента в группу (принимает на вход ссылку на объект Student, если массив уже полностью заполнен - реализуйте расширение массива);
метод возвращает массив студентов;
метод возвращает массив студентов, отсортированный по фамилиям (подумайте, как это сделать; если фамилии одинаковы - сортируйте по имени; если имена одинаковы - по номеру зачетной книжки).
Создайте класс Payment
- платы за обучение.
В классе должны быть следующие поля:
дата (экземпляр класса java.util.Date
);
размер суммы, переведенной студентом на счет университета.
без параметров (дата содержит null
, сумма - 0);
с двумя параметрами - датой оплаты и суммой.
геттеры и сеттеры для приватных полей.
Создайте класс ContractStudent
, расширяющий класс Student
. Данный класс добавляет следующие члены:
Поля:
приватное поле - массив платежей;
приватное поле - стоимость обучения за семестра (предполагается, что эта стоимость в течение всего периода обучения меняться не будет).
Конструкторы:
конструктор с именем и фамилией студента (номер зачетной книжки - 0, создает массив платежей нулевой длины);
конструктор, который принимает на вход имя, фамилию, номер книжки (создает массив платежей нулевой длины);
Методы:
геттер для массива платежей и стоимости обучения за семестр;
метод, возвращающий размер задолженности студента на текущий момент. Исходите из того, что плата за обучение не меняется. Зная текущую дату и год поступления, можно выяснить, сколько семестров студент проучился и определить сумму, которую он должен внести за проведенные в университете семестры. Сравните эту сумму с тем, что студент оплатил по факту;
метод, добавляющий новый платеж в конец списка платежей;
Создайте интерфейс Event
- мероприятия, в котором принимает участие студент. Интерфейс определяет следующие методы:
геттеры и сеттеры для даты проведения мероприятия (методы работают с объектами типа java.util.Date
);
геттеры и сеттеры для названия города, в котором проводилось мероприятие.
Определите класс Olympics
- студенческой олимпиады. Класс реализующий интерфейс Event
.
В классе должны быть следующие поля:
дата проведения олимпиады;
название города, где проходила олимпиада;
место, которое занял студент на олимпиаде.
конструктор с тремя полями (дата, город, место).
геттеры и сеттеры для приватных полей.
Определите класс Conference
- студенческой конференции. Класс должен реализовывать интерфейс Event
.
В классе должны быть следующие поля:
дата проведения конференции;
город, где проходила конференция;
название доклада, с которым студент выступал на конференции.
конструктор с тремя полями (дата, город, название доклада).
геттеры и сеттеры для приватных полей.
Определите класс Competition
- студенческого соревнования. Класс должен реализовывать интерфейс Event
.
В классе должны быть следующие поля:
дата проведения соревнования;
город, где проходило соревнование;
название студенческого проекта;
выигранная сумма (0, если студент ничего не выиграл).
конструктор с четырьмя полями (дата, город, название проекта, сумма).
геттеры и сеттеры для приватных полей.
Определите интерфейс Activist
- участника различных конкурсов, олимпиад и так далее. Интерфейс определяет следующие методы:
метод возвращает общее количество мероприятий, в которых принимал участие студент;
метод возвращает число призовых мест (с 1 по 3), занятых на олимпиадах;
метод возвращает число докладов на конференциях;
метод возвращает строку, состоящую из названий проектов (названия разделены переходом на новую строку), за которые студент получил какую-то сумму на соревнованиях.
Измените класс Student
. Он теперь должен реализовывать интерфейс Activist
. Реализуйте методы интерфейса. Помимо этого, в класс необходимо добавить следующие члены класса:
Поля:
массив событий, в которых принимал участие студент.
Методы:
добавление нового события в массив событий;
поиск событий по дате (метод возвращает ссылку на объект события);
удаление события по дате.
Внесите изменения в конструктор(-ы) класса Student
, чтобы они инициализировали массив событий.
Измените класс Group
. Добавьте следующие методы:
метод возвращает массив студентов-активистов (студенты, которые принимали участие хотя бы в одном событии);
метод возвращает список студентов-призеров (которые хотя бы раз занимали призовое место на олимпиаде или выигрывали какую-то сумму в соревновании);
метод возвращает количество студентов-активистов;
метод возвращает число "бюджетников" в группе;
метод возвращает число контрактных студентов в группе;
метод возвращает количество студентов-должников (вовремя не оплативших контракт).
В классе Main
напишите код, чтобы протестировать функциональность созданных классов и реализованных методов
Модифицируйте приложение Draw из лабораторной работы №5, что приложение использовало механизм абстрактных классов.
Данная программа выводит список студентов и позволяет отсортировать их по имени, фамилии и по среднему баллу.
В программе уже реализована сортировка студентов по имени.
Задание состоит в следующем:
доделать программу, чтобы по нажатию на соответствующие кнопки происходила сортировка студентов по фамилии и по среднему баллу;
задание на дополнительные баллы - доработайте программу так, чтобы при повторном нажатии на ту же кнопку сортировки, осуществлялась обратная сортировка студентов. То есть, при первом нажатии на кнопку "Сортировка по имени" студенты сортировались в прямом порядке (по возрастанию), а при повторном нажатии на эту же кнопку, студенты сортировались в обратном порядке (по убыванию).
Список студентов находится в коллекции students
, для сортировки необходимо вызвать у коллекции метод sort()
и в этот метод передать объект компаратора.
Что такое компаратор. Базовая операция для почти любой сортировки – сравнение двух элементов. Если вы сортируете обычные числа или строки, сравнение происходит элементарно по известным всем правилам. Но как быть, если вы хотите отсортировать объекты, которые могут иметь десятки полей различных типов?
Для этого существует так называемый компаратор. Компаратор в Java – это объект класса, который реализует интерфейс Comparator
. Интерфейс определяет всего один метод compare()
, который принимает на вход два объекта типаObject
. Если первый объект «меньше» – метод возвращает отрицательное число (обычно это просто -1, но может быть и любое другое отрицательное число), если первый объект «больше» – метод возвращает положительное число (обычно это 1, но может быть и любое другое положительное число), если объекты «равны» – метод возвращает 0.
Задача программиста - прописать нужную логику сравнения и вернуть -1/0/1 в том или ином случае.
Вы создаете класс, указываете, что этот класс реализует интерфейс Comparator
и в методе compare()
описываете логику сравнения двух объектов. В случае со сравнением двух студентов по имени, код компаратора выглядит следующим образом
Обратите внимание, что сначала мы должны убедиться, что оба объекта являются объектами класса Student
, после чего делаем нисходящее преобразование.
В данном случае мы сравниваем два поля name
у двух объектов класса Student
. Так как это тип String
, нам нет нужды сравнивать строки «вручную», мы просто можем воспользоваться методом compareTo()
, который есть у любого объекта класса String
.
Скачайте архив с проектом по этому адресу -
Что такое коллекции? Чем отличаются коллекции от обычных структур данных?
Какие типы коллекций вы знаете?
В чем особенность коллекции List? Какие классы реализуют List и чем они отличаются?
В чем особенность коллекции Queue? Какие классы реализуют Queue и чем они отличаются?
В чем особенность коллекции Set? Какие классы реализуют Set и чем они отличаются?
В чем особенность коллекции Map? Какие классы реализуют Map и чем они отличаются?
В качестве "Desired username" указать фамилию и группу латиницей, например, ivanov171.
3. Вам необходимо выполнить все задачи в этой главе (3 вопроса и 14 упражнений).
4. При выполнении задачи, вам дано условие задачи и код. Необходимо вписать то, что должен вернуть метод при определенном входном аргументе.
Протокол лабораторной работы должен содержать ответы на задачу и правильный код методов для упражнений. При сдаче лабораторной работы вы должны зайти под своим аккаунтом в систему и показать, что все задачи и упражнения решены верно.
1. Дан список из 100 целочисленных значений от -1000 до 1000 (создайте и сгенерируйте список). Не используя вспомогательных объектов, переставьте отрицательные элементы данного списка в конец, а положительные – в начало списка. Не используйте методы сортировки.
2. Дан случайный текст – http://pastebin.com/JEwMJVbh. Выделить все различные слова. Слова, отличающиеся только регистром букв, считать одинаковыми (подумайте о том – какую коллекцию лучше использовать).
3. Дан случайный текст – http://pastebin.com/JEwMJVbh. Выделить все различные слова. Для каждого слова подсчитать частоту его встречаемости (сколько раз слово встречается / общее количество слов). Слова, отличающиеся регистром букв, считать различными (подумайте о том – какую коллекцию лучше использовать).
4. Дан случайный текст – http://pastebin.com/JEwMJVbh. Разбить текст на предложения, каждое предложение записать в список. Выполнить сортировку строк, используя метод sort() из класса Collections. Результат вывести на экран.
Ознакомиться с механизмом исключений;
Научиться обрабатывать исключения в программе (блок try-catch, ключевое слово throws);
Разобраться с различными видами исключений;
Научиться добавлять собственные типы исключений в программу;
Изучить использование блока finally и try-with-resources.
изучить структуру и основные компоненты клиент-серверных систем;
разобраться с созданием приложения с помощью Spring Boot;
изучить структуру протокола HTTP;
разобраться с использованием архитектурного стиля REST;
изучить процесс создания конечных точек и использование атрибутов для создания конечных точек.
Создать Spring Boot проект, запустить проект и убедиться, что application server запущен и на нем установлен проект
Придумать предметную область для веб-службы.
Возможные варианты: электронный журнал группы, электронная регистратура в больнице, электронная библиотека.
Придумать несколько классов, которые описывают предметную область. Придумать поля для этих классов.
Для каждой сущности написать класс REST-контроллера, который содержит конечные точки для основных действий над сущностями.
С помощью приложения Postman (https://www.getpostman.com/downloads/) протестировать работу приложения на сервере, убедиться, что сервер возвращает корректный ответ в тех или иных ситуациях.
изучить назначение и порядок создания классов служб;
изучить процесс работы с базами данных, понятие драйвера, протокола JDBC;
разобраться с технологией ORM, ее предназначением, преимуществами и недостатками;
подключить необходимые библиотеки и настроить работу приложения вместе с СУБД;
модифицировать классы предметной области для работы с технологией ORM.
Подключить требуемые зависимости для работы с выбранной СУБД (СУБД выбирается студентом самостоятельно);
Создать и сконфигурировать настроечный файл application.yml для работы с выбранной СУБД.
Модифицировать классы предметной области для преобразования их в классы сущностей для работы с Hibernate;
Создать интерфейсы репозитория для каждой сущности.
Написать классы сервисов для каждой сущности, предусмотреть методы для выполнения действий с репозиторием каждой сущности;
Модифицировать классы REST-контроллеров, внедрить классы соответствующих сервисов.
Создадим аккаунт на сервисе Heroku
Зайдем в аккаунт и создадим новое приложение (new app)
Зайдем в раздел Resources, в пункте Add-ons добавим Heroku Postgres
Перейдем в настройки базы данных
На страничке адд-она перейдем в раздел Settings и выберем View Credentials
Таким образом, мы получим credentials для подключения к базе.
Перейдем в application.yml и укажем настройки для БД на heroku
После указания настроек, можете сразу запустить приложение, чтобы убедиться, что вы правильно указали настройки для подключения БД.
Заходим на сайт https://git-scm.com/, скачиваем последнюю версию установщик и устанавливаем Git.
Заходим в командную строку и настраиваем имя и почту разработчика
Возвращаемся в Heroku, скачиваем и настраиваем Heroku CLI
Заходим в командую строку и логинимся на heroku с помощью команды heroku login
Переходим в директорию проекта, после чего инициализируем git-репозиторий с помощью команды git init
Далее устанавливаем удаленный репозиторий
Начинаем отслеживать файлы проекта с помощью команды git add .
, после чего делаем коммит с помощью команды git commit -am "initial commit"
.
Теперь можно пушить проект на удаленный репозиторий с помощью команды git push heroku master
.
Как видим, мы успешно развернули проект на Heroku. Проверим работу веб-сервиса. Перейдем в браузер и укажем адрес https://opnu-ej.herokuapp.com/
Теперь попробуем осуществить REST-запросы к серверу. Добавим группу, добавим студента, получим список групп.
Добавляем группу
Добавляем студента
Получаем список групп
Развернуть разработанное приложение (10 и 11 лабораторные работы) в облачной платформе по выбору (в инструкции рассматривается разворачивание облака на платформе Heroku).
Активировать и подключить базу данных облачной платформы.
С помощью приложения Postman продемонстрировать корректную работу приложения.
изучить принципы гибкой методологии Scrum;
определить пользователей и написать user stories (пользовательские истории) для разрабатываемого приложения;
сформировать бэклог продукта на основе написанных пользовательских историй;
сформировать Storyboard и макеты окон.
Разработку программного обеспечения можно представить в виде набора определенных процессов:
формирование требований к ПО (сбор и анализ). Требования к ПО – это описание функциональных возможностей и ограничений, накладываемых на программную систему. Успешно разработанное программное обеспечение – программное обеспечение, соответствующее требованиям. Результатом этого процесса является сформированный SRS (Software Requirement Specification) – законченное описание программной системы, которую нужно разработать;
На основе сформированных требований осуществляется проектирование программного обеспечения – этап разработки ПО, в рамках которого исследуются структура и взаимосвязи элементов разрабатываемой системы. Прорабатывается архитектура системы, определяются требования к программному обеспечению. Результатом данного процесса является набор документов – проектная документация (Software Architecture Document, Software Design Document) – набор документов, которые могут включать диаграммы, блок-схемы, макеты и т.д. Проектируются:
архитектура (выбор структурных элементов, их интерфейсов, объединение элементов в блоки, способ объединения этих элементов и т.д);
устройство компонентов ПО;
UI приложения.
Разработка программного продукта осуществляется на основе разработанной проектной документации. Фактически, на этом этапе пишется программный код – кодируются алгоритмы, выбираются и кодируются структуры данных. На этом этапе, как правило, пишется техническая документация. Результатом этого этапа является написанный программный код, а также техническая документация;
Тестирование программного обеспечения осуществляется на основе разработанного программного кода и ставит перед собой две цели:
выявить ошибки (ситуации, при которых поведение программы является неправильным, нежелательным или не соответствующим требованиям);
продемонстрировать заказчику, что ПО соответствует требованиям.
Сопровождение программного обеспечения происходит после передачи ПО в эксплуатацию. На этом этапе происходит улучшение, оптимизация и устранение выявленных дефектов.
Также, в качестве отдельных ключевых процессов иногда выделяют отладку программного обеспечения и внедрение программного обеспечения:
Отладка программного обеспечения – выполняется после этапа тестирования, состоит в нахождении, локализации и устранении ошибок. В отличии от тестирования, на этапе отладки обнаруживают причину ошибки (на этапе тестирование происходит обнаружение факта ошибки);
Внедрение программного обеспечения – происходит после процесса отладки и состоит в установке ПО (очень часто это не просто запуск инсталлятора, программная система может умещаться на десятках клиентов, серверов, мобильных устройств и т.д.), настройки ПО под условия заказчика, а также обучение пользователей работе с программным продуктом.
Последовательность выполнения и взаимное отношение этих процессов называют моделью разработки программного продукта. То есть, вышеперечисленные этапы могут выполняться в разных последовательностях, повторяться, применяться не ко всему программному продукту, а только к его части – всё это определяет модель разработки ПО. Существует множество моделей разработки ПО. Приведем иллюстрации некоторых из них:
Гибкая методология разработки (Agile software development) – серия подходов к разработке программного обеспечения (начало 00-х годов). Суть agile-похода изложена в «манифесте», но коротко ее можно сформулировать так:
разработка ведется короткими циклами (итерациями, называется «спринт») продолжительностью 1-4 недели;
в конце каждой итерации заказчик получает ценное для него приложение (или его часть), которое можно использовать в бизнесе;
команда разработки сотрудничает с заказчиком в ходе всего проекта;
изменения в проекте приветствуется и быстро включаются в работу.
Agile-манифест:
Наивысшим приоритетом для нас является удовлетворение потребностей заказчика, благодаря регулярной и ранней поставке ценного программного обеспечения;
Изменение требований приветствуется, даже на поздних стадиях разработки. Agile-процессы позволяют использовать изменения для обеспечения заказчику конкурентного преимущества;
Работающий продукт следует выпускать как можно чаще, с периодичностью от пары недель до пары месяцев;
На протяжении всего проекта разработчики и представители бизнеса должны ежедневно работать вместе;
Над проектом должны работать мотивированные профессионалы. Чтобы работа была сделана, создайте условия, обеспечьте поддержку и полностью доверьтесь им;
Непосредственное общение является наиболее практичным и эффективным способом обмена информацией как с самой командой, так и внутри команды;
Работающий продукт — основной показатель прогресса;
Инвесторы, разработчики и пользователи должны иметь возможность поддерживать постоянный ритм бесконечно. Agile помогает наладить такой устойчивый процесс разработки;
Постоянное внимание к техническому совершенству и качеству проектирования повышает гибкость проекта;
Простота — искусство минимизации лишней работы — крайне необходима;
Самые лучшие требования, архитектурные и технические решения рождаются у самоорганизующихся команд;
Команда должна систематически анализировать возможные способы улучшения эффективности и соответственно корректировать стиль своей работы.
Существует много методологий гибкой разработки (XP, Lean, FDD, Scrum, Scrumban и так далее). Наиболее популярной из них является Scrum.
Организуйте работу в вашей организации в небольших кроссфункциональных командах, которые содержат всех необходимых специалистов. Выделите человека – скрам-мастера, который будет отвечать за соблюдение процессов в команде и конструктивную атмосферу.
Требования разбейте на небольшие, ориентированные на пользователей, функциональные части, которые максимально независимы друг от друга, в результате чего получите беклог продукта. Затем упорядочите элементы беклога по их важности и произведите относительную оценку объемов каждой истории. Выделите отдельного человека – владельца продукта, который будет отвечать за требования и их приоритеты, замыкая на себя всех заинтересованных лиц.
Всю работу ведите короткими (от 1 до 4 недель) фиксированными итерациями – спринтами, поставляя в конце каждого из них законченный функционал, который можно при необходимости вывести на рынок – инкремент продукта. Команда согласно своей скорости набирает задачи на неизменяемую по времени итерацию – спринт.
Каждый день проводится скрам-митинг, на котором команда синхронизирует свою работу и обсуждает проблемы. В процессе работы члены команды берут в работу элементы беклога согласно приоритету.
В конце каждого спринта проводите обзор спринта, чтобы получить обратную связь от владельца продукта, и ретроспективу спринта, чтобы оптимизировать ваши процессы. После этого владелец продукта может изменить требования и их приоритеты и запустить новый спринт
В Scrum принято выделять три основных роли: владелец продукта, скрам-мастер и команда:
Product owner (владелец продукта) – это человек, ответственный за создание требований и их приоритезацию;
Scrum-master (скрам-мастер) – член команды, который отвечает за процессы в Scrum (координация работы, митинги и т.д.);
Команда – разработчики, которые реализуют требования владельца продукта.
Product backlog (Бэклог продукта) – приоритезированный список требований с оценкой трудозатрат. Обычно состоит из бизнес-требований, которые приносят конкретную бизнес-ценность и называются элементы бэкглога;
Sprint backlog (бэклог спринта) – часть беклога продукта, с самой высокой важностью и суммарной оценкой, не превышающей скорость команды, отобранная для спринта;
Инкремент продукта – новая функциональность продукта, созданная во время спринта.
Как было сказано выше бэклог продукта состоит из бизнес-требований, которые обычно оформляются в виде пользовательских историй (user story). Давайте взглянем более подробно, что представляет собой отдельная история пользователя:
уникальный числовой идентификатор истории;
название истории пользователя – короткое описание функционала с точки зрения пользователя, сформулированное в виде тройки «Роль», «Действие», «Цель»;
важность – уникальный числовой приоритет истории пользователя, чем она выше, тем раньше данную историю необходимо сделать;
оценка – числовая относительная оценка истории пользователя по специальной шкале. В рамках данной практической работы не используется.
Данные поля являются фактически обязательными, но достаточно часто используются и дополнительные поля:
подробное описание – текстовое и графическое описание истории пользователя. Применяется, прежде всего, в распределенных командах для хранения знаний о функционале продукта;
демонстрация - достаточно подробный сценарий, позволяющий провести демонстрацию истории пользователя. Например, для вышеприведенной истории пользователя с авторизацией, можно использовать следующие краткие сценарии для демонстрации:
пользователь вводит логин «root» и пароль «pass», и переходит на страницу личного профиля на сайте;
пользователь вводит логин «root» и пароль «wrongpass», и получает сообщение «Введен неправильный логин или пароль»;
категория – используется для повышения управляемости с помощью категоризации задач. В качестве категорий могут выступать как продуктовые категории («темы» и «эпики» в терминологии Scrum), так и категории типа «Оптимизация производительности», «Техническая история» и тому подобные.
Итак, у нас есть потребность в реализации системы, которая бы позволила пользователям хранить и обмениваться фотографиями. Ожидается, что прибыль от системы будет достигаться за счет процента с продаж пользователями своих фотографий, также, возможно, за счет рекламы третьих компаний.
Это короткое предложение – ничто иное как видение (vision) системы. Его вполне достаточно для того, чтобы начать описывать истории. Но сначала, давайте идентифицируем группы пользователей – истории будут рассказаны от их имени. Знание о будущих пользователях поможет нам сфокусироваться на нуждах каждого из них, не упустив важные моменты в требованиях к системе. И так, разными аспектами системы будут пользоваться такие обобщенные пользовательские роли:
«пользователи» – хранят и обмениваются своими фотографиями;
«рекламодатели» – размещают свою рекламу, ориентированную на «пользователей» системы;
в результате обсуждения видения системы возникает необходимость в появлении еще одной роли «администраторы» – они будут обеспечивать поддержку системы для блага других пользователей.
Возможно, потом появятся другие роли, про которые мы пока не знаем. Для начала достаточно этих.
Так, имея роли пользователей и их основные задачи, попробуем описать самые важные истории, которые могли бы нам рассказать о будущей системе. Истории предлагается писать в таком формате:
Как <пользователь>, я могу <действие>, для того, чтобы <цель>
где:
<пользователь> – одна из обобщенных пользовательских ролей;
<действие> – действие, выполняемое пользователем посредством взаимодействия с системой;
<цель> – конечная цель текущей задачи, выполняемой пользователем посредством взаимодействия с системой.
Этот формат себя хорошо зарекомендовал – он поможет нам во время продумывания и последующего обсуждения историй персонализировать себя с тем или иным пользователям, помогая лучше представить детали их взаимодействия с системой. Последняя часть <цель> может быть опущена, если цель истории и так ясна.
Придумаем истории:
Как пользователь я могу хранить свои фотографии в системе, чтобы иметь возможность показать или продать их другим пользователям;
Как рекламодатель я могу помещать свою рекламу в системе, ориентированную на пользователей;
Как администратор я могу управлять фотографиями пользователей, так чтобы контент сайта был легальным.
Во время обсуждения первой истории, product owner и команда приходят к тому, что пользователи системы должны быть авторизированы системой перед выполнением каких-либо действий с фотографиями. Это приводит к появлению новой пользовательской роли «гостя» – группы людей, которые неавторизированы системой или вообще пока не имеют пользовательской учетной записи.
Как гость я могу зарегистрироваться в системе для получения пользовательской учетной записи и последующей работы;
Как гость я могу войти в систему под ранее созданной учетной записью, для последующей работы.
Пользуясь принципом симметричности требований, product owner и заказчик принимают решение, что пользователь должен иметь возможность удалить свою учетную запись в случае необходимости:
Как пользователь я могу удалить свою учетную запись и перестать быть пользователем системы.
Обсуждая концепцию учетных записей, рождаются также следующие истории:
Как пользователь я могу изменить данные своей учетной записи;
Как пользователь я могу сделать некоторые поля своей учетной записи видимыми для других пользователей.
Во время обсуждения историй, product owner и команда приходят к пониманию уровня детализации, который необходим на текущей фазе и принимают совместные решения, пополняя истории все бОльшим количеством информации.
Рассмотрим историю
Как гость я могу зарегистрироваться в системе для получения пользовательской учетной записи и последующей работы.
Во время обсуждения этой истории с командой product owner`у задают вопрос о том какая информация нужна для создания пользовательской учетной записи. Обсуждая различные варианты, заказчик и команда приходят к тому, что для первой версии системы достаточно будет проверенного электронного адреса плюс имени пользователя и его пароля.
К истории дописывается этой комментарий. Теперь история выглядит так:
Как гость я могу зарегистрироваться в системе для получения пользовательской учетной записи и последующей работы:
Нужен проверенный email и выбранные пользователем имя и пароль.
В ходе дальнейших высказываний кто-то из тестировщиков задает резонный вопрос о минимальной длине пароля и проверке на уникальности имени. Продолжая дискуссию, команда и заказчики приходят к мнению, что необходимо описать основные критерии готовности истории, чтобы команда понимала ожидания и знала, когда объявлять историю готовой:
Как гость я могу зарегистрироваться в системе для получения пользовательской учетной записи и последующей работы:
Нужен проверенный email и выбранные пользователем имя и пароль.
Тест 1: пользователь не может ввести пароль меньше 6 символов
Тест 2: пользователь не может ввести имя меньше 3 и больше 20 символов;
Тест 3: пользователь должен иметь уникальное имя в системе;
Тест 4: после регистрации пользователь должен получить email для активизации своей учетной записи;
Тест 5: пользователь не может войти в систему, если учетная запись не была активизирована;
Тест 6: при успешном входе система приветствует пользователя текстом «Добро пожаловать, <имя пользователя>»
Возможно во время реализации, тестирования и приема истории возникнут ещё какие-то дополнительные моменты. В этом случае они могут быть описаны в виде уточняющих тестов или как комментарии. Возможно из этих дополнения появятся новые истории.
Таким образом истории пополняются деталями по мере необходимости, эволюционируя от коротких высказываний до детализированных и согласованных требований со встроенными критериями готовности.
Пользуясь знанием рынка, а также здравым смыслом, product owner выстраивает список историй таким образом, чтобы максимизировать возврат вложений от проекта.
Как гость я могу зарегистрироваться в системе для получения пользовательской учетной записи и последующей работы;
Как гость я могу войти в систему под ранее созданной учетной записью, для последующей работы.
Как пользователь я могу хранить свои фотографии в системе, чтобы иметь возможность показать или продать их другим пользователям.
Как администратор я могу управлять фотографиями пользователей, так чтобы контент сайта был легальным.
Как пользователь я могу изменить данные своей учетной записи.
Как рекламодатель я могу помещать свою рекламу в системе, ориентированную на пользователей.
Как пользователь я могу сделать некоторые поля своей учетной записи видимыми для других пользователей.
Как пользователь я могу удалить свою учетную запись и перестать быть пользователем системы.
Истории выстроены в порядке, который, во-первых, логичен с точки зрения product owner`а и команды, а во-вторых ценность историй уменьшается сверху вниз. Таким образом, если, к примеру, на половине проекта наступает нехватка ресурсов (скажем, после реализации истории для администратора системы), заказчики смогут получить выгоду от продукта, так как наиболее важные истории уже будут реализованы
Кроме инструментария ранжирования историй, в руках у заказчика есть и другие мощные средства, позволяющие повысить эффективность своих финансовых вложений. К примеру, одна из описанных на ранней фазе проекта историй в какой-то момент может показаться слишком большой в сравнении с другими, что усложняет понимание её приоритета:
Как пользователь я могу хранить свои фотографии в системе, чтобы иметь возможность показать или продать их другим пользователям.
В этом случае заказчик и команда могут попробовать разбить ее на несколько более мелких историй, каждая из которых может получить свой приоритет:
Как пользователь я могу хранить свои фотографии в системе, чтобы иметь возможность показать их другим пользователям;
Как пользователь я могу хранить свои фотографии в системе, чтобы иметь возможность продать их другим пользователям.
При этом нужно учесть, что начальная история не разбивается на две «под-истории», а замещается двумя новыми. Это не разбиение историй на подзадачи для постановки их программистам, это всего лишь переформулировка требований для более эффективного управления ими.
Хорошая user story должна соответствовать модели «INVEST»:
Independent. Reduced dependencies = easier to plan (история должна быть, по возможности, независима от других историй);
Negotiable. Details added via collaboration (детали истории должны дополняться в результате обсуждения);
Valuable. Provides value to the customer (история должна давать бизнес-ценность заказчику);
Estimable. Too big or too vague = not estimable (история не должна быть слишком большой и расплывчатой);
Small. Can be done in less than a week by the team (история должна быть реализована менее чем за неделю);
Testable. Good acceptance criteria (ясный критерий приемки).
Лучше написать много историй поменьше, чем несколько громоздких;
Каждая история в идеале должна быть написана избегая технического жаргона – чтобы клиент мог приоритезировать истории и включать их в итерации;
Истории должны быть написаны таким образом, чтобы их можно было протестировать;
Тесты должны быть написаны до кода;
Как можно дольше стоит избегать UI. История должна выполняться без привязки к конкретным элементам;
Каждая история должна содержать оценку;
История должна иметь концовку – т.е. приводить к конкретному результату;
История должна вмещаться в итерацию.
Придумать тему для курсовой работы и заполнить задание на курсовую работу.
Работа с пользовательскими историями:
Сформировать видение системы;
Определить пользовательские роли;
Написать пользовательские истории (хотя бы для первого спринта):
краткое описание;
приоритет (от 1 до 10);
критерии приемки;
детали истории.
Из историй сформировать бэклог продукта (приоритезированный список историй).
Сервер - компьютер или программа, которая управляет ресурсами (информация, файлы, база данных) называется сервером этого ресурса или просто сервером.
Архитектура "клиент-сервер" определяет общие принципы организации взаимодействия, где имеются серверы (узлы-поставщики некоторых специфичных функций и сервисов) и клиенты, (потребители этих сервисов).
Между клиентами и серверами должны быть установлены правила взаимодействия, которые называются протоколом взаимодействия или протоколом обмена. Каждая часть взаимодействует друг с другом, обмениваясь сообщениями в заранее согласованном формате.
Более подробно про клиент-серверное взаимодействие читайте здесь - http://bit.ly/2qmKbHk
В рамках данного курса рассматривается так называемая "трехзвенная архитектура"
Компоненты трехзвенной архитектуры:
клиент - этот компонент отвечает за представление данных конечному пользователю;
выделенный сервер приложений - здесь содержится бизнес-логика приложения;
сервер БД - предоставляет запрашиваемые данные.
Сервер приложений (application server) – сервисная программа, которая обеспечивает доступ клиентов к прикладным программам, выполняющимся на сервере.
Большинство серверов приложений имеют в своем составе веб-сервер. Это означает, что сервер приложений может делать все, на что способен веб-сервер. Кроме того, сервер приложений имеет компоненты и функции для поддержки сервисов уровня приложения, таких как пул соединений, поддержка транзакций и так далее.
Информация о сервере приложений - http://bit.ly/2qt2Q4t. Отличия веб-сервера и сервера приложений - http://bit.ly/2qlUaNe Подробнее про сервлеты и контейнеры сервлетов - http://bit.ly/2Q9GAaP
Spring – свободно-распространяемый легковесный фреймворк, призванный упростить разработку корпоративных и веб-приложений (можно использовать и для любых других типов приложений) на языке Java (является альтернативной стеку Jakarta EE).
В данный момент Spring представляет собой целый набор модулей, которые можно использовать выборочно для тех или иных проектов.
Дадим краткую характеристику некоторым модулям Spring:
Spring Core – ядро платформы, предоставляет базовые средства для создания приложений — управление компонентами (бинами, beans), внедрение зависимостей, MVC фреймворк, транзакции, базовый доступ к БД. В основном это низкоуровневые компоненты и абстракции. По сути, неявно используется всеми другими компонентами;
Spring MVC – обеспечивает архитектуру паттерна Model-View-Controller при помощи слабо связанных готовых компонентов для разработки веб-приложений;
Spring Data – обеспечивает доступ к данным: реляционные и нереляционные БД, KV хранилища и т.п.;
Spring Cloud – используется для микросервисной архитектуры;
Spring Security – авторизация и аутентификация, доступ к данным, методам и т.п. OAuth, LDAP, и различные провайдеры.
Проект Spring Boot – решение, которое позволяет вам легко создавать полноценные приложения Spring, про которые можно сказать «просто запусти».
Spring Boot позволяет быстро создать и сконфигурировать (т.е. настроить зависимости между компонентами) приложение, упаковать его в исполняемый самодостаточный артефакт. Это то связующее звено, которое объединяет вместе набор компонентов в готовое приложение.
Особенности Spring Boot:
создание полноценных Spring-приложений;
встроенный сервлет-контейнер (Tomcat или Jetty);
обеспечивает начальные pom-файлы для упрощения конфигурации Maven;
используется автоконфигурация, где это возможно;
используется принцип «convention over configuration». Для большинства конфигураций не нужно ничего настраивать.
Изучение фреймворка Spring лучше всего начать с установки требуемого программного обеспечения и разработки тестового приложения с помощью Spring Boot.
Всемирная паутина является готовой платформой для создания и использования распределенных систем на основе веб-служб. Веб-сервер выступает в качестве сервера приложений, к которым обращаются не конечные пользователи, а сторонние приложения. Это позволяет многократно использовать функциональные элементы, устранить дублирование кода, упростить решение задач интеграции приложений.
Веб-служба или веб-сервис (web-service) – сетевая технология, обеспечивающая межпрограммное взаимодействие на основе веб-стандартов. W3C определяет веб-службу как «программную систему, разработанную для поддержки интероперабельного межкомпьютерного (machine-to-machine) взаимодействия через сеть».
К моменту появления веб-служб уже существовали технологии, позволяющие приложениям взаимодействовать на расстоянии, где одна программа могла вызвать какой-нибудь другой метод в другой программе, которая при этом могла быть запущена на компьютере, расположенном в другом городе или даже стране. Это сокращенно называется RPC (Remote Procedure Calling – удаленный вызов процедур). В качестве примеров можно привести технологии CORBA, а для Java – RMI (Remote Method Invoking – удаленный вызов методов).
Идея веб-службы заключалась в создании такого RPC, который будет упаковываться в HTTP пакеты. Такой подход стал очень популярным, т.к. HTTP был хорошо известен, прост, понятен и обеспечивал лучшее «прохождение» через различные firewall`ы. Именно с появлением веб-сервисов развилась идея SOA – сервис-ориентированной архитектуры веб-приложений (Service Oriented Architecture).
Протокол HTTP лежит в основе обмена данными в Интернете. HTTP является протоколом клиент-серверного взаимодействия, что означает инициирование запросов к серверу самим получателем (браузером или другим клиентским приложением).
Клиенты и серверы взаимодействуют, обмениваясь одиночными сообщениями (а не потоком данных). Сообщения, отправленные клиентом называются запросами, а сообщения, отправленные сервером, называются ответами.
HTTP - это клиент-серверный протокол, то есть запросы отправляются какой-то одной стороной - участником обмена (user-agent). Чаще всего в качестве участника выступает веб-браузер, но им может быть кто угодно.
Каждый запрос (request) отправляется серверу, который обрабатывает его и возвращает ответ (response).
Участник обмена (user agent) - это любой инструмент или устройство, действующее от лица пользователя.
На другой стороне коммуникационного канала расположен сервер, который обслуживает (serve) пользователя, предоставляя ему документы по запросу. С точки зрения конечного пользователя, сервер всегда является некой одной виртуальной машиной, полностью или частично генерирующий документ, хотя фактически он может быть группой серверов, между которыми балансируется нагрузка, то есть перераспределяются запросы различных пользователей, либо сложным программным обеспечением, опрашивающим другие компьютеры.
Пример HTTP запроса
Запросы содержат следующие элементы:
HTTP-метод, обычно глагол подобно GET, POST или существительное, как OPTIONS или HEAD, определяющее операцию, которую клиент хочет выполнить. Обычно, клиент хочет получить ресурс (используя GET) или передать значения HTML-формы (используя POST), хотя другие операции могут быть необходимы в других случаях;
путь к ресурсу;
заголовки (опционально), предоставляющие дополнительную информацию для сервера;
для некоторых методов, таких как POST, тело метода, которое содержит отправленный ресурс.
Пример HTTP-ответа
Ответы содержат следующие элементы:
версию HTTP-протокола;
HTTP код состояния, сообщающий об успешности запроса или причине неудачи;
сообщение состояния - краткое описание кода состояния;
опционально: тело, содержащее пересылаемый ресурс.
Код состояния - это трехзначное число, которое отдает сервер на запрос клиента и благодаря которому корректируется дальнейшая обработка запрашиваемого документа. За числом всегда идет краткое пояснение кода на английском языке, отделенное пробелом - первичная инструкция клиенту.
Классы состояния - группа кодов, объединенных определенными признаками. На класс состояния указывает первая цифра в коде.
Выделяют пять классов:
1ХХ - информационные кода. Они отвечают за процесс передачи данных. Это временные коды, они информируют о том, что запрос принят и обработка будет продолжаться;
2ХХ - успешная обработка. Запрос был получен и успешно обработан сервером;
3ХХ - перенаправление (редирект). Эти ответы сервера гласят, что нужно предпринять дальнейшие действия для выполнения запроса. Например, сделать запрос по другому адресу;
4ХХ - ошибка клиента. Это значит, что запрос не может быть выполнен на стороне клиента;
5ХХ - ошибка сервера. Эти коды возникают из-за ошибок на стороне сервера. В данном случае клиент сделал все правильно, но сервер не может выполнить запрос. Для кодов этого класса сервер обязательно показывает сообщение, что не может обработать запрос и по какой причине.
На сегодняшний день наибольшее распространение получили следующие протоколы реализации веб-служб:
SOAP (Simple Object Access Protocol) – тройка стандартов SOAP/WSDL/UDDI. Сообщения упаковываются в виде структуры, которая называется конверт (envelope), которая включает идентификатор сообщения, заголовок и тело сообщения;
REST (Representational State Transfer) – архитектурны стиль, который использует концепцию ресурсов и определяет операции через методы HTTP-протокола;
XML-RPC (XML Remote Procedure Call) – вызов удаленных процедур, использующий XML для кодирования своих сообщений и HTTP в качестве транспортного механизма.
Передача состояния представления (Representational State Transfer (REST)) является архитектурным стилем, в котором веб-службы рассматриваются, как ресурсы и могут быть идентифицированы Унифицированными идентификаторами ресурсов (Uniform Resource Identifiers (URI)).
Веб-службы, разработанные в стиле REST и с учетом ограничений REST, известны как RESTful веб-службы.
Каждая единица информации в REST называется ресурсом и имеет однозначный URI, который является ее, своего рода, первичным ключом. То есть, например, третья книга с книжной полки будет иметь URI /book/3, а 35ая страница в этой книге – /book/3/page/35/. Отсюда и получается строго заданный формат. Причем совершенно не имеет значения, в каком формате находятся данные по адресу /book/3/page/35/ – это может быть и HTML, и отсканированная копия книги в виде jpeg-файла и документ Microsoft Word.
Над ресурсами выполняется ряд простых четко определенных операций. В качестве протокола передачи данных используется stateless-протокол, обычно HTTP.
При использовании протокола HTTP действия над данными выполняются с помощью HTTP-методов: GET (получить), PUT (добавить, заменить), POST (добавить, изменить, удалить), DELETE (удалить). Таким образом, действия CRUD (Create-Read-Update-Delete) могут выполняться как со всеми 4-мя методами, так и только с помощью GET и POST. Примеры запросов:
GET /book/ – получить список всех книг;
GET /book/3 – получить книгу номер 3;
PUT /book/ – добавить книгу (данные в теле запроса);
POST /book/3 – изменить книгу (данные в теле запроса);
DELETE /book/3 – удалить книгу.
Как правило, необязательно поддерживать все методы, но, как правило, веб-служба должна поддерживать:
GET – используется для получения существующих ресурсов;
POST – используется для создания/обновления нового ресурса;
PUT – используется для обновления/замены ресурса;
DELETE – используется для удаления ресурса.
Кроме этого, служба может поддерживать такие методы как PATCH (обновление части ресурса), HEAD (возвращение заголовка ресурса, т.е. метаданных) и т.д.
Для выполнения домашнего задания нам понадобится следующее программное обеспечение:
приложение Postman - https://www.getpostman.com/downloads/;
среда разработки, которая поддерживает Spring (например, IntelliJ IDEA Ultimate Edition или дистрибутив Eclipse под названием Spring Tool Suite – https://spring.io/tools) либо любая другая IDE с поддержкой Java и Maven.
Существует несколько способов создать Spring Boot проект. Из наиболее простых способов можно выделить:
генерация готового проекта на сайте https://start.spring.io/ (проект Spring Initializr);
создание проекта средствами IDE.
Создадим проект с помощью мастера Intellij IDEA. Создадим новый Spring Boot проект (выберите пункт Spring Initializr). Необходимо указать JDK, метаданные проекта, а также выбрать из списка модулей нужные нам модули Spring
Для выполнения задания нам необходимо выбрать web-модуль. Панель выбранных компонентов будет иметь следующий вид:
После окончания работы мастера создания проектов, мы получим стартовый проект Spring Boot. Рассмотрим структуру проекта и обозначим ключевые файлы:
HotelApplication.java - стартовый класс Spring Boot приложения;
application.properties - файл с настройками приложения. В нем можно переопределить настройки по умолчанию;
pom.xml - POM-файл проекта. Используется сборщиком Maven.
POM-файл (Project Object Model) – это XML-файл, который содержит информацию о деталях проекта, и конфигурации для создания проекта на Maven. Он всегда находится в базовом каталоге проекта. Во время выполнения задач, Maven ищет pom-файл в базовой директории проекта. Он читает его и получает необходимую информацию, после чего выполняет задачи.
Корневым элементом является элемент <project>. Внутри тега project содержится основная и обязательная информация о проекте.
Зависимости (dependency) – это те библиотеки, которые непосредственно используются в проекте для компиляции кода или его тестирования.
Мы создаем RESTful веб-службу с помощью Spring Boot, поэтому нам нужно «подтянуть» для нашего проекта различные Spring-модули (библиотеки с классами, jar-файлы).
В обычных проектах нам бы было необходимо добавлять каждую зависимость вручную, но Spring Boot позаботился о нас и предоставил нам своего рода «мета-зависимости». Смысл их в том, что Spring Boot понимает, что если вы создаете web-приложение то вам нужен примерно одинаковый набор jar-файлов, поэтому чтобы не писать каждый jar-файл отдельно, мы указываем одну зависимость, а она уже «подтянет» за нас другие отдельные зависимости для создания веб-приложения.
Теперь давайте сразу запустим приложение. Убедимся, что приложение запущено успешно
перейдем в браузер и попробуем зайти на сайт.
Как видите, Spring Boot приложение успешно запущено. Так как Spring Boot берет на себя большую часть рутинной работы по созданию и запуску приложения, давайте разберемся, что же происходит, когда мы запускаем приложение:
Устанавливается конфигурация приложения по умолчанию;
Запускается контекст приложения Spring (Spring application context) – это контейнер для кода, который работает на сервере (службы, контроллеры и т.д.). Все приложения Spring имеют этот контекст, который запускается при запуске приложения. Spring Boot создает этот контекст при запуске приложения;
Выполняется сканирование пути к классам (class path scan). Чтобы добавить код в Spring Boot, необходимо создать свои классы и аннотировать их определенным образом. Например, если вы хотите добавить контроллер, вы создаете класс и аннотируете его с помощью аннотации @Controller и так далее. То есть, вы как бы помечаете ваши классы, что это контроллер, это сервис, это еще что-то. Spring сканирует эти классы и, в зависимости от нашего маркера, он работает с этими классами по-разному. То есть Spring сканирует ваш код и ищет классы с этими аннотациями (помимо маркеров, обычно в аннотациях содержатся другие метаданные, которые дают уточняющую информацию для Spring);
Запускается Tomcat-сервер. Мы как раз зашли на сервер через URL и получили страницу с ошибкой, так как на сервере не был предусмотрен обработчик запроса с таким URL. Мы не скачивали Tomcat и не устанавливали его – все за нас сделал Spring Boot.
Простое приложение Spring имеет трехслойную структуру:
Web layer – верхний слой приложения. Он отвечает за обработку ввода пользователя и возврат корректного ответа. Также веб-слой отвечает за обработку исключений, которые могут выбрасываться в других слоях приложения. Так как веб-слой является точкой входа в приложение, он также отвечает за аутентификацию и является первой линией защиты приложения;
Service layer – слой сервисов, находится ниже веб-слоя. Этот слой содержит сервисы приложения и инфраструктуры. Сервисы приложения предоставляют публичный API сервисного слоя. Они также отвечают за транзакции и авторизацию. Инфраструктурные сервисы содержат код для взаимодействия с внешними ресурсами, такими как файловая система, базы данных, почтовые сервера и так далее. Часто эти сервисы используются несколькими сервисами приложения;
Repository layer – самый нижний слой приложения. Он отвечает за взаимодействие с используемыми хранилищами данных.
Для обработки запросов и возврата данных необходимо предусмотреть соответствующие контроллеры REST-запросов, которые и будут составлять наш веб-слой.
Контроллер – это java-класс, методы которого призваны обрабатывать HTTP-запросы. Отличие обычного контроллера от REST-контроллера заключается в том, что в REST-контроллере каждый метод класса возвращает данные вместо представления. Рассмотрим пример простого REST-контроллера. Создадим в проекте пакет controllers, внутри которого создадим класс HelloController.
Обратите внимание, что мы пометили класс аннотацией @RestController. Таким образом, мы даем знать Spring, что это не просто класс, а контроллер REST-запросов. В классе создадим метод, который будет возвращать строку.
Говорят, что методы контроллера «отображаются» на HTTP-запросы. Это значит, что при поступлении определенного HTTP-запроса (с определенным URL и HTTP-методом), будет вызван определенный метод контроллера, который вернет некоторые данные. Этим данные будут упакованы в HTTP-ответ и высланы обратно клиенту.
Нам необходимо сделать так, чтобы наш созданный метод был вызван, когда на сервер поступит HTTP-запрос с определенным URL, например http://localhost:8080/hello. Для этого необходимо пометить метод аннотацией @GetMapping c параметром (“/hello”) – часть URL, на который будет отображаться данный метод.
Для каждого из четырех основных HTTP-метода предусмотрена своя аннотация (@GetMapping, @PostMapping, @PutMapping, @DeleteMapping). Метод, помеченный определенной аннотацией, обрабатывает запросы только с определенным HTTP-методом.
Запустим сервер, заходим на http://localhost:8080/hello и видим строку с ответом.
Что произошло? Строка «hello» была помещена в тело HTTP-ответа, браузер получил text/plain с содержимым «hello» и просто вывел его на экран.
Очень часто клиенту необходимо вместе с запросом передать некоторые параметры запроса, которые уточняют и конкретизируют запрос.
Параметры запроса можно передать несколькими способами. Рассмотрим следующие способы:
указание параметра в URL-пути (localhost:8080/rooms/256);
указание параметра в строке запроса, которая идет после URL-пути и отделяется символом ? (localhost:8080/rooms?id=256¶m2=value2);
передача параметров в теле запроса (часто используется для передачи заполненной пользователем формы или передачи данных в формате JSON).
Рассмотрим, каким образом можно получить и обработать параметры запроса, переданные тем или иным способом.
При создании endpoint, в аннотации необходимо указать вариативную часть и назначить ей идентификатор
Далее необходимо предусмотреть входной аргумент метода, куда Spring запишет значение вариативной части и указать аннотацию @PathVariable для этой переменной. Также необходимо указать идентификатор, который вы указали в аннотации @GetMapping.
В рамках одного запроса может быть несколько вариативных частей, которые можно считать и обработать
В этом случае, для каждого параметра запроса создается входной аргумент, указывается аннотация @RequestParam, а также указывается имя параметра.
Если в качестве клиента выступает браузер пользователя, данные от клиента на сервер передаются в виде полей формы, которые заполняет пользователь браузера. В этом случае параметры передаются в теле запроса с помощью метода POST.
Форма может иметь следующие MIME-типы:
application/x-www-form-urlencoded
: значения кодируются в кортежах с ключом, разделенных символом '&'
, с '='
между ключом и значением. Не буквенно-цифровые символы - percent encoded: это причина, по которой этот тип не подходит для использования с двоичными данными (вместо этого используйте multipart/form-data
);
multipart/form-data
: каждое значение посылается как блок данных ("body part"), с заданными пользовательским клиентом разделителем ("boundary"), разделяющим каждую часть. Эти ключи даются в заголовки Content-Disposition
каждой части text/plain
.
Для обработки данных формы необходимо создать входной аргумент для каждого параметра, для каждого входного аргумента указать аннотацию @RequestParam, а также имя параметра.
Существует несколько более простых способов получения данных формы, но в данном курсе они не рассматриваются. Вышеуказанный способ является самым простым и понятным на данном этапе изучения курса.
Так как язык Java является ОО языком, нам было бы удобно работать с входящими и исходящими данными в объектном виде - было бы здорово, если бы REST-контроллер возвращал бы данные в виде объекта некоторого класса, а не в виде набора полей со значениями. Также было бы здорово, чтобы мы могли просто возвращать клиенту объект или коллекцию объектов некоторых классов без необходимости формировать Map из полей и значений.
Для реализации этого функционала, в Spring используется механизм сериализации и десериализации.
Сериализация - это преобразование объекта в последовательность байтов, так что объект можно легко сохранить в постоянное хранилище или передать по каналу связи. Затем поток байтов можно десериализовать - преобразовать в реплику исходного объекта.
Язык Java предоставляет стандартный механизм Java Serialization API для создания сериализуемых объектов, однако, он нам не подходит, так как ограничивает возможности для использования различных языков и технологий на стороне клиента и сервера.
Мы можем использовать сторонние библиотеки для сериализации объекта с помощью формата XML или JSON.
Использование формата JSON (http://bit.ly/32ZelBq) является более предпочтительным. Для сериализации и десериализации в Spring по-умолчанию используется библиотека Jackson.
Библиотека Jackson позволяет гибко настроить процесс сериализаци и десериализации, однако, в рамках данного курса мы будем использовать стандартные механизмы сериализации и десериализации, чтобы уделять этому процессу как можно меньше внимания.
Рассмотрим ситуацию, когда нам необходимо вернуть клиенту данные в объектном виде. Создадим класс с несколькими полями, создадим объект и вернем его в качестве результата GET-запроса.
Обратите внимание, что в код класса Room не зря были включены геттеры и сеттеры. Их наличие обязательно для сериализации и десериализации!
Используем Postman для эмуляции клиента, сделаем GET-запрос и получим следующий результат
Как мы видим, поля объекта были сериализованы с помощью формата JSON. Теперь клиент, после получения этих данных, сможет с помощью процесса десериализации получить объект и удобно работать с ним.
Теперь рассмотрим обратную ситуацию. Клиент делает POST-запрос и передает в теле запроса данные о новом студенте.
На стороне сервера создаем класс Student с соответствующими полями.
Создаем конечную точку для обработки запроса. Обратите внимание, что мы используем аннотацию @RequestBody.
Далеко не всегда ответ сервера состоит в возврате какого-то значения или какого-то объекта. Очень часто необходимо вернуть ответ с определенным HTTP-кодом и сообщением об ошибке, указать определенный заголовок и так далее.
В этом случае необходимо использовать класс ResponeEntity. Класс ResponseEntity является оберткой для ответа и дополнительно для HTTP заголовков и кода статуса. Он является обобщенным, что позволяет использовать любой тип в качестве тела ответа.
Подробную информацию по поводу ResponseEntity читайте здесь - https://www.baeldung.com/spring-response-entity.
Создадим проект с индексной страницей, на которой расположена форма добавления нового студента.
Изначально, форма не имеет средств валидации, то есть мы не можем отследить корректность заполнения формы.
Spring предоставляет несколько инструментов для реализации валидации формы, воспользуемся библиотекой Bean Validation API
Добавим библиотеку в список зависимостей в файле pom.xml
Будем использовать механизм встроенных ограничений. Алгоритм использования встроенных ограничений следующий - с помощью аннотаций необходимо указать над полем класса-сущности требуемые параметры валидации и другие параметры. В нашем случае, сущностью выступает класс Student. Добавим необходимые ограничения для полей сущности.
Как мы видим, все достаточно просто и наглядно.
Далее, нам необходимо модифицировать контроллеры и реализовать следующий функционал:
указать, что объект типа Student должен пройти валидацию;
получить результаты валидации объекта;
если объект не прошел валидацию - не добавлять объект в хранилище, выдать сообщение об ошибке в консоль.
Нам необходимо модифицировать метод контроллера, который обрабатывает данные формы. Указываем аннотацию @Valid, которая говорит о том, что полученный объект необходимо подвергнуть валидации. Далее указываем аргумент типа BindingResult, который хранит информацию о результате валидации. С помощью метода hasErrors() получаем результат валидации объекта.
При попытке отправить пустую форму, получаем сообщение в консоли
Последний шаг - необходимо предоставить пользователю информацию о том, что то или иное поле формы не прошло валидацию.
Самый простой способ проинформировать пользователь - показать сообщение об ошибке около поля, которое не прошло валидацию. Чтобы реализовать данный функционал, перейдем в шаблон index.html.
Рассмотрим поле "Фамилия". Сообщение об ошибке мы разместим снизу поля. Добавим соответствующий элемент <small> в HTML-макет.
Используем тег th:if. Если выражение внутри тега равно true, то элемент <small> будет показан на экране, если false - будет скрыт.
Выражение ${fields.hasErrors('lastName)}
означает, есть ли ошибки валидации для поля lastName
? Если ошибки есть - поле будет показано. Текст ошибки выводим с помощью атрибута th:errors.
Добавляем элементы для вывода ошибок для остальных полей формы. Проверяем результат
Ниже представлен листинг классов и файлов
Spring MVC – веб-фреймворк, призванный упростить разработку веб-приложений. Опираясь на шаблон модель–представление–контроллер (Model-View-Controller, MVC), фреймворк Spring MVC помогает строить веб-приложения, столь же гибкие и слабо связанные, как сам фреймворк Spring.
Схема работы фреймворка Spring MVC
Схема работы фреймворка выглядит следующим образом:
Краткое описание схемы работы Spring MVC звучит следующим образом:
вначале DispatcherServlet (диспетчер сервлетов) получает запрос, далее он смотрит свои настройки, чтобы понять какой контроллер использовать (на рисунке Handler Mapping);
после получения имени контроллера запрос передается на обработку в этот контроллер (на рисунке Controller). В контроллере происходит обработка запроса и обратно посылается ModelAndView (модель — сами данные; view (представление) — как эти данные отображать);
DispatcherServlet на основании полученного ModelAndView, должен определить, какое представление будет выводить данные. Для этого используется арбитр представлений (View Resolver), который на основании полученного логического имени представления возвращает ссылку на файл View;
в представление передаются данные (Model) и обратно, если необходимо, посылается ответ от представления.
Давайте рассмотрим этот процесс более подробно:
Когда запрос покидает браузер, он несет в себе информацию о требовании пользователя. По крайней мере, запрос будет нести в себе запрошенный URL. Но он может также нести дополнительные данные, такие как информация из формы, заполненной пользователем;
Первой остановкой на пути запроса является DispatcherServlet. Как и большинство веб-фреймворков на языке Java, фреймворк Spring MVC пропускает все входящие запросы через единственный сервлет входного контроллера. Входной контроллер (front controller) является типичным шаблоном проектирования веб-приложений, где единственный сервлет берет на себя ответственность за передачу всех запросов остальным компонентам приложения, выполняющим фактическую их обработку. В Spring MVC входным контроллером является DispatcherServlet;
Задача контроллера DispatcherServlet состоит в том, чтобы передать запрос контроллеру Spring MVC. Контроллер – это компонент Spring, обрабатывающий запрос. Но приложение может иметь несколько контроллеров, и входному контроллеру DispatcherServlet требуется помощь, чтобы определить, какому контроллеру передать запрос. Поэтому контроллер DispatcherServlet консультируется c одним или несколькими механизмами отображения (Handler Mapping) и выясняет, какой контроллер будет обрабатывать тот или иной запрос. При принятии решения механизм отображения в первую очередь руководствуется адресом URL в запросе;
Как только будет выбран соответствующий контроллер, DispatcherServlet отправляет запрос в путь к выбранному контроллеру. Достигнув контроллера, запрос отдаст часть своего груза (информацию, отправленную пользователем) и терпеливо будет ждать, пока контроллер обработает эту информацию. (На самом деле хорошо спроектированный контроллер сам почти не занимается обработкой информации, вместо этого он делегирует ответственность за обработку одному или нескольким служебным объектам);
В результате работы контроллера часто появляется некоторая информация, которая должна быть передана назад пользователю и отображена в браузере. Эта информация называется моделью (Model). Но отправки обратно необработанной информации недостаточно, перед отправкой ее следует представить в удобном для пользователя формате, обычно в HTML. Для этого информация должна быть передана в одно из представлений (View), которыми обычно являются JSP-страницы;
Последнее, что должен сделать контроллер, – упаковать вместе модель и имя представления для отображения результатов в браузере. Затем он отсылает запрос вместе с моделью и именем представления обратно входному контроллеру DispatcherServlet;
Чтобы контроллер не оказался тесно связанным с каким-либо конкретным представлением, имя представления, возвращаемое входному контроллеру DispatcherServlet, не определяет JSP-страницу непосредственно. Фактически оно даже не предполагает, что представление вообще является страницей JSP. Оно является лишь логическим именем представления, используемым затем для поиска фактического представления. Чтобы отобразить логическое имя представления в ссылку на конкретную реализацию, входной контроллер DispatcherServlet обратится к арбитру представлений (view resolver);
Теперь, когда контроллер DispatcherServlet определил, какое представление будет отображать результаты, работа запроса подошла к концу. Его конечная остановка – реализация представления (возможно, страница JSP), куда он доставит модель данных. На этом работа запроса заканчивается. На основе модели данных представление создаст отображение страницы, которое будет отправлено обратно клиенту с другим (не таким трудолюбивым) курьером – объектом ответа.
Рассмотренную выше схему работы фреймворка можно также представить следующей диаграммой
Создадим новый Spring Boot проект, выберем следующие модули
В новом проекте обратите внимание на структуру папок. В папке resources\templates будут содержаться html-файлы с использованием шаблонизатора Thymeleaf.
Создадим файл index.html. Обратите внимание, что в теге html необходимо указать пространство имен th для подключения тегов Thymeleaf.
В проекте Spring Boot MVC страница index будет автоматически передана при переходе на URL "/". Запустим проект и зайдем в браузер.
Создадим две html-страницы для нашего проекта.
Создадим класс контроллера, который обрабатывает GET запросы с URL "/" и "/add_student".
В файле index.html добавим ссылку для кнопки "Добавить студента" . Для формирования ссылки используем тег th:href. Для указания пути относительно домена используем комбинацию @{~}.
Проверим работу приложения в браузере
Нажмем на кнопку "Добавить студента"
Для обработки формы необходимо выполнить следующую последовательность действий:
Создать объект, поля которого будут содержать данные формы. В нашем случае создадим класс Student;
Передать пустой объект Student при переходе на страницу формы;
В полях формы настроить соответствие между полями формы и полями объекта класса Student;
После отсылки формы заполненный объект Student передается в теле HTTP-запроса с методом POST, после чего заполненный объект можно обработать, сохранить в базе данных и так далее.
Создадим класс Student
Изменим метод контроллера, который обрабатывает URL "/add_student". Передадим пустой объект студента
Изменим файл add_student.html. В полях формы добавим привязку к полям объекта, в теге формы укажем название объекта (исходя из метода контроллера он должен называться "student", а также укажем URL для отправки данных формы).
Создадим метод контроллера, который принимает POST-запрос с URL "/add_student". Данный метод будет обрабатывать результат заполнения формы.
Изменим класс Student, превратив его в сущность
Создадим интерфейс репозитория
Добавим класс сервиса для работы с DAO
Изменим класс контроллера. После получения результатов заполнения формы, данные будут сохраняться в базе данных.
ля вывода данных на странице index, необходимо выполнить следующие действия:
обратиться к базе данных для получения списка студентов;
передать список студентов в View;
предусмотреть вывод полей каждого студента в нужных ячейках таблицы.
Модифицируем метод контроллера, который отвечает за обработку запроса "\"
Модифицируем страницу index.html. Добавим вывод полей каждого объекта типа Student в таблице
Запустим приложение и посмотрим на результат. Изначально таблица студентов пустая
Добавляем нового студента
После добавления студента, нас перенаправляют на страницу index
Информацию по поводу использования библиотеки можно найти (см. раздел 8 мануала)