实现“依赖注入”的技术

techniques to implement "dependency injection"

提问人:MrD at KookerellaLtd 提问时间:10/6/2023 最后编辑:MrD at KookerellaLtd 更新时间:10/12/2023 访问量:95

问:

我所说的“依赖注入”是指将某种指针传递给代码的 OO 概念。在 FP 中,这将使用高阶函数来完成。

在 xslt 2+ 中,似乎有这些替代方案

  • 传递函数(根据函数式编程)
  • 传递 psuedo 'modes',即传递一个显式构造的元素(或嵌入在某个节点结构中的值)并匹配它。

(还有其他替代方案可以使用include/import静态注入代码,其中include样式表中的模板会覆盖包含的样式表或与包含的样式表进行交互,但这更类似于“模板方法”,即静态组合)

XSLT 社区中是否有这方面的惯用/模式?

(能够以某种方式“捆绑”注入会很好,就像接口或类型类一样,这似乎可以通过将“模式”作为元素或值嵌入到另一个元素中来使用 psuedo 模式技术,或者“模式”是“捆绑”,但显然不是直接使用函数)


我没有给出明确的例子,如果你需要它们来说明我的意思,请告诉我。


实际上还有第三种选择,你返回一个 DSL,然后“解释它”,不同的上下文以不同的方式解释,这可能类似于“流水线”技术,虽然我非常喜欢它,但当你只想注入一个微小的细微差别时,它有点矫枉过正。


例子:

(都使用此 XML - 这无关紧要

<root>
    <foo/>
</root>

)

#1 “高阶函数”(我认为这可能是从 Martin Honnen 之前对 HOF 的回答中窃取的)

函数“apply-function”允许“注入”一个函数,然后它执行,显然任何具有兼容签名的函数都可以被注入。这是默认的 FP 技术。

<xsl:stylesheet version="3.0" 
    xmlns:my="my"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
    <!-- Define a function that accepts a function parameter -->
    <xsl:function name="my:apply-function" as="xs:integer">
        <xsl:param name="input" as="xs:integer"/>
        <xsl:param name="function" as="function(xs:integer) as xs:integer"/>
        <xsl:sequence select="$function($input)"/>
    </xsl:function>
    
    <!-- Define a sample function to be passed as a parameter -->
    <xsl:function name="my:double" as="xs:integer">
        <xsl:param name="value" as="xs:integer"/>
        <xsl:sequence select="$value * 2"/>
    </xsl:function>
    
    <!-- Main template -->
    <xsl:template match="/">
        <result>
            <!-- Call my:apply-function and pass my:double as a parameter -->
            <xsl:variable name="result" select="my:apply-function(5, my:double#1)"/>
            <doubled-value>
                <xsl:value-of select="$result"/>
            </doubled-value>
        </result>
    </xsl:template>
</xsl:stylesheet>

#2 “伪装模式”

这样,你就可以在某个结构中显式地对你的“选择”进行编码,然后匹配它。

即“函数指针”被编码为

<Mode value='double'/>

我怀疑这种技术有更好的标签,但对我来说,模拟运行时“模式”感觉像是一种机械主义。

<xsl:stylesheet version="3.0" 
    xmlns:my="my"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
    <xsl:template mode="inject" match="Mode[@value = 'double']">
        <xsl:param name="value" as="xs:integer"/>
        <xsl:sequence select="$value * 2"/>
    </xsl:template>

    <xsl:template mode="inject" match="Mode[@value = 'treble']">
        <xsl:param name="value" as="xs:integer"/>
        <xsl:sequence select="$value * 3"/>
    </xsl:template>
    
    <!-- Main template -->
    <xsl:template match="/">
        <result>
            <doubled-value>
                <xsl:variable name="mode">
                    <Mode value='double'/>
                </xsl:variable>
                <xsl:apply-templates mode="inject" select="$mode">
                    <xsl:with-param name="value" select="5"/>                    
                </xsl:apply-templates>
            </doubled-value>
        </result>
    </xsl:template>
</xsl:stylesheet>

#3 DSL/解释器

在这里,“callee”代码返回一些由my:makeDSL编码为xml的领域特定语言,然后由客户端特定的intepreter解释。

在这里,depedency并没有真正注入,它被外化,然后以被调用者想要的方式被嵌入,但结果是一样的。

(“nop”说明仅供说明)

<xsl:stylesheet version="3.0" 
    xmlns:my="my"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
    <xsl:template mode="inject" match="Mode[@value = 'double']">
        <xsl:param name="value" as="xs:integer"/>
        <xsl:sequence select="$value * 2"/>
    </xsl:template>

    <xsl:template mode="inject" match="Mode[@value = 'treble']">
        <xsl:param name="value" as="xs:integer"/>
        <xsl:sequence select="$value * 3"/>
    </xsl:template>
    
    <xsl:template mode="double" match="nop"/>
        
    <xsl:template mode="double" match="inject">
        <xsl:sequence select="2 * @value"/>
    </xsl:template>
    
    <xsl:template mode="treble" match="nop"/>
    
    <xsl:template mode="treble" match="inject">
        <xsl:sequence select="3 * @value"/>
    </xsl:template>
    
    <!-- Main template -->
    <xsl:template match="/">
        <result>
            <doubled-value>
                <xsl:apply-templates mode="double" select="my:makeDSL(5)"/>
            </doubled-value>
        </result>
    </xsl:template>
    
    <xsl:function name="my:makeDSL">
        <xsl:param name="value"/>
        <nop/>
        <inject value="{$value}"/>
        <nop/>
    </xsl:function>
