Создание объектов Java с помощью статических фабричных методов

Источник: Medium Это руководство объясняет использование статических фабричных методов для создания экземпляров объектов Java. Кофе-брейк #238. Создание объектов Java с помощью статических фабричных методов. Параллельное выполнение зависимых и независимых задач на Java - 1Всякий раз, когда мы хотим, чтобы какой-либо клиент получил экземпляр класса, мы создаем общедоступный конструктор класса (с параметрами или без них) и предоставляем его клиенту для создания объекта вне класса с помощью ключевого слова new. Для этого нам нужно создать внутри нативного класса или в его подклассе public static method (публичный статический метод) со значимым именем и вернуть экземпляр, который нужен клиенту. Примечание. Есть еще один более чистый способ создания экземпляра объекта. Давайте посмотрим его на примере.

public class Player {

    String name;
    int age;
    String game;

    private Player(int age, String name, String game) {
        this.age = age;
        this.name = name;
        this.game = game;
    }

    public static Player getCricketPlayer(int age, String playerName) {
        return new Player(age, playerName, "cricket");
    }

    public static Player getFootballPlayer(int age, String playerName) {
        return new Player(age, playerName, "football");
    }

    public static Player of(int age, String playerName,String game) {
        return new Player(age, playerName, game);
    }
}

public class Client {

    public static void main(String[] args) {
        var cricketPlayer = Player.getCricketPlayer(25, "Jon");
        System.out.println(cricketPlayer);

        var footballPlayer = Player.getFootballPlayer(22, "Igor");
        System.out.println(footballPlayer);
    }
}
В приведенном выше классе Player есть методы, а именно getCricketPlayer, getFootballPlayer и of. Эти методы дают четкое представление о том, какой тип объекта будет создан и возвращен. Здесь может возникнуть важный вопрос: нужно ли мне постоянно менять класс Player, если я хочу иметь другой тип объекта? Нет, как правило клиентская программа должна использовать соответствующий метод для создания объекта. Несколько встроенных в JDK статических фабричных методов для лучшего понимания:

Collections.unmodifiableCollection();

Collections.synchronizeCollection();

LocalDateTime.now();
Ключевые преимущества, которые следует учитывать при использовании статического фабричного метода:
  • Статические фабричные методы имеют имена, которые, в отличие от конструкторов, могут пояснять код.
    Хорошей практикой именования статических фабричных методов является использование таких названий, как of, from, instance, getInstance, newInstance и так далее.
  • Статические фабричные методы не требуют создания нового объекта при каждом вызове — при необходимости объекты можно кэшировать и использовать повторно.
  • Статические фабричные методы могут возвращать подтип своего возвращаемого типа — в частности, могут возвращать объект, класс реализации которого неизвестен вызывающей стороне. Это очень ценная и широко используемая функция во многих фреймворках, использующих интерфейсы в качестве возвращаемого типа статических фабричных методов.
  • Статические фабричные методы могут иметь дополнительные журналы для аудита объекта, например времени создания и другие.
Статические фабричные методы обеспечивают гибкость, инкапсуляцию и контроль над созданием объектов, что делает их мощным инструментом программирования на Java.

Параллельное выполнение зависимых и независимых задач на Java

Источник: Medium Благодаря этой публикации вы узнаете, как параллельно вызывать зависимые и независимые задачи с помощью Java CompletableFuture. В основном три сегодняшние задачи будут вызываться параллельно, так время отклика будет намного меньше. Я собираюсь показать вам, как выполнять параллельное выполнение с помощью класса CompletableFuture и Java 8.

Условия задачи

Допустим, у нас есть задача А, задача B и задача C, которые выполняются как зависимые и независимые задачи в параллельном режиме с указанными ниже условиями.
  1. Задача A и задача C могут выполняться независимо.
  2. Задача B зависит от результата задачи A.
Для этого мы воспользуемся простым сценарием приготовления кофе баристой.

Пример использования

Перед нами бариста, который готовит кофе. Ему нужно выполнить несколько заданий:
  1. Измельчить необходимое количество кофейных зерен (без предшествующих задач).
  2. Нагреть немного воды (без предшествующих задач).
  3. Приготовить эспрессо, используя молотый кофе и нагретую воду (зависит от 1 и 2).
  4. Вспенить немного молока (без предшествующих задач).
  5. Смешать вспененное молоко и эспрессо (зависит от 3,4).
