什么是“执行”成语?
什么是我听说过的“执行”成语(或类似)? 为什么我可以使用它,为什么我不想使用它?
基本上就是这样一种模式,在这种模式下,你需要编写一个方法来处理总是需要的事情,比如资源分配和清理,并且让调用者通过“我们想要对资源做什么”。 例如:
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开始,lambdaexpression式可以像许多其他语言一样实现(例如C#lambdaexpression式或Groovy),而这个特殊情况是从Java 7开始用try-with-resources
和AutoClosable
stream。
尽pipe“分配和清理”是一个典型的例子,但还有很多其他可能的例子 – 事务处理,日志logging,执行一些具有更多权限的代码等等。它基本上与模板方法模式有些类似,但是没有inheritance。
当你发现自己不得不做这样的事情时,使用Execute Around idiom:
//... 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.
为了避免重复所有总是在你的实际任务“周围”执行的冗余代码,你需要创build一个类来自动处理:
//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是一个很好的例子。 在运行实际查询和处理结果集之前,您总是获取一个连接并创build一个语句(或准备语句),然后总是在最后执行相同的样板清理 – closures语句和连接。
执行的想法是,如果你能分解样板代码,那么这样做会更好。 这可以节省你一些打字,但原因是更深。 这里就是“不要重复”(DRY)原则 – 将代码隔离到一个位置,所以如果有错误或者需要修改,或者只是想了解它,那么它们都在一个地方。
这种分解方法有点棘手,但是你可以参考“之前”和“之后”两部分需要看到的内容。 在JDBC示例中,这将包括连接和(准备)语句。 所以要处理你基本上用目标代码“包装”你的目标代码。
您可能熟悉Java中的一些常见情况。 一个是servletfilter。 另一个是AOP的build议。 第三个是Spring中的各种xxxTemplate类。 在每种情况下,你都有一些包装对象,你的“有趣的”代码(比如说JDBC查询和结果集处理)被注入到其中。 包装器对象执行“之前”部分,调用有趣的代码,然后执行“之后”部分。
另请参阅Code Sandwiches ,它通过许多编程语言对此构造进行了调查,并提供了一些有趣的研究思路。 关于为什么要使用它的具体问题,上面的文章提供了一些具体的例子:
只要程序操作共享资源就会出现这种情况。 用于锁,套接字,文件或数据库连接的API可能需要程序明确closures或释放之前获取的资源。 在没有垃圾收集的语言中,程序员负责在使用之前分配内存,并在使用之后释放内存。 通常,各种编程任务都要求程序进行更改,在更改的上下文中进行操作,然后撤消更改。 我们把这种情况称为三明治。
然后:
代码三明治出现在许多编程情况。 几个常见的例子涉及稀缺资源的获取和释放,例如locking,文件描述符或套接字连接。 在更一般的情况下,程序状态的任何暂时改变可能需要代码三明治。 例如,基于GUI的程序可能暂时忽略用户input,或者OS内核可能会暂时禁用硬件中断。 在这些情况下未能恢复到以前的状态将会导致严重的错误。
本文不探讨为什么不使用这个成语,但它确实描述了为什么这个成语在没有语言层面的帮助的情况下很容易出错:
在出现exception和相关的不可见控制stream时,缺陷代码三明治出现最频繁。 事实上,pipe理代码三明治的特殊语言特征主要出现在支持exception的语言中。
但是,例外情况不是代码三明治不合格的唯一原因。 无论何时对体代码进行更改,都可能会出现绕过后代码的新控制path。 在最简单的情况下,维护人员只需要向三明治的主体添加一个
return
语句来引入一个新的缺陷,这可能会导致无声的错误。 当体码大, 前后分开时,这种错误很难被视觉识别。
Execute Around方法是将任意代码传递给一个方法的地方,该方法可以执行设置和/或拆卸代码并在其间执行代码。
Java不是我select这样做的语言。传递闭包(或lambdaexpression式)作为参数更加时尚。 虽然对象可以等同于封闭 。
在我看来,执行周围方法有点像控制反转 (dependency injection),你可以随意改变,每次你调用方法。
但它也可以被解释为控制耦合的一个例子(在这种情况下,通过它的论点告诉一个方法该怎么做)。
这让我想起了策略devise模式 。 请注意,我指出的链接包括模式的Java代码。
显然,可以通过初始化和清理代码来执行“Execute Around”,只是传入一个策略,然后总是被包装在初始化和清理代码中。
和任何用来减less代码重复的技术一样,在你至less有2个需要的情况下,甚至3个(YAGNI原则),你都不应该使用它。 请记住,删除代码重复会减less维护(代码的副本数越less意味着在每个副本上复制修复所花费的时间越less),同时也增加了维护(更多的代码总数)。 因此,这个技巧的代价就是你增加了更多的代码。
这种types的技术不仅仅是初始化和清理很有用。 当你想要更容易地调用你的函数时(例如,你可以在向导中使用它,以便“下一个”和“上一个”button不需要巨大的case语句来决定如何去做下一页/上一页。
我会试着解释一下,就像我四岁那样:
例1
圣诞老人来到城里 他的小精灵在他背后想要的任何东西,除非他们改变了一些重复的东西:
- 拿包装纸
- 获得超级任天堂 。
- 把它包起来
或这个:
- 拿包装纸
- 得到芭比娃娃 。
- 把它包起来
….有一百万次,有一百万个不同的礼物,要注意:唯一不同的是第二步。如果第二步是唯一不同的,那么为什么圣诞老人复制这个代码,也就是他为什么要复制步骤1和3百万次? 一百万份礼物意味着他不必要地重复步骤1和3百万次。
执行周围有助于解决这个问题。 并有助于消除代码。 步骤1和步骤3基本不变,只允许步骤2变化。
例#2
如果还是不明白的话,还有一个例子:想一想:外面的面包总是一样的,但是里面的东西会根据你select的三明治的types而变化(例如,火腿,奶酪,果酱,花生酱等)。 面包总是在外面,你不需要重复那十亿次你正在创造的每种types的沙子。
现在,如果你阅读上面的解释,也许你会觉得更容易理解。 我希望这个解释对你有帮助。
如果你想要groovy成语,这里是:
//-- 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(); }