如何用不同于维基百科示例的方式在Python中编写策略模式?

46 投票
5 回答
39068 浏览
提问于 2025-04-15 12:06

在2009年的维基百科关于策略模式的条目中,有一个用PHP写的例子

其他大多数代码示例都是这样做的:

a = Context.new(StrategyA.new)
a.execute #=> Doing the task the normal way

b = Context.new(StrategyB.new)
b.execute #=> Doing the task alternatively

c = Context.new(StrategyC.new)
c.execute #=> Doing the task even more alternative

在Python的代码中,使用了一种不同的技术,涉及到一个提交按钮。我在想,如果Python的代码也像其他代码示例那样写,会是什么样子。

更新:用Python的第一类函数,能不能写得更简短一些呢?

5 个回答

41

你说得对,维基百科的例子并没有帮助。它把两个概念搞混了。

  1. 策略

  2. Python的一些特性可以让实现策略变得简单。说“没有必要明确实现这个模式”是不对的。你通常需要实现策略,但Python通过允许你使用函数而不需要在函数外包裹一个类来简化这个过程。

首先,策略

class AUsefulThing( object ):
    def __init__( self, aStrategicAlternative ):
        self.howToDoX = aStrategicAlternative
    def doX( self, someArg ):
        self. howToDoX.theAPImethod( someArg, self )

class StrategicAlternative( object ):
    pass

class AlternativeOne( StrategicAlternative ):
    def theAPIMethod( self, someArg, theUsefulThing ):
        pass # an implementation

class AlternativeTwo( StrategicAlternative ):
    def theAPImethod( self, someArg, theUsefulThing ):
        pass # another implementation

现在你可以像这样做。

t = AUsefulThing( AlternativeOne() )
t.doX( arg )

它会使用我们创建的策略对象。

其次,Python的替代方案。

class AUsefulThing( object ):
    def __init__( self, aStrategyFunction ):
        self.howToDoX = aStrategyFunction
    def doX( self, someArg ):
        self.howToDoX( someArg, self )

def strategyFunctionOne( someArg, theUsefulThing ):
        pass # an implementation

def strategyFunctionTwo( someArg, theUsefulThing ):
        pass # another implementation

我们可以这样做。

t= AUsefulThing( strategyFunctionOne )
t.doX( anArg )

这也会使用我们提供的策略函数。

51

这是在回答一个老问题,专门给那些搜索“python 策略模式”的人看的...

在支持一等函数的编程语言中,这种模式几乎不存在。你可能想要利用Python中的这个特性:

def strategy_add(a, b):
    return a + b

def strategy_minus(a, b):
    return a - b

solver = strategy_add
print solver(1, 2)
solver = strategy_minus
print solver(2, 1)

这种方法非常简洁明了。

另外,别忘了去看看Joe Gregorio在2009年PyCon大会上的演讲,内容是关于Python和设计模式(或者说缺乏设计模式): http://pyvideo.org/video/146/pycon-2009--the--lack-of--design-patterns-in-pyth

80

这个Python的例子和其他语言的例子差不多。要模拟PHP脚本,可以这样做:

class StrategyExample:
    def __init__(self, func=None):
        if func:
             self.execute = func

    def execute(self):
        print("Original execution")

def executeReplacement1():
    print("Strategy 1")

def executeReplacement2():
    print("Strategy 2")

if __name__ == "__main__":
    strat0 = StrategyExample()
    strat1 = StrategyExample(executeReplacement1)
    strat2 = StrategyExample(executeReplacement2)

    strat0.execute()
    strat1.execute()
    strat2.execute()

输出结果:

Original execution
Strategy 1
Strategy 2

主要的不同点有:

  • 你不需要写其他的类或者实现任何接口。
  • 你可以直接传一个函数引用,这个函数会和你想要的方法绑定在一起。
  • 这些函数仍然可以单独使用,如果你想的话,原始对象可以有默认的行为(可以用if func == None这种方式来实现)。
  • 确实,Python的写法简洁优雅。但这样你会失去一些信息;因为没有明确的接口,程序员需要被认为是成年人,知道自己在做什么。

需要注意的是,在Python中有三种动态添加方法的方式:

  • 我刚才给你展示的方式。但这个方法是静态的,不会传递“self”参数。

  • 使用类名:

    StrategyExample.execute = func

在这里,所有的实例都会把func作为execute方法,并且会把self作为参数传递。

  • 只绑定到一个实例(使用types模块):

    strat0.execute = types.MethodType(executeReplacement1, strat0)

    或者在Python 2中,还需要指定实例的类:

    strat0.execute = types.MethodType(executeReplacement1, strat0, StrategyExample)

这样会把新方法绑定到strat0,而且只有strat0会这样。像第一个例子一样,但start0.execute()会把self作为参数传递。

如果你需要在函数中使用当前实例的引用,那么你可以把第一种和最后一种方法结合起来。如果不需要:

class StrategyExample:
    def __init__(self, func=None):
        self.name = "Strategy Example 0"
        if func:
             self.execute = func

    def execute(self):
        print(self.name)

def executeReplacement1():
    print(self.name + " from execute 1")

def executeReplacement2():
    print(self.name + " from execute 2")

if __name__ == "__main__":
    strat0 = StrategyExample()
    strat1 = StrategyExample(executeReplacement1)
    strat1.name = "Strategy Example 1"
    strat2 = StrategyExample(executeReplacement2)
    strat2.name = "Strategy Example 2"

    strat0.execute()
    strat1.execute()
    strat2.execute()

你会得到:

Traceback (most recent call last):
  File "test.py", line 28, in <module>
    strat1.execute()
  File "test.py", line 13, in executeReplacement1
    print self.name + " from execute 1"
NameError: global name 'self' is not defined

所以正确的代码应该是:

import sys
import types

if sys.version_info[0] > 2:  # Python 3+
    create_bound_method = types.MethodType
else:
    def create_bound_method(func, obj):
        return types.MethodType(func, obj, obj.__class__)

class StrategyExample:
    def __init__(self, func=None):
        self.name = "Strategy Example 0"
        if func:
             self.execute = create_bound_method(func, self)

    def execute(self):
        print(self.name)

def executeReplacement1(self):
    print(self.name + " from execute 1")

def executeReplacement2(self):
    print(self.name + " from execute 2")

if __name__ == "__main__":
    strat0 = StrategyExample()
    strat1 = StrategyExample(executeReplacement1)
    strat1.name = "Strategy Example 1"
    strat2 = StrategyExample(executeReplacement2)
    strat2.name = "Strategy Example 2"

    strat0.execute()
    strat1.execute()
    strat2.execute()

这将输出预期的结果:

Strategy Example 0
Strategy Example 1 from execute 1
Strategy Example 2 from execute 2

当然,在这种情况下,这些函数就不能再单独使用了,但仍然可以绑定到任何其他对象的实例上,没有任何接口限制。

撰写回答