Рубрики
Без рубрики

Java – сокращение избыточных объектов с помощью шаблона проектирования Flyweight

Создание объекта – самая фундаментальная операция в ООП. Было бы трудно сосчитать их количество… С тегами java, ооп, качество кода, программирование.

Создание объекта – самая фундаментальная операция в ООП. Было бы трудно подсчитать количество объектов, которые мы создаем (сознательно или за кулисами) даже в самых тривиальных случаях использования.

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

Давайте рассмотрим простой пример: у меня есть приложение, которое возвращает мне большое количество точек данных для построения графика. Точка данных содержит две информации – данные и то, как точка выглядит на графике

private static class DataPoint {
        private final double data;
        private final Point point;

        public DataPoint(double data, Point point) {
            this.data = data;
            this.point = point;
        }
    }

Каждая точка, в свою очередь, имеет форму и цвет:

class Point {
    private String color;
    private String shape;

    public Point(String color, String shape) {
        this.color = color;
        this.shape = shape;
    }
}

Итак, давайте создадим потребителя, который создаст для меня некоторые точки данных.

        DataPoint[] dp = new DataPoint[N];
        for(int i=0; i 0.5 ? new Point("Green", "Circle") : new Point("Red", "Cross");
            dp[i] = new DataPoint(data, point);
        }   

Выглядит просто и отлично работает. Давайте посмотрим на объем памяти, который мы использовали при создании этого массива точек данных.

  1. Объект точки данных -> 2 ссылки + заполнение => ~24 байта.
  2. В свою очередь, каждый объект DataPoint имеет объект Point, который занимает собственную память -> 2 ссылки + литералы пула строк (незначительно) + Заполнение => ~24 байта

Таким образом, общая память, используемая нашим массивом, становится *N байт. Не так уж много? – Ну, зависит от N и зависит от количества одновременных потоков. Ибо это означает 48 Кбит/с. Добавьте к нему 100 потоков => 4,8 Мбайт.

Проблема

Существует практически только два варианта точек – Зеленый круг и Красный крест но мы создали N точечных объектов.

Решение – Противовесы

Принцип прост – избегайте избыточных значений в объектах. Чтобы определить наше решение, давайте определим два термина:

  1. Повторяющиеся свойства – Свойства, которые, вероятно, останутся неизменными для многих экземпляров объекта.
  2. Уникальные свойства – Свойства, которые изменяются с каждым экземпляром объекта.

В нашем сценарии каждая половина объектов точки данных содержит одно и то же значение для точки (вероятностно).

Шаблон проектирования flyweight предполагает, что части объекта, которые, вероятно, будут повторяться среди большого количества объектов, должны совместно использоваться/использоваться повторно среди них, а не повторяться. Некоторые важные примеры использования, когда мы должны рассмотреть возможность использования противовесов:

  1. Повторяющиеся свойства тяжелы – Точечный объект в этом случае является тяжелым.
  2. Существует ограниченное число значений, которые могут принимать повторяющиеся свойства. – Одним из примеров является класс Boolean. Он может принимать только два значения true или false.

Есть много способов реализовать это. Давайте рассмотрим несколько способов реализации шаблона Flyweight.

Способ 1 – статические фабрики

Мы предоставляем статический фабричный метод для каждого из двух возможных экземпляров Point object.

class Point {
    private String color;
    private String shape;
    private static Point GREEN_CIRCLE = new Point("Green", "Circle");
    private static Point RED_CROSS = new Point("Red", "Cross");

    private Point(String color, String shape) {
        this.color = color;
        this.shape = shape;
    }

    public static Point getGreenCircle() {
        return GREEN_CIRCLE;
    }
    public static Point getRedCross() {
        return RED_CROSS;
    }
}

Особенности:

  1. Именованные методы, которые описывают тип возвращаемого объекта.
  2. Частные статические экземпляры – неизменяемые и только одна копия.
  3. Частный конструктор – чтобы запретить создание объекта извне.

Способ 2 – Перечисления

enum Point {
    GREEN_CIRCLE("Green", "Circle"),
    RED_CROSS("Red", "Cross");

    private final String color;
    private final String shape;

    Point(String color, String shape) {
        this.color = color;
        this.shape = shape;
    }
}

Особенности:

  1. Конструктор неявно является закрытым.
  2. Перечисление четко передает цель, заключающуюся в том, что возможно лишь несколько вариантов.
  3. Неизменяемые данные.

Как статические фабрики, так и перечисления создадут только 2 копии объекта Point, независимо от того, сколько раз они требуются.

Способ 3 – Кэширование

Приведенные выше два примера хорошо работают, когда все варианты уже известны. Другой сценарий может быть, когда одно из полей может принимать больше значений, чем ожидалось. Однако другие значения объекта не изменяются до тех пор, пока не изменится это изменяющееся поле.

Давайте возьмем другой пример в этом случае. Мы создаем объект продукта. Вы знаете, что для одного идентификатора продукта возможно только одно значение объекта продукта. Повторное создание объекта Product является сложной задачей, так как вам нужно задать множество свойств. Лучше сохранить копию продукта в кэше, чтобы для одного продукта не требовалось дважды создавать объект. Давайте посмотрим на код для этого.

class ProductCache {
    public static Map productMap = new HashMap<>();

    public Product getProduct(String productId) {
        Product product;
        if(productMap.containsKey(productId)) {
            product = productMap.get(productId);
        } else {
            product = new Product(/*properties*/);
            productMap.put(productId, product);
        }
        return product;
    }
}

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

Надеюсь, вам понравилось это введение в шаблон Flyweight и вы найдете способы реализовать его в своих приложениях. Спасибо за чтение.

Оригинал: “https://dev.to/abh1navv/java-reduce-redundant-objects-with-flyweight-design-pattern-3b1f”