多年来, 使用Plumbr进行性能监视时,我遇到了数百个资源泄漏引起的性能问题。 在这篇文章中,我想描述一种最简单的方法来清理资源并避免该问题。
首先,我以电影播放器应用程序为例来描述问题。 这种应用程序的主要功能自然是在播放电影本身。 按照当今的习惯,我们不想将整个电影收藏存储在用于播放电影的设备上。 相反,我们将电影下载到本地临时文件中,然后播放并删除该文件以释放下一部电影的空间。 这种工作流程的简单实施如下所示:
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类。 如果Catalog类有其他客户,则每个客户都需要引入更改。 结果, MoviePlayer的每个附加功能都变得越来越大,处理越来越多的独立问题。 结果,代码最终将难以理解和更改。
考虑到MoviePlayer应该主要只是处理电影的播放,所以听起来确实有太多额外的麻烦。 确实,因此让我们尝试将所有这些混乱的内容从MoviePlayer中移出,以确保我们拥有一个负责任的班级。 命令设计模式是20年的技术,最适合手头的任务。
减轻痛苦:救援的命令模式
该方法背后的基本思想是首先抽象出因使用案例而异的操作,以使其与算法中更稳定的部分区分开。 在我们的情况下,这可能涉及电影播放或使用不同的视频编解码器进行重新编码。 因此,包括“查找电影-下载-执行某些操作-删除本地文件”的乏味步骤的样板将与特定用例隔离。 在我们的示例中,我们可以使用以下简单界面执行此操作:
interface MovieCommand {
void execute(Movie movie);
}
上面的更改包括引入一种带有MovieAction类型的附加参数的新方法。 在此方法中,将执行整个算法:
- 电影已找到。
- 电影已下载。
- 传递给该方法的动作或命令在影片上执行。 现在,特定操作是随用例而变化的唯一可变部分。
- 最后,释放电影的文件句柄并执行对临时文件的清理。
现在,如果需要以任何方式更改算法,我们将只在一个地方进行操作,而不会影响仍仅关注其特定动作(例如电影播放或编码)的任何客户端。 现在,我们的MoviePlayer示例非常简单:
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进行救援之前,都是噩梦般的处理顺序。
同样,可以通过使用命令模式,从不必要的样板中清除代码并大大简化代码库,在自定义代码库中实现不同方面,例如安全检查或缓存。
翻译自: https://www.javacodegeeks.com/2016/09/resource-leakages-command-pattern-rescue.html