1. Введение
Derive4J — это процессор аннотаций, реализующий различные функциональные концепции в Java 8.
В этом руководстве мы познакомим вас с Derive4J и наиболее важными концепциями, поддерживаемыми фреймворком:
- Алгебраические типы данных
- Сопоставление структурных шаблонов
- Первоклассная лень
2. Зависимость от Maven
Чтобы использовать Derive4J, нам нужно включить зависимость в наш проект:
<dependency>
<groupId>org.derive4j</groupId>
<artifactId>derive4j</artifactId>
<version>1.1.0</version>
<optional>true</optional>
</dependency>
3. Алгебраические типы данных
3.1. Описание
Алгебраические типы данных (ADT) являются своего рода составным типом — они представляют собой комбинации других типов или обобщений.
Обычно ADT делятся на две основные категории:
- сумма
- товар
Алгебраические типы данных присутствуют по умолчанию во многих языках, таких как Haskell и Scala.
3.2. Тип суммы
Сумма — это тип данных, представляющий логическую операцию ИЛИ. Это означает, что это может быть что-то одно, но не то и другое одновременно. Проще говоря, тип суммы — это набор различных падежей. Название «сумма» происходит от того факта, что общее количество различных значений равно общему количеству наблюдений.
Enum
ближе всего к типу суммы в Java. Enum
имеет набор возможных значений, но может иметь только одно из них за раз. Однако мы не можем связать какие-либо дополнительные данные с Enum
в Java, что является основным преимуществом алгебраических типов данных перед Enum
.
3.3. Тип продукта
Продукт — это тип данных, представляющий логическую операцию И. Это сочетание нескольких значений.
Класс
в Java можно рассматривать как тип продукта. Типы продуктов определяются комбинацией их полей в целом.
Дополнительную информацию о АТД мы можем найти в этой статье в Википедии .
3.4. Применение
Один из наиболее часто используемых алгебраических типов данных — « Либо».
Либо
можно рассматривать как более сложный необязательный
параметр, который можно использовать, когда есть возможность пропустить значения или операция может привести к исключению.
Нам нужно аннотировать абстрактный класс
или интерфейс
хотя бы одним абстрактным методом, который будет использоваться Derive4J для создания структуры нашего АТД.
Чтобы создать тип данных Both в Derive4J
, нам нужно создать интерфейс
:
@Data
interface Either<A, B> {
<X> X match(Function<A, X> left, Function<B, X> right);
}
Наш интерфейс
снабжен аннотацией @Data
, что позволит Derive4J сгенерировать для нас правильный код. Сгенерированный код содержит фабричные методы, ленивые конструкторы и различные другие методы.
По умолчанию сгенерированный код получает имя аннотированного класса
, но во множественном числе. Но есть возможность настроить это через параметр inClass
.
Теперь мы можем использовать сгенерированный код для создания АТД либо
и убедиться, что он работает правильно:
public void testEitherIsCreatedFromRight() {
Either<Exception, String> either = Eithers.right("Okay");
Optional<Exception> leftOptional = Eithers.getLeft(either);
Optional<String> rightOptional = Eithers.getRight(either);
Assertions.assertThat(leftOptional).isEmpty();
Assertions.assertThat(rightOptional).hasValue("Okay");
}
Мы также можем использовать сгенерированный метод match()
для выполнения функции в зависимости от того, какая сторона присутствует
:
public void testEitherIsMatchedWithRight() {
Either<Exception, String> either = Eithers.right("Okay");
Function<Exception, String> leftFunction = Mockito.mock(Function.class);
Function<String, String> rightFunction = Mockito.mock(Function.class);
either.match(leftFunction, rightFunction);
Mockito.verify(rightFunction, Mockito.times(1)).apply("Okay");
Mockito.verify(leftFunction, Mockito.times(0)).apply(Mockito.any(Exception.class));
}
4. Сопоставление с образцом
Одной из возможностей, доступных благодаря использованию алгебраических типов данных, является сопоставление с образцом.
Сопоставление с образцом — это механизм проверки значения по образцу. По сути, сопоставление с образцом — это более мощный оператор switch
, но без ограничений на тип сопоставления или требование, чтобы шаблоны были постоянными. Для получения дополнительной информации мы можем проверить эту статью в Википедии о сопоставлении с образцом .
Чтобы использовать сопоставление с образцом, мы создадим класс, который будет моделировать HTTP-запрос. Пользователи смогут использовать один из указанных HTTP-методов:
- ПОЛУЧИТЬ
- ПОЧТА
- УДАЛИТЬ
- ПОМЕЩАТЬ
Давайте смоделируем наш класс запросов как ADT в Derive4J, начиная с интерфейса HTTPRequest
:
@Data
interface HTTPRequest {
interface Cases<R>{
R GET(String path);
R POST(String path);
R PUT(String path);
R DELETE(String path);
}
<R> R match(Cases<R> method);
}
Сгенерированный класс HttpRequests
(обратите внимание на форму множественного числа) теперь позволит нам выполнять сопоставление шаблонов на основе типа запроса.
Для этой цели мы создадим очень простой класс HTTPServer
, который будет отвечать разным статусом
в зависимости от типа запроса.
Во-первых, давайте создадим простой класс HTTPResponse
, который будет служить ответом нашего сервера нашему клиенту:
public class HTTPResponse {
int statusCode;
String responseBody;
public HTTPResponse(int statusCode, String responseBody) {
this.statusCode = statusCode;
this.responseBody = responseBody;
}
}
Затем мы можем создать сервер, который будет использовать сопоставление с образцом для отправки правильного ответа:
public class HTTPServer {
public static String GET_RESPONSE_BODY = "Success!";
public static String PUT_RESPONSE_BODY = "Resource Created!";
public static String POST_RESPONSE_BODY = "Resource Updated!";
public static String DELETE_RESPONSE_BODY = "Resource Deleted!";
public HTTPResponse acceptRequest(HTTPRequest request) {
return HTTPRequests.caseOf(request)
.GET((path) -> new HTTPResponse(200, GET_RESPONSE_BODY))
.POST((path,body) -> new HTTPResponse(201, POST_RESPONSE_BODY))
.PUT((path,body) -> new HTTPResponse(200, PUT_RESPONSE_BODY))
.DELETE(path -> new HTTPResponse(200, DELETE_RESPONSE_BODY));
}
}
Метод acceptRequest()
нашего класса
использует сопоставление с образцом для типа запроса и будет возвращать разные ответы в зависимости от типа запроса:
@Test
public void whenRequestReachesServer_thenProperResponseIsReturned() {
HTTPServer server = new HTTPServer();
HTTPRequest postRequest = HTTPRequests.POST("http://test.com/post", "Resource");
HTTPResponse response = server.acceptRequest(postRequest);
Assert.assertEquals(201, response.getStatusCode());
Assert.assertEquals(HTTPServer.POST_RESPONSE_BODY, response.getResponseBody());
}
5. Первоклассная лень
Derive4J позволяет нам ввести понятие лени, означающее, что наши объекты не будут инициализированы до тех пор, пока мы не выполним над ними операцию. Давайте объявим интерфейс
как LazyRequest
и назовем сгенерированный класс LazyRequestImpl
:
@Data(value = @Derive(
inClass = "{ClassName}Impl",
make = {Make.lazyConstructor, Make.constructors}
))
public interface LazyRequest {
interface Cases<R>{
R GET(String path);
R POST(String path, String body);
R PUT(String path, String body);
R DELETE(String path);
}
<R> R match(LazyRequest.Cases<R> method);
}
Теперь мы можем убедиться, что сгенерированный ленивый конструктор работает должным образом:
@Test
public void whenRequestIsReferenced_thenRequestIsLazilyContructed() {
LazyRequestSupplier mockSupplier = Mockito.spy(new LazyRequestSupplier());
LazyRequest request = LazyRequestImpl.lazy(() -> mockSupplier.get());
Mockito.verify(mockSupplier, Mockito.times(0)).get();
Assert.assertEquals(LazyRequestImpl.getPath(request), "http://test.com/get");
Mockito.verify(mockSupplier, Mockito.times(1)).get();
}
class LazyRequestSupplier implements Supplier<LazyRequest> {
@Override
public LazyRequest get() {
return LazyRequestImpl.GET("http://test.com/get");
}
}
Больше информации о лени первого класса и примеры мы можем найти в документации Scala .
6. Заключение
В этом руководстве мы представили библиотеку Derive4J и использовали ее для реализации некоторых функциональных концепций, таких как алгебраические типы данных и сопоставление с образцом, которые обычно недоступны в Java.
Дополнительную информацию о библиотеке можно найти в официальной документации Derive4J .
Как всегда, все примеры кода можно найти на GitHub.