【发布时间】:2018-05-31 19:06:22
【问题描述】:
与 TCL 合作,我想实现类似 Strategy Pattern 的东西。我想在 TCL 函数中传递打印输出的“策略”,这样我就可以轻松地在打印到屏幕和打印到日志文件之间切换。在 TCL 中执行此操作的最佳方法是什么?
【问题讨论】:
-
如果您对实现策略模式有疑问,请不要在 TCL 中询问函数指针。
标签: design-patterns function-pointers tcl
与 TCL 合作,我想实现类似 Strategy Pattern 的东西。我想在 TCL 函数中传递打印输出的“策略”,这样我就可以轻松地在打印到屏幕和打印到日志文件之间切换。在 TCL 中执行此操作的最佳方法是什么?
【问题讨论】:
标签: design-patterns function-pointers tcl
TCL 允许您将过程的名称存储在一个变量中,然后使用该变量调用该过程;所以
proc A { x } {
puts $x
}
set strat A
$strat Hello
将调用proc A并打印出Hello
【讨论】:
除了显示如何将过程分配给变量的答案之外,您还可以将过程的名称作为参数传递给另一个过程。这是一个简单的例子:
proc foo { a } {
puts "a = $a"
}
proc bar { b } {
puts "b = $b"
}
proc foobar { c } {
$c 1
}
foobar foo
foobar bar
这将打印 a = 1 和 b = 1
【讨论】:
上面列出的稍微扩展的示例可以更清楚地说明策略模式:
proc PrintToPDF {document} {
<snip logic>
}
proc PrintToScreen {document} {
<snip logic>
}
proc PrintToPrinter {document} {
<snip logic>
}
set document "my cool formatted document here"
set printMethod "printer"
switch -- $printMethod {
"printer" {
set pMethodName "PrintToPrinter"
}
"pdf" {
set pMethodName "PrintToScreen"
}
"screen" {
set pMethodName "PrintToPDF"
}
}
$pMethodName $document
【讨论】:
除了使用 proc,您实际上还可以使用代码块。这有一些变化。首先是最明显的,只需 evaling 即可。
set strategy {
puts $x
}
set x "Hello"
eval $strategy
unset x
这可行,但也有一些缺点。首先很明显,两段代码必须共同使用参数的通用命名。这用另一种(本地)替换了一个令人头疼的命名空间(procs),这实际上可以说是更糟糕。
不太明显的是 eval 故意解释它的参数而不编译字节码。这是因为假设 eval 将使用动态生成的、通常是唯一的参数调用,并且如果字节码只使用一次,相对于立即解释块,编译为字节码的效率会很低。这更容易解决,所以这是成语:
set x "Hello"
if 1 $strategy
unset x
if 与eval 不同,它会编译和缓存它的代码块。如果$strategy 块只是一个或几个不同的可能值,那么这非常有效。
这对于将参数传递给带有局部变量的块的讨厌没有任何帮助。有很多方法可以解决这个问题,例如执行substitutions,就像tk 用% 替换命令参数一样。您可以尝试使用 uplevel 或 upvar 做一些骇人听闻的事情。例如,您可以这样做:
set strategy {
puts %x
}
if 1 [string map [list %% % %x Hello] $strategy]
如果传递的参数变化不大,这在字节码编译方面效果很好。另一方面,如果参数经常更改,则应使用eval 而不是if 1。就论点而言,这无论如何也好不了多少。由于您使用的是特殊语法,因此不太可能混淆已通过和未通过的内容。如果您想在返回代码块之前使用变量替换,这也很有帮助:如set strategy "$localvar %x"。
幸运的是,tcl 8.5 有true anonymous functions,使用apply 命令。 apply 命令的第一个词是参数和正文的列表,就好像proc 的那些参数已被取出。其余参数立即作为参数传递给匿名命令。
set strategy [list {x} {
puts $x
}]
apply $strategy "Hello"
【讨论】:
% set val 4444
4444
% set pointer val
val
% eval puts $$pointer
4444
% puts [ set $pointer ]
4444
% set tmp [ set $pointer ]
4444
【讨论】:
如何使用变量函数?我不记得太多 TCL(已经有一段时间了......)但也许其中一个可以满足您的需要:
如果我错了,任何人都可以纠正我。
【讨论】:
要阐明 Jackson 的方法为何有效,请记住在 TCL 中,everything 是一个字符串。无论您使用的是文字字符串、函数、变量还是其他任何东西,everything 都是字符串。您可以像传递“数据指针”一样传递“函数指针”:只需使用不带前导“$”的对象名称即可。
【讨论】:
上述所有内容,尽管在从命名空间移动到命名空间时,您可能希望将[namespace current ]::proc_name 用作传递的[namespace current ]::proc_name,以确保不会出现任何中断。
对于 OO 方法,您需要关注此线程中的内容:Pass a method of a specific object as an input argument in Tcl
神速。
【讨论】: