Статьи

Утечки ресурсов: шаблон команд на помощь

За годы мониторинга производительности с помощью Plumbr я столкнулся с сотнями проблем производительности, вызванных утечками ресурсов. В этом посте я хотел бы описать один из самых простых способов приблизиться к очистке ресурса и избежать проблемы.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public class MoviePlayer {
  private final Catalog catalog = new Catalog();
 
  public void play(String movieName, String screen) {
    Movie movie = catalog.find(movieName);
    try {
      movie.fetch();
      movie.play(screen);
    } finally {
      movie.release();
    }
  }
}
 
class Catalog {
  Movie find(String name) {
    return new Movie(name);
  }
}

Как видите, класс MoviePlayer , являющийся клиентом класса Catalog , должен заботиться о всем жизненном цикле воспроизведения фильма. Поиск, загрузка, воспроизведение и удаление файла принадлежат реализации класса MoviePlayer .

Здесь кроется первая проблема: если хотя бы один из таких клиентов был написан каким-то неосторожным разработчиком, который забывает вызывать метод movie.release () , загруженный файл будет оставлен на вашем локальном диске. Таким образом, каждый воспроизводимый фильм добавляет еще один файл, и дисковое пространство на устройстве в конечном итоге будет исчерпано.

Вторая проблема с таким кодом «мастер на все руки» раскрывается, когда вводится дополнительная функциональность. Например, представьте, что вам нужно добавить возможность регистрировать фактическое время воспроизведения фильма.

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

Учитывая, что MoviePlayer должен быть в основном связан с воспроизведением фильма, это, безусловно, звучит как слишком много дополнительных хлопот. Действительно, так что давайте попробуем убрать весь этот беспорядок из MoviePlayer, чтобы убедиться, что у нас есть класс с единственной ответственностью. Модель командного проектирования, 20-летняя техника, лучше всего подходит для поставленной задачи.

Облегчение боли: командный образец на помощь

моделей-в-Java-командный шаблон
Основная идея этого подхода заключается в том, чтобы сначала абстрагироваться от действия, которое варьируется от варианта использования к варианту использования, чтобы отличить его от более стабильных частей алгоритма. В нашем случае это может включать воспроизведение фильма или перекодирование с использованием другого видеокодека. Таким образом, шаблон, включающий в себя скучные шаги последовательности «найти фильм, скачать что-нибудь, удалить локальный файл», будет изолирован от конкретного варианта использования. В нашем примере мы можем сделать это с помощью следующего простого интерфейса:

1
2
3
interface MovieCommand {
  void execute(Movie movie);
}

Вышеуказанное изменение включает в себя введение нового метода с одним дополнительным параметром типа MovieAction . В этом методе весь алгоритм выполняется:

  • Фильм находится.
  • Фильм загружен.
  • Действие или команда, переданная методу, выполняется в фильме. Определенное действие — теперь единственная переменная часть, изменяющаяся от варианта использования к варианту использования.
  • И, наконец, освобождается дескриптор файла фильма и выполняется очистка от временных файлов.

Теперь, если нам нужно каким-либо образом изменить алгоритм, мы сделаем это точно в одном месте, не затрагивая ни одного из клиентов, которые все еще озабочены только своими конкретными действиями, такими как воспроизведение фильма или кодирование. Наш пример MoviePlayer теперь будет таким простым:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
class Catalog {
 
  private Movie find(String name) {
    return new Movie(name);
  }
 
  void withMovie(String movieName, MovieCommand action) {
    Movie movie = find(movieName);
    try {
      movie.fetch();
      action.execute(movie);
    } finally {
      movie.release();
    }
  }
}

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

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

Ссылка: Утечки ресурсов: шаблон команды на помощь от нашего партнера по JCG Никиты Сальникова Тарновского в блоге Plumbr Blog .