Как написать шаблон стратегии в Python по-другому, чем пример в Википедии?

В записи Wikipedia 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?

Ответ 1

Пример в Python не так отличается от других. Чтобы издеваться над PHP script:

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

Конечно, в случае, если функции больше не могут использоваться автономно, но все равно могут быть привязаны к любому другому экземпляру любого объекта без каких-либо ограничений интерфейса.

Ответ 2

Отвечая на старый вопрос для Гуглеров, которые искали "шаблон стратегии 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 PyCon 2009 о Python и шаблонах проектирования (или их отсутствии): http://pyvideo.org/video/146/pycon-2009--the--lack-of--design-patterns-in-pyth p >

Ответ 3

Вы правы, пример wikipedia не помогает. Он объединяет две вещи.

  • Стратегия.

  • Особенности 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 )

Это также будет использовать предусмотренную нами стратегическую функцию.

Ответ 4

Для ясности я все равно буду использовать псевдо-интерфейс:

class CommunicationStrategy(object):
    def execute(self, a, b):
        raise NotImplementedError('execute')

class ConcreteCommunicationStrategyDuck(CommunicationStrategy):
    def execute(self, a, b):
        print "Quack Quack"

class ConcreteCommunicationStrategyCow(CommunicationStrategy):
    def execute(self, a, b):
        print "Mooo"

class ConcreteCommunicationStrategyFrog(CommunicationStrategy):
    def execute(self, a, b):
        print "Ribbit! Ribbit!"

Ответ 5

Я попытался преобразовать пример "Утки" из 1-й главы (охватывающий шаблон стратегии) ​​Head First Design Pattern в Python:

class FlyWithRocket():
    def __init__(self):
        pass
    def fly(self):
        print 'FLying with rocket'

class FlyWithWings():
    def __init__(self):
        pass
    def fly(self):
        print 'FLying with wings'

class CantFly():
    def __init__(self):
        pass
    def fly(self):
        print 'I Cant fly'

class SuperDuck:
    def __init__(self):
        pass
    def setFlyingBehaviour(self, fly_obj):
        self.fly_obj = fly_obj
    def perform_fly(self):
        self.fly_obj.fly()

if __name__ == '__main__':
    duck = SuperDuck()
    fly_behaviour = FlyWithRocket()
    #fly_behaviour = FlyWithWings()
    duck.setFlyingBehaviour(fly_behaviour)
    duck.perform_fly()