Кофе-брейк #238. Создание объектов Java с помощью статических фабричных методов. Параллельное выполнение зависимых и независимых задач на Java - 2

Пример кода


package com.demo.myexamples.service;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.BiFunction;
import java.util.function.Supplier;

public class Barrista
{

    // количество потоков, используемых в executor
    static final int NOTHREADS = 3;

    // время каждого задания
    static final int HEATWATER = 1000;
    static final int GRINDBEANS = 1000;
    static final int FROTHMILK = 1000;
    static final int BREWING = 1000;
    static final int COMBINE = 1000;

    // метод имитации работы (приостановка текущего потока без выдачи проверенного исключения)
    public static void pause(long t)
    {
        try
        {
            Thread.sleep(t);
        }
        catch(Exception e)
        {
            throw new Error(e.toString());
        }
    }

    // задание по нагреву воды
    static class HeatWater implements Supplier<String>
    {
        @Override
        public String get()
        {
            System.out.println("Heating Water");
            pause(HEATWATER);
            return "hot water";
        }
    }

    // задание по измельчению зерен
    static class GrindBeans implements Supplier<String>
    {
        @Override
        public String get()
        {
            System.out.println("Grinding Beans");
            pause(GRINDBEANS);
            return "grinded beans";
        }
    }

    // задание вспенить немного молока
    static class FrothMilk implements Supplier<String>
    {
        @Override
        public String get()
        {
            System.out.println("Frothing some milk");
            pause(FROTHMILK);
            return "some milk";
        }
    }

    // задание сварить немного кофе
    static class Brew implements BiFunction<String,String, String>
    {
        @Override
        public String apply(String groundBeans, String heatedWater)
        {
            System.out.println("Brewing coffee with " + groundBeans + " and " + heatedWater);
            pause(BREWING);
            return "brewed coffee";
        }
    }

    // задание смешать кофе и молоко
    static class Combine implements BiFunction<String,String, String>
    {
        @Override
        public String apply(String frothedMilk, String brewedCoffee)
        {
            System.out.println("Combining " + frothedMilk + " "+ brewedCoffee);
            pause(COMBINE);
            return "Final Coffee";
        }
    }

    public static void main(String[] args)
    {
        ExecutorService executor = Executors.newFixedThreadPool(NOTHREADS);

        long startTime = System.currentTimeMillis();

        try
        {
            // создаем все задачи и позволяем executor обрабатывать порядок выполнения
            CompletableFuture<String> frothMilk =       CompletableFuture.supplyAsync(new FrothMilk(), executor);
            CompletableFuture<String> heatWaterFuture = CompletableFuture.supplyAsync(new HeatWater(), executor);
            CompletableFuture<String> grindBeans =      CompletableFuture.supplyAsync(new GrindBeans(), executor);

            CompletableFuture<String> brew = heatWaterFuture.thenCombine(grindBeans, new Brew());
            CompletableFuture<String> coffee =          brew.thenCombine(frothMilk,  new Combine());

            // готовое кофе
            System.out.println("Here is the coffee:" + coffee.get());

            // анализ времени:
            System.out.println("\n\n");
            System.out.println("Time taken using multi-threaded:\t\t" + (System.currentTimeMillis() - startTime)/1000.0);

            // вычислить максимально возможное время:
            long longestTime = HEATWATER + GRINDBEANS + FROTHMILK + BREWING + COMBINE;
            System.out.println("Time taken using single-threaded thread:\t" + longestTime/1000.0);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            executor.shutdown();
        }

    }
}
Вывод:
Frothing some milk Heating Water Grinding Beans Brewing coffee with hot water and grinded beans Combining brewed coffee some milk Here is the coffee:Final Coffee Time taken using multi-threaded: 3.027 Time taken using single-threaded thread: 5.0
В нашем случае общее время выполнения всех пяти задач, вызываемых в однопоточном режиме, составляет 1000 + 1000 + 1000 + 1000 + 1000 = 5000 мс. В реальной жизни, если вы ждете готовое кофе, то вам наверняка придется подождать выполнение задачи, которая занимает больше всего времени. Но если представить, что все это выполняется в многопоточном режиме, то общее время составит 3027 мс.

Заключение

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