什么是策略模式的功能类似物?

6
免责声明:我不使用函数式语言;只是尝试理解FP的某些部分。
谷歌建议查阅文章,其中使用带有lambda的一阶函数可以提供与策略模式类似的功能。
然而,我们需要匹配数据和相应的lambda。使用面向对象设计,这是通过虚方法表(VMT)自动完成的,即类型本身携带了需要推断执行流程所需的重要信息,使得添加新行为变得容易(开闭原则):继承和覆盖。旧代码保持不变。函数式模式匹配在这方面似乎是静态的,并且不允许这种动态。
当然,可以编写可配置的匹配行为来选择基于给定数据的lambda,但这不是OOP开箱即用的吗?

6
所有“策略(Strategy)”需要的只是高阶函数。事实上,您可以将策略的存在视为弥补许多面向对象编程语言中缺乏高阶函数的不足,而函数式编程语言则具备这些功能。 - molbdnilo
@molbdnilo 没问题,但是你如何选择特定数据所需的 lambda 呢? - Pavel Voronin
@voroninp 使用条件语句或类似的方法。你有更具体的例子吗?如果你提供一个使用策略模式的小型、具体的代码示例,那么说明这个问题会更容易些。 - David Young
相关问题: https://dev59.com/aYjca4cB1Zd3GeqP4fgb - Fuhrmanator
2个回答

11

最简单的方式,就是当人们谈论高阶函数替代策略模式时,他们所指的大概就是这种方式:将策略作为参数传递给通用代码。下面是一个 Scala 示例,它对两个数字执行一个策略,然后将结果乘以 3:

def commonCode(strategy: (Int, Int) => Int)(arg1: Int, arg2: Int) : Int = 
  strategy(arg1, arg2) * 3

你可以这样定义不同的策略:

def addStrategy(arg1: Int, arg2: Int) : Int      = arg1 + arg2

def subtractStrategy(arg1: Int, arg2: Int) : Int = arg1 - arg2

像这样调用:

commonCode(addStrategy)(2, 3)      // returns 15
commonCode(subtractStrategy)(2, 3) // returns -3
您可以使用部分应用来避免在多个位置传递策略:
val currentStrategy = addStrategy _
...
val currentCommon = commonCode(currentStrategy)_
currentCommon(2, 3) // returns 15

这种方法非常普遍,我们不称之为策略或者模式。它只是基本的函数式编程。传递给commonCode函数的strategy参数就像任何其他数据一样。您可以将其与其他函数一起放入数据结构中。 您可以使用闭包或部分应用程序来关联附加的针对策略的特定数据。您可以使用类似 commonCode(_ / _) 的lambda表达式来避免给策略命名。


嗯,我应该重新表述一下问题。策略模式最重要的部分是多态行为。在“传统”的面向对象设计中,它是通过VMT实现的,但当我们谈论FP时,我不清楚VMT的替代品是什么。我认为我已经在多方法/双重调度中找到了答案。 - Pavel Voronin
2
策略模式实际上并不需要多态性,只需要具有相同签名的函数。当您的语言不容易支持高阶函数时,多态性是解决方法。如何在FP中实现多态性实际上是一个单独的问题。 - Karl Bielefeldt
这是一篇关于如何使用柯里化(柯里化是一种高阶函数)在Javascript中以FP方式实现策略模式的好例子和文章:https://www.linkedin.com/pulse/strategy-pattern-functional-programming-vladim%C3%ADr-gorej/ - peterhil

1
这里有两种在Haskell中实现简单策略模式的方法。这是基于一个简单的OO示例simple OO example。它没有实现不同的行为,只是展示了它们应该放在哪里。
例1:使用带有钩子的数据结构。注意,在创建机器人时指定所需的行为。在这里,我创建了构造函数来定义我想要的不同配置的机器人。缺点是:这些不同类型的机器人共享相同的结构,因此它们的实现可能会耦合。
module Main where

data Robot = Robot {
    moveImpl :: Robot -> IO Robot
    }

move :: Robot -> IO Robot
move r = (moveImpl r) r

aggressiveMove :: Robot -> IO Robot
aggressiveMove r = putStrLn "Find another robot, then attack it!" >> return r

defensiveMove :: Robot -> IO Robot
defensiveMove r = putStrLn "Find another robot, then run away from it!" >> return r

aggressiveRobot :: Robot
aggressiveRobot = Robot aggressiveMove

defensiveRobot :: Robot
defensiveRobot = Robot defensiveMove

main = do
    let robots = [aggressiveRobot, defensiveRobot]
    mapM_ move robots
例子2:使用类型类。这可以让您采用完全不同的结构,代表不同的行为,并以统一的方式使它们工作。缺点是:您不能只将它们放在列表中,因为机器人不再是将所有不同种类的机器人绑定在一起的数据类型。
module Main where

class Robot r where
    move :: r -> IO r

data AggressiveRobot = AggressiveRobot

aggressiveMove :: AggressiveRobot -> IO AggressiveRobot
aggressiveMove r = putStrLn "Find another robot, then attack it!" >> return r

instance Robot AggressiveRobot where
    move = aggressiveMove

data DefensiveRobot = DefensiveRobot

defensiveMove :: DefensiveRobot -> IO DefensiveRobot
defensiveMove r = putStrLn "Find another robot, then run away from it!" >> return r

instance Robot DefensiveRobot where
    move = defensiveMove

main = do
    let robotA = AggressiveRobot
        robotB = DefensiveRobot
    move robotA
    move robotB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接