“四处执行”的成语是什么?

我一直听说的“到处执行”(或类似的)习语是什么? 为什么我可以使用它,为什么我不想使用它?

25705 次浏览

基本上,这是一种模式,你编写一个方法去做那些总是需要做的事情,例如资源分配和清理,让调用者传递“我们想要用资源做什么”。例如:

public interface InputStreamAction
{
void useStream(InputStream stream) throws IOException;
}


// Somewhere else


public void executeWithFile(String filename, InputStreamAction action)
throws IOException
{
InputStream stream = new FileInputStream(filename);
try {
action.useStream(stream);
} finally {
stream.close();
}
}


// Calling it
executeWithFile("filename.txt", new InputStreamAction()
{
public void useStream(InputStream stream) throws IOException
{
// Code to use the stream goes here
}
});


// Calling it with Java 8 Lambda Expression:
executeWithFile("filename.txt", s -> System.out.println(s.read()));


// Or with Java 8 Method reference:
executeWithFile("filename.txt", ClassName::methodName);

调用代码不需要担心打开/清理端-它将由 executeWithFile负责。

这在 Java 中是非常痛苦的,因为闭包非常冗长,从 Java 8 lambda 表达式开始就可以像其他语言一样实现(例如 C # lambda 表达式,或者 Groovy) ,这种特殊情况在 Java 7中使用 try-with-resourcesAutoClosable流处理。

虽然“分配和清理”是给出的典型示例,但是还有很多其他可能的示例——事务处理、日志记录、执行一些具有更多特权的代码等等。它基本上有点像 模板方法,但没有继承。

执行环绕方法是将任意代码传递给方法的地方,方法可以执行安装和/或拆卸代码,并在两者之间执行代码。

Java 不是我想用的语言。将闭包(或 lambda 表达式)作为参数传递更时尚。虽然对象可以说是 相当于闭包

在我看来,Execute Around Method 有点像 控制反转(依赖注入) ,每次调用这个方法时,都可以临时改变它。

但是它也可以被解释为控制耦合的一个例子(通过它的参数告诉一个方法做什么,字面意思就是在这种情况下)。

当你发现自己不得不做这样的事情时,你可以使用“围绕执行”这个成语:

//... chunk of init/preparation code ...
task A
//... chunk of cleanup/finishing code ...


//... chunk of identical init/preparation code ...
task B
//... chunk of identical cleanup/finishing code ...


//... chunk of identical init/preparation code ...
task C
//... chunk of identical cleanup/finishing code ...


//... and so on.

为了避免重复所有这些多余的代码,这些代码总是“围绕”你的实际任务执行,你可以创建一个类来自动处理它:

//pseudo-code:
class DoTask()
{
do(task T)
{
// .. chunk of prep code
// execute task T
// .. chunk of cleanup code
}
};


DoTask.do(task A)
DoTask.do(task B)
DoTask.do(task C)

这个习惯用法将所有复杂的冗余代码移动到一个位置,使您的主程序更具可读性(和可维护性!)

看一下用于 C # 示例的 这篇文章和用于 C + + 示例的 这篇文章

我看到这里有一个 Java 标记,所以我将使用 Java 作为例子,即使这个模式不是特定于平台的。

其思想是,有时候在运行代码之前和运行代码之后,您的代码总是涉及相同的样板。JDBC 就是一个很好的例子。您总是在运行实际查询和处理结果集之前获取连接并创建语句(或准备好的语句) ,然后总是在最后执行相同的样板清理——关闭语句和连接。

可执行的思想是,如果能够将样板代码分解出来,那么效果会更好。这为您节省了一些输入,但原因更为深刻。这里是不重复(DRY)原则——将代码隔离到一个位置,因此如果有 bug 或者需要更改它,或者只是想理解它,它们都在一个位置。

但是,这种因式分解有点棘手,因为您有“ before”和“ after”部分都需要查看的引用。在 JDBC 示例中,这将包括 Connection 和(预备)语句。因此,要处理这个问题,您实际上需要用样板代码“包装”目标代码。

您可能熟悉 Java 中的一些常见情况。一个是 servlet 过滤器。另一个是围绕建议的 AOP。第三个是 Spring 中的各种 xxxTemplate 类。在每种情况下,都有一些包装器对象,其中注入了“有趣的”代码(比如 JDBC 查询和结果集处理)。包装器对象执行“ before”部分,调用有趣的代码,然后执行“ after”部分。

这让我想起了 战略设计模式战略设计模式。注意,我指向的链接包含了模式的 Java 代码。

显然,可以通过编写初始化和清理代码并仅仅传递一个策略来执行“ Execute Around”,然后这个策略将始终包装在初始化和清理代码中。

与任何用于减少代码重复的技术一样,在至少有2种情况需要它之前,不应该使用它,甚至可能需要3种情况(如 YAGNI 原则)。请记住,删除代码重复可以减少维护(代码副本越少意味着在每个副本之间复制修复程序的时间越少) ,但是也会增加维护(更多的代码)。因此,这个技巧的代价就是要添加更多的代码。

这种类型的技术不仅仅适用于初始化和清理。当你想更容易地调用你的函数时,它也很有用(例如,你可以在向导中使用它,这样“下一个”和“上一个”按钮就不需要巨大的 case 语句来决定去下一个/上一个页面做什么。

如果你想要一些时髦的习语,这里有一些:

//-- the target class
class Resource {
def open () { // sensitive operation }
def close () { // sensitive operation }
//-- target method
def doWork() { println "working";} }


//-- the execute around code
def static use (closure) {
def res = new Resource();
try {
res.open();
closure(res)
} finally {
res.close();
}
}


//-- using the code
Resource.use { res -> res.doWork(); }

另见 代码三明治,它调查了许多编程语言中的这种结构,并提供了一些有趣的研究思路。关于为什么可以使用它的具体问题,上述文件提供了一些具体例子:

每当程序操纵共享资源时,就会出现这种情况。 用于锁、套接字、文件或数据库连接的 API 可能需要 程序显式关闭或释放它以前关闭或释放的资源 在没有垃圾收集的语言中,程序员是 负责在使用和释放内存之前分配内存 一般来说,各种编程任务都需要 程序进行更改,并在该更改的上下文中进行操作,以及 然后撤销改变。我们称这种情况为代码三明治。

后来:

代码三明治出现在许多编程环境中 例子涉及到稀缺资源的获取和释放, 例如锁、文件描述符或套接字连接 一般情况下,任何程序状态的临时改变可能需要 例如,一个基于 GUI 的程序可能会暂时忽略 用户输入,或操作系统内核可能暂时禁用硬件 在这些情况下,不能恢复早期状态将导致 严重的虫子。

本文没有探讨为什么 没有使用这个习语,但是描述了为什么这个习语在没有语言层面的帮助下很容易出错:

缺陷代码三明治最常出现在 异常及其相关的不可见控制流, 管理代码三明治的特殊语言特性主要出现在 支持异常的语言。

然而,异常不是造成代码缺陷的唯一原因吗 无论何时改变 尸体代码,新的控制路径 可能会出现绕过 之后代码的情况 维护人员只需要将 return语句添加到三明治的 尸体到 引入一个新的缺陷,这可能导致无声错误 代码很大,而且 之前之后相距很远,这样的错误 视觉上很难发现。

我会试着向一个四岁的孩子解释:

例子一

圣诞老人要来了。他的小精灵们在他背后编写任何他们想要的代码,除非他们做出改变,否则就会变得有点重复:

  1. 去拿包装纸
  2. 接通 超级任天堂
  3. 包起来。

或者这样:

  1. 去拿包装纸
  2. 接通 芭比娃娃
  3. 包起来。

一百万次不同的礼物注意,唯一不同的是第二步。如果第二步是唯一不同的,那为什么圣诞老人要复制代码,也就是说,为什么他要复制第一步和第三步100万次?一百万份礼物意味着他不必要地重复了一百万次第一步和第三步。

到处执行有助于解决这个问题。帮助消除代码。步骤1和步骤3基本上是不变的,允许步骤2是唯一改变的部分。

例 # 2

如果你还是不明白,这里有另外一个例子: 想想三明治: 外面的面包总是一样的,但是里面的面包会根据你选择的三明治的类型而改变。火腿、芝士、果酱、花生酱等)。面包总是放在外面的,你不需要对你做的每种沙子重复十亿次。

现在如果你读了上面的解释,也许你会发现它更容易理解。我希望这个解释对你有帮助。