За годы мониторинга производительности с помощью 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-летняя техника, лучше всего подходит для поставленной задачи.
Облегчение боли: командный образец на помощь
Основная идея этого подхода заключается в том, чтобы сначала абстрагироваться от действия, которое варьируется от варианта использования к варианту использования, чтобы отличить его от более стабильных частей алгоритма. В нашем случае это может включать воспроизведение фильма или перекодирование с использованием другого видеокодека. Таким образом, шаблон, включающий в себя скучные шаги последовательности «найти фильм, скачать что-нибудь, удалить локальный файл», будет изолирован от конкретного варианта использования. В нашем примере мы можем сделать это с помощью следующего простого интерфейса:
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 . |