Сложные программы на java

Один из часто рассматриваемых паттернов — паттерн Builder. В основном рассматриваются варианты реализации «классического» варианта этого паттерна:

MyClass my = MyClass.builder().first(1).second(2.0).third(«3»).build();

Паттерн прост и понятен как табурет, но чувствуется какая-то недосказанность — то минимальный вариант объявляется антипаттерном, то более сложные случаи игнорируются. Хотелось бы исправить этот момент, рассмотрев предельные случаи и определив минимальную и максимальную границы сложности этого паттерна.

Итак, расссмотрим их:

Минимальный builder или Реабилитация double brace initialization

Сначала рассмотрим минимальный builder, про который часто забывают — double brace initialization (
http://stackoverflow.com/questions/1958636/what-is-double-brace-initialization-in-java, http://c2.com/cgi/wiki?DoubleBraceInitialization). Используя double brace initialization мы можем делать следующее:

Не смотри обучающие ролики!


new MyClass() >
Нарушение совместимости equals

Что такое «совместимость equals»? Дело в том что стандартный equals примерно такой:

И при сравнении с унаследованным классом equals будет возвращать false. Но мы создаём анонимный унаследованный класс и вмешиваемся в цепочку наследования.

В результате обычно double brace initialization используют для инициализации составных структур. Например:

new TreeMap() >

Тут используются методы, а не прямой доступ к полям и совместимость по equals обычно не требуется. Так как же мы можем использовать такой ненадёжный хакоподобный метод? Да очень просто — выделив для double brace initialization отдельный класс билдера.

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

public static class Builder < public int first = -1 ; public double second = Double.NaN; public String third = null ; public MyClass create() < return new MyClass( first , second, third ); >>
new MyClass.Builder()>.create()

  1. Builder не вмешивается в цепочку наследования — это отдельный класс.
  2. Builder не течёт — его использование прекращается после создания объекта.
  3. Builder может контролировать параметры — в методе создания объекта.

Для использовании наследования, Builder разделяется на две части (один с полями, другой — с методом создания) следующим образом:

public class MyBaseClass < protected static class BuilderImpl < public int first = -1 ; public double second = Double.NaN; public String third = null ; >public static class Builder extends BuilderImpl < public MyBaseClass create() < return new MyBaseClass( first , second, third ); >> . > public class MyChildClass extends MyBaseClass < protected static class BuilderImpl extends MyBaseClass.BuilderImpl < public Object fourth = null; >public static class Builder extends BuilderImpl < public MyChildClass create() < return new MyChildClass( first , second, third , fourth ); >> . >

Если нужны обязательные параметры, они будут выглядеть так:

Сравнение Python и Java #программирование #shorts #айти


public static class Builder < public double second = Double.NaN; public String third = null ; public MyClass create(int first) < return new MyClass( first , second, third ); >>
new MyClass.Builder()>.create(1)

Это настолько просто, что можно использовать хоть как построитель параметров функций, например:

String fn = new fn()>.invoke();

Перейдём к сложному.

Максимально сложный Mega Builder

  1. не позволять использовать недопустимые комбинации параметров
  2. не позволять строить объект если не заполнены обязательные параметров
  3. не допускать повторной инициализации параметров

Нам понадобится интерфейс для присвоения каждого параметра и возврата нового билдера. Он должен выглядеть как-то так:

public interface TransitionNAME

При этом NAME должен быть разным для каждого интерфейса — ведь их потом надо будет объединять.

Также понадобится и getter, чтобы мы могли получить значение после такого присвоения:

public interface GetterNAME

Поскольку нам понадобится связка transition-getter, определим transition-интерфейс следующим образом:

public interface TransitionNAME

Это также добавит статического контроля в описаниях.

Примерно понятно, наборы каких интерфейсов мы собираемся перебирать. Определимся теперь, как это сделать.

Возьмём такой же как в предыдущем примере 1-2-3 класс и распишем для начала все сочетания параметров. Получим знакомое бинарное представление:

first second third — — — — — + — + — — + + + — — + — + + + — + + +

Для удобства представим это в виде дерева следующим образом:

first second third — — — / + — — /+ + + — /+/+ + + + /+/+/+ + — + /+/-/+ — + — /-/+ — + + /-/+/+ — — + /-/-/+

Промаркируем допустимые сочетания, например так:

first second third — — — / * + — — /+ * + + — /+/+ * + + + /+/+/+ + — + /+/-/+ * — + — /-/+ — + + /-/+/+ * — — + /-/-/+ *

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

first second third — — — / * + — — /+ * + + — /+/+ * + — + /+/-/+ * — + — /-/+ — + + /-/+/+ * — — + /-/-/+ *

Как же реализовать это?

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

public interface Get_first < int first (); >public interface Get_second < double second(); >public interface Get_third < String third (); >public interface Trans_first < T first (int first ); >public interface Trans_second < T second(double second); >public interface Trans_third

Читайте также:
Сборник самых нужных программ

Табличку с этим рисовать неудобно, сократим идентификаторы:

public interface G_1 extends Get_first <> public interface G_2 extends Get_second<> public interface G_3 extends Get_third <> public interface T_1 extends Trans_first <> public interface T_2 extends Trans_second <> public interface T_3 extends Trans_third <>

Нарисуем табличку переходов:

public interface B extends T_1, T_2, T_3 <> // — — — / * public interface B_1 extends T_2, T_3 <> // + — — /+ * public interface B_1_2 extends <> // + + — /+/+ * public interface B_1_3 extends <> // + — + /+/-/+ * public interface B_2 extends T_1, T_3 <> // /-/+ public interface B_2_3 extends <> // — + + /-/+/+ * public interface B_3 extends T_1, T_2 <> // — — + /-/-/+ *

Определим Built интерфейс:

public interface Built

Промаркируем интерфейсы, где уже можно построить класс интерфейсом Built, добавим getter-ы и определим получившийся Builder-интерфейс:

// транзит // | можем строить // геттеры | | // | | | // ————- ———————————- —— // // first first first // | second | second | second // | | third| | third | | third // | | | | | | | | | public interface B extends T_1, T_2, T_3, Built <> // — — — / * public interface B_1 extends G_1, T_2, T_3, Built <> // + — — /+ * public interface B_1_2 extends G_1, G_2, Built <> // + + — /+/+ * public interface B_1_3 extends G_1, G_3, Built <> // + — + /+/-/+ * public interface B_2 extends G_2, T_1, T_3 <> // /-/+ public interface B_2_3 extends G_2, G_3, Built <> // — + + /-/+/+ * public interface B_3 extends G_3, T_1, T_2, Built <> // — — + /-/-/+ * public interface Builder extends B <>

Этих описаний достаточно, чтобы по ним можно было в run-time соорудить proxy, надо только подправить получившиеся определения, добавив в них маркерные интерфейсы:

public interface Built extends BuiltBase <> public interface Get_first extends GetBase < int first (); >public interface Get_second extends GetBase < double second(); >public interface Get_third extends GetBase < String third (); >public interface Trans_first extends TransBase < T first (int first ); >public interface Trans_second extends TransBase < T second(double second); >public interface Trans_third extends TransBase

Теперь надо получить из Builder-классов значения чтобы создать реальный класс. Тут возможно два варианта — или создавать методы для каждого билдера и статически-типизированно получать параметры из каждого builder-а:

public MyClass build(B builder) < return new MyClass(-1 , Double.NaN , null); >public MyClass build(B_1 builder) < return new MyClass(builder.first(), Double.NaN , null); >public MyClass build(B_1_2 builder) < return new MyClass(builder.first(), builder.second(), null); >.

или воспользоваться обобщённым методом, определённым примерно следующим образом:

public MyClass build(BuiltValues values) < return new MyClass( // значения из values ); >

Но как получить значения?

Во-первых у нас есть по-прежнему есть набор builder-классов у которых есть нужные getter-ы. Соответственно надо проверять есть ли реализация нужного getter и если есть — приводить тип к нему и получать значение:

(values instanceof Get_first) ? ((Get_first) values).first() : -1

Конечно, можно добавить метод получения значения, но оно будет нетипизированным, так как мы не сможем получить тип значения из существующих типов:

Object getValue(final Class < ? extends GetBase>key);
(Integer) values.getValue(Get_first.class)

Для того чтобы получить тип, пришлось бы создавать дополнительные классы и связки наподобие:

public interface TypedGetter < ClassgetterClass(); >; public static final Classed GET_FIRST = new Classed(Get_first.class);

Тогда метод получения значения мог бы быть определён следующим образом:

public T get(TypedGetter typedGetter);

Но мы попытаемся обойтись тем что есть — getter и transition интерфейсами. Тогда, без приведений типов, вернуть значение можно только вернув getter-интерфейс или null, если такой интерфейс не определён для данного builder:

T get(Class key);
(null == values.get(Get_second.class)) ? Double.NaN: values.get(Get_second.class).second()

Это уже лучше. Но можно ли добавить значение по-умолчанию в случае отсутствия интерфейса, сохранив тип? Конечно, возможно возвращать типизированный getter-интерфейс, но всё равно придётся передавать нетипизированное значение по умолчанию:

T get(Class key, Object defaultValue);

Но мы можем воспользоваться для установки значения по умолчанию transition-интерфейсом:

T getDefault(Class < ? super T>key);

И использовать это следующим образом:

values.getDefault(Get_third.class).third(«1»).third()

Это всё что можно типобезопасно соорудить с существующими интерфейсами. Создадим обобщённый метод инициализации иллюстрирующий перечисленные варианты использования и проинициализируем результирующий билдер:

Теперь можно его вызывать:

builder() .build(); builder().first(1) .build(); builder().first(1).second(2) .build(); builder().second(2 ).first (1).build(); builder().first(1) .third(«3»).build(); builder().third («3»).first (1).build(); builder() .second(2).third(«3»).build(); builder().third («3»).second(2).build(); builder() .third(«3»).build();

Скачать код и посмотреть на работу context assist можно отсюда.

В частности:
Код рассматриваемого примера: MyClass.java
Пример с generic-типами: MyParameterizedClass.java
Пример не-статического builder: MyLocalClass.java.

Итого

  • Double brace initialization не будет хаком или антипаттерном, если добавить немного билдера.
  • Гораздо проще пользоваться динамическими объектами + типизированными дескрипторами доступа (см. в тексте пример с TypedGetter) чем использовать сборки интерфейсов или другие варианты статически-типизированных объектов, поскольку это влечёт за собой необходимость работы с reflection со всеми вытекающими.
  • С использованием аннотаций возможно удалось бы упростить код proxy-генератора, но это усложнило бы объявления и, вероятно, ухудшило бы выявление несоответствий в compile-time.
  • Ну и наконец, в данной статье мы окончательно и бесповоротно определили минимальную и максимальную границу сложности паттерна Builder — все остальные варианты находятся где-то между ними.
Читайте также:
Как запустить программу в virtualbox

Источник: habr.com

Сложные программы на java

Комментарии

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

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

6 книг по Java для программистов любого уровня

Подборка материалов по Java. Если вы изучаете его, то обязательно найдете для себя что-то полезное и неважно на какой стадии изучения вы находитесь.

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

Данная статья содержит не только самые распространенные алгоритмы и структуры данных, но и более сложные вещи, о которых вы могли не знать. Читаем и узнаем!

Изучаем алгоритмы: полезные книги, веб-сайты, онлайн-курсы и видеоматериалы

В этой подборке представлен список книг, веб-сайтов и онлайн-курсов, дающих понимание как простых, так и продвинутых алгоритмов.

Источник: proglib.io

Пишем Java веб-приложение на современном стеке. С нуля до микросервисной архитектуры. Часть 1

Обложка: Пишем Java веб-приложение на современном стеке. С нуля до микросервисной архитектуры. Часть 1

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

Spring Boot

Spring Boot — один из самых популярных универсальных фреймворков для построения веб-приложений на Java. Создадим в среде разработки Gradle Project. Для облегчения работы воспользуемся сайтом https://start.spring.io, который поможет сформировать build.gradle.

Для начала нам необходимо выбрать следующие зависимости:

  • Spring Web — необходим для создания веб-приложения;
  • Spring Data JPA — для работы с базами данных;
  • PostgreSQL Driver — драйвер для работы с PostgreSQL;
  • Lombok — библиотека, позволяющая уменьшить количество повторяющегося кода.

В результате генерации build.gradle должно получиться что-то похожее:

plugins < id ‘org.springframework.boot’ version ‘2.4.3’ id ‘io.spring.dependency-management’ version ‘1.0.11.RELEASE’ id ‘java’ >group ‘org.example’ version ‘1.0-SNAPSHOT’ configurations < compileOnly < extendsFrom annotationProcessor >> repositories < mavenCentral() >dependencies < implementation ‘org.springframework.boot:spring-boot-starter-web’ compileOnly ‘org.projectlombok:lombok:1.18.22’ annotationProcessor ‘org.projectlombok:lombok:1.18.22’ testImplementation ‘org.junit.jupiter:junit-jupiter-api:5.7.0’ testRuntimeOnly ‘org.junit.jupiter:junit-jupiter-engine:5.7.0’ implementation ‘org.springframework.boot:spring-boot-starter-data-jpa’ implementation ‘org.springframework.boot:spring-boot-starter-web’ runtimeOnly ‘org.postgresql:postgresql’ >test

Тот же результат можно получить и в самой IntelliJ Idea: File → New → Project → Spring Initializr.

Опишем самый простой контроллер, чтобы удостовериться, что проект работает:

Запустим проект в среде разработки или через терминал: ./gradlew bootRun .

Разработчик автотестов java Открытие , Удалённо , По итогам собеседования

Результат работы можно проверить в браузере перейдя по адресу http://localhost:8080/hello?name=World или с помощью консольной утилиты curl:

curl «http://localhost:8080/hello?name=World» Hello, World

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

Представим, что нам требуется разработать некий сервис для интернет-магазина по продаже книг. Это будет rest-сервис, который будет позволять добавлять, редактировать, получать описание книги. Хранить данные будем в БД Postgres.

Docker

Для хранения данных нам потребуется база данных. Проще всего запустить инстанс БД с помощью Docker. Docker позволяет запускать приложение в изолированной среде выполнения — контейнере. Поддерживается всеми операционными системами.

Выкачиваем образ БД и запускаем контейнер:

docker pull postgres:12-alpine docker run -d -p 5432:5432 —name db -e POSTGRES_USER=admin -e POSTGRES_PASSWORD=password -e POSTGRES_DB=demo postgres:12-alpine

Lombok

Создадим data-класс «книга». Он будет иметь несколько полей, которые должны иметь getters, конструктор и должна быть неизменяемой (immutable). Среда разработки позволяет автоматически генерировать конструктор и методы доступа к полям, но чтобы уменьшить количество однотипного кода, будем использовать Lombok.

После сборки проекта можно посмотреть, как выглядит класс после компиляции. Воспользуемся стандартной утилитой, входящей в состав JDK:

javap -private build/classes/java/main/com/example/BookStore/model/Book public final class com.example.bookstore.model.Book

Lombok очень упрощает читаемость подобного рода классов и очень широко используется в современной разработке.

Spring Data JPA

Для работы с БД нам потребуется Spring Data JPA, который мы уже добавили в зависимости проекта. Дальше нам нужно описать классы Entity и Repository. Первый соответствует таблице в БД, второй необходим для загрузки и сохранения записей в эту таблицу.

Чемпионат по‌ ‌машинному‌ ‌обучению‌, ‌искусственному‌ ‌интеллекту‌ ‌и‌ ‌большим‌ ‌данным

Чемпионат по‌ ‌машинному‌ ‌обучению‌, ‌искусственному‌ ‌интеллекту‌ ‌и‌ ‌большим‌ ‌данным
14 июля – 18 августа 2021, уже закончилось, Онлайн, Беcплатно

Класс Repository будет выглядеть совсем просто — достаточно объявить интерфейс и наследоваться от CrudRepository:

public interface BookRepository extends CrudRepository

Никакой реализации не требуется. Spring всё сделает за нас. В данном случае мы сразу получим функциональность CRUD — create, read, update, delete. Функционал можно наращивать — чуть позже мы это увидим. Мы описали DAO-слой.

Теперь нам нужен некий сервис, который будет иметь примерно следующий интерфейс:

public interface BookService < Book getBookById(Long id);// получить книгу по id ListgetAllBooks();// получить список всех книг void addBook(Book book);// добавить книгу >

Это так называемый сервисный слой. Реализуем этот интерфейс:

При создании объекта класса Spring опять всё возьмёт на себя — сам создаст объект BookRepository и передаст его в конструктор. Имея объект репозитория мы можем выполнять операции с БД:

bookRepository.findById(id); //прочитать запись из БД по первичному ключу id bookRepository.findAll(); //прочитать все записи из БД и вернуть их в виде списка bookRepository.save(bookEntity); //сохранить объект в БД

Метод findById возвращает объект типа Optional . Это такой специальный тип который может содержать, а может и не содержать значение. Альтернативный способ проверки на null , но позволяющий более изящно написать код. Метод orElseThrow извлекает значение из Optional , и, если оно отсутствует, бросает исключение, которое создается в переданном в качестве аргумента лямбда-выражении. То есть объект исключения будет создаваться только в случае отсутствия значения в Optional .

Читайте также:
Как пользоваться программой фотошоп ps

MapStruct

Смотря на код может показаться, что класс Book не нужен, и достаточно только BookEntity, но это не так. Book — это класс сервисного слоя, а BookEntity — DAO. В нашем простом случае они действительно повторяют друг друга, но бывают и более сложные случаи, когда сервисный слой оперирует с несколькими таблицами и соответственно DAO-объектами.

Если присмотреться, то и тут мы видим однотипный код, когда мы перекладываем данные из BookEntity в Book и обратно. Чтобы упростить себе жизнь и сделать код более читаемым, воспользуемся библиотекой MapStruct. Это mapper, который за нас будет выполнять перекладывание данных из одного объекта в другой и обратно. Для этого добавим зависимости в build.gradle:

dependencies

Создадим mapper, для этого необходимо объявить интерфейс, в котором опишем методы для конвертации из BookEntity в Book и обратно:

После сборки проекта, в каталоге build/generated/sources/annotationProcessor появится сгенерированный исходный код mapper, избавив нас от необходимости писать однотипные десятки строк кода:

Воспользуемся мэппером и перепишем DefaultBookService. Для этого нам достаточно добавить добавить final-поле BookMapper, которое Lombok автоматически подставит в аргумент конструктора, а spring сам инстанциирует и передаст параметром в него:

Нам также потребуется конвертировать объект AddBookRequest в объект Book. Создадим для этого BookToDtoMapper:

Теперь объявим контроллер, на эндпоинты которого будут приходить запросы на создание и получение книг, добавив зависимости BookService и BookToDtoMapper. При необходимости аналогично объекту AddBookRequest можно описать Response-объект, добавив соответствующий метод в мэппер, который будет конвертировать Book в GetBookResponse. Контроллер будет содержать 3 метода: методом POST мы будем добавлять книгу, методом GET получать список всех книг и книгу по идентификатору, который будем передавать в качестве PathVariable.

Осталось создать файл настроек приложения. Для Spring boot по умолчанию это application.properties или application.yml . Мы будем использовать формат properties. Необходимо указать настройки для соединения с БД (выше мы задавали пользователя и его пароль при старте docker-контейнера):

spring.datasource.url=jdbc:postgresql://localhost:5432/demo spring.datasource.username=admin spring.datasource.password=password spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true

Настройка spring.jpa.hibernate.ddl-auto=update указывает hibernate необходимость обновить схему когда это нужно. Так как мы не создавали никаких схем, то приложение сделает это автоматически. В процессе промышленной разработки схемы баз данных постоянно меняются, и часто используются инструменты для версионирования и применения этих изменений, например Liquibase.

Запустим наше приложение и выполним запросы на добавление книг:

curl -X POST —location «http://localhost:8080/books» -H «Content-Type: application/json» -d «< «author» : «Joshua Bloch», «title» : «Effective Java», «price» : 54.99 >» curl -X POST —location «http://localhost:8080/books» -H «Content-Type: application/json» -d «< «author» : «Kathy Sierra», «title» : «Head First Java», «price» : 12.66 >» curl -X POST —location «http://localhost:8080/books» -H «Content-Type: application/json» -d «< «author» : «Benjamin J. Evans», «title» : «Java in a Nutshell: A Desktop Quick Reference», «price» : 28.14 >»

После выполнения запросов в таблице books должны появиться записи. Чтобы удостовериться в этом, можно использовать любой удобный клиент БД. Для примера сделаем это, используя консольный клиент, входящий в состав docker-контейнера. При создании контейнера, мы указали его имя ‘db’ (если имя не задавалось, то можно вывести список всех запущенных контейнеров командой docker container ls , и дальше использовать идентификатор нужного контейнера). Для доступа к шелл-оболочке выполним:

docker exec -ti db sh

Запустим клиент БД и выполним sql-запрос:

psql —username=admin —dbname=demo psql (12.6) Type «help» for help. demo=# SELECT * FROM books; id | author | price | title —-+——————-+——-+———————————————— 1 | Joshua Bloch | 54.99 | Effective Java 2 | Kathy Sierra | 12.66 | Head First Java 3 | Benjamin J. Evans | 28.14 | Java in a Nutshell: A Desktop Quick Reference (3 rows)

Получим список всех книг:

curl -X GET —location «http://localhost:8080/books» -H «Accept: application/json» [ < «id»: 1, «author»: «Joshua Bloch», «title»: «Effective Java», «price»: 54.99 >, < «id»: 2, «author»: «Kathy Sierra», «title»: «Head First Java», «price»: 12.66 >, < «id»: 3, «author»: «Benjamin J. Evans», «title»: «Java in a Nutshell: A Desktop Quick Reference», «price»: 28.14 >]

Получим книгу через запрос к api нашего сервиса, указав идентификатор книги:

curl -X GET —location «http://localhost:8080/books/2» -H «Accept: application/json»
List findAllByAuthorContaining(String author);

В документации можно подробнее прочитать об именовании методов. Здесь мы указываем findAll — найти все записи, ByAuthor — параметр обрамляется %. При вызове этого метода (например с аргументом ‘Bloch’) будет сгенерирован следующий запрос:

SELECT * FROM books WHERE author LIKE ‘%Bloch%’;

Далее добавим метод в BookService и DefaultBookService:

А в контроллере немного модифицируем метод получения списка книг таким образом, что при передаче get-параметра author мы искали по автору, а если параметр не передётся, то используется старая логика и выводится список всех книг:

Теперь можно выполнить поиск:

curl -X GET —location «http://localhost:8080/books?author=Bloch» -H «Accept: application/json» [ < «id»: 1, «author»: «Joshua Bloch», «title»: «Effective Java», «price»: 54.99 >]

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

Код проекта доступен на GitHub.

Источник: tproger.ru

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru