Как написать шаблон стратегии в Python иначе, чем пример в Википедии? - PullRequest
37 голосов
/ 08 июня 2009

В записи Википедии 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 ]

65 голосов
/ 08 июня 2009

Пример в 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

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

39 голосов
/ 19 января 2012

Отвечая на старый вопрос для пользователей Google, которые искали "шаблон стратегии 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)

Этот подход очень чистый и простой.

Кроме того, обязательно ознакомьтесь с докладом Джо Грегорио о PyCon 2009 о Python и шаблонах проектирования (или его отсутствии): http://pyvideo.org/video/146/pycon-2009--the--lack-of--design-patterns-in-pyth

31 голосов
/ 08 июня 2009

Вы правы, пример из Википедии бесполезен. Это объединяет две вещи.

  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 )

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

9 голосов
/ 08 июня 2009

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

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!"
1 голос
/ 03 мая 2016

Я пытался преобразовать пример 'Duck' из 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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...