</xsl:stylesheet>
XSLT-3.0

评论

0赞 Martin Honnen 10/6/2023
如果您使用高阶函数,例如声明/使用某种类型的函数参数(例如,这不是某种类型约束,就像依赖注入(注入某种类型/接口)一样)?<xsl:param name="my-func" as="function(item(), node()?) as node()*"/>
0赞 MrD at KookerellaLtd 10/6/2023
您正在注入 1 个函数?在 OO 中,你注入了 N 个方法,而在 FP 世界中,你可以注入嵌入在某个数据结构(如类型类)中的 N 个函数。
0赞 MrD at KookerellaLtd 10/6/2023
如果你使用 psuedo 模式的东西(你可能有一个更好的术语),那么你可以将 psuedo 模式名称嵌入到一些元素结构中,尽管它有点非类型化
0赞 Martin Honnen 10/6/2023
让我们看看其他人怎么说,我已经把这个问题提请了 xml.com Slack 社区的注意,所以也许一些对 DI 和函数式编程以及 XSLT 有更深入理解的软件工程师确实会出现在这里。
0赞 MrD at KookerellaLtd 10/6/2023
实际上,我最近是 xml.com 的一员,但我仍然来到这里。我添加了 FP 中使用的第 3 种替代方案,但感觉有点矫枉过正,在 FP 中,您经常混合和匹配这些技术,在 XSLT 中它迫使您考虑它,并且由于模板全局存在,太多的“解释器”使代码混乱

答:

0赞 MrD at KookerellaLtd 10/12/2023 #1

目前,答案似乎是否定的(没有惯用的方法),至少不是有意识的。

流水线模式我怀疑是惯用的,但在更一般的意义上,该语言鼓励一种类似于 OO 模板方法(即静态注入)的方法,其中包含/导入样式表并通过模式覆盖模板。

这并不是说它无法实现。

问题中的技术都是适用的,类似于“字典传递”的技术可以通过在 Maps 中嵌入函数来使用。

这是一个使用“monoid”的简单示例,即可以加在一起的事物。

我们有 2 个编码为 map 的 monoid“字典”,一个用于整数,一个用于字符串,我们将每个字典传递(注入)到一些代码中,这些代码“添加”一些值并返回答案。

输入 xml 是这样的

<root>
    <foo value1="123" value2="456"/>
</root>

样式表将 Foo 元素第一个处理为整数,然后处理为字符串

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:kooks="kookerella.com"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    exclude-result-prefixes="#all"
    version="3.1">
    
    <xsl:function name="kooks:stringAdd" as="xs:string">
        <xsl:param name="s1" as="xs:string"/>
        <xsl:param name="s2" as="xs:string"/>
        <xsl:sequence select="concat($s1,$s2)"/>
    </xsl:function>
    
    <xsl:variable name="stringMonoid" as="map(*)">
        <xsl:map>
            <xsl:map-entry key="'zero'" select="''" as="xs:integer"/>
            <xsl:map-entry key="'add'" select="kooks:stringAdd#2"/>
        </xsl:map>
    </xsl:variable>
    
    <xsl:function name="kooks:integerAdd" as="xs:integer">
        <xsl:param name="i1" as="xs:integer"/>
        <xsl:param name="i2" as="xs:integer"/>
        <xsl:sequence select="$i1 + $i2"/>
    </xsl:function>
    
    <xsl:variable name="integerMonoid" as="map(*)">
        <xsl:map>
            <xsl:map-entry key="'zero'" select="0" as="xs:integer"/>
            <xsl:map-entry key="'add'" select="kooks:integerAdd#2"/>
        </xsl:map>
    </xsl:variable>
    
    <xsl:template match="/">
        <root>
            <integer>
                <xsl:apply-templates select="root/foo">
                    <xsl:with-param name="monoid" select="$integerMonoid"/>
                </xsl:apply-templates>
            </integer>
            <string>
                <xsl:apply-templates select="root/foo">
                    <xsl:with-param name="monoid" select="$stringMonoid"/>
                </xsl:apply-templates>
            </string>
        </root>    
    </xsl:template>
    
    <xsl:template match="foo">
        <xsl:param name="monoid"/>
        
        <result 
            value1="{@value1}" 
            value2="{@value2}" 
            zero="{map:get($monoid,'zero')}"
            sum="{map:get($monoid,'add')(@value1,@value2)}"/>
    </xsl:template>
</xsl:stylesheet>

这导致了这个

<root>
   <integer>
      <result value1="123" value2="456" zero="0" sum="579"/>
   </integer>
   <string>
      <result value1="123" value2="456" zero="" sum="123456"/>
   </string>
</root>

当然,这只是将函数作为参数传递的变体,即高阶函数,但是将函数嵌入映射中的能力使得将它们打包成“类型类”之类的东西成为可能。

(注意 OOers,这些伪装类型类不是对象,如果你把它们看作是对象,那么它们就是没有状态的对象,它们的目的只是打包函数,而不是封装状态)