2009-06-08 4 views
34

Dans l'entrée Wikipédia 2009 du modèle de stratégie, il y a un exemple written in PHP.Comment écrire Strategy Pattern en Python autrement que par exemple dans Wikipedia?

La plupart des autres exemples de code font quelque chose comme:

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 

Dans le code Python une autre technique est utilisée avec un bouton Soumettre. Je me demande à quoi ressemblera le code Python s'il l'a aussi fait comme les autres exemples de code.

Mise à jour: Peut-elle être plus courte en utilisant des fonctions de première classe en Python?

Répondre

56

L'exemple dans Python n'est pas si différent des autres. Pour se moquer du script 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() 

Sortie:

Original execution 
Strategy 1 
Strategy 2 

Les principales différences sont les suivantes:

  • Vous n'avez pas besoin d'écrire une autre classe ou mettre en œuvre une interface.
  • Au lieu de cela, vous pouvez passer une référence de fonction qui sera liée à la méthode que vous voulez.
  • Les fonctions peuvent toujours être utilisées séparément, et l'objet d'origine peut avoir un comportement par défaut si vous le souhaitez (le modèle if func == None peut être utilisé pour cela).
  • En effet, c'est propre et court comme d'habitude avec Python. Mais vous perdez des informations. sans interface explicite, le programmeur est supposé être un adulte pour savoir ce qu'il fait.

Notez qu'il existe 3 façons d'ajouter dynamiquement une méthode en Python:

  • La façon dont je vous ai montré. Mais la méthode sera statique, elle n'obtiendra pas l'argument "self".

  • Utilisation du nom de la classe:

    StrategyExample.execute = func

Ici, tout l'instance va se func comme méthode execute et va se self passé comme argument.

  • liaison à une instance uniquement (à l'aide du module types):

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

    ou avec Python 2, la classe de l'instance en cours de modification est également nécessaire:

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

Ceci sera bi sd la nouvelle méthode à strat0, et seulement strat0, comme avec le premier exemple. Mais obtiendra self passé en argument.

Si vous devez utiliser une référence à l'instance en cours dans la fonction, vous devez combiner la première et la dernière méthode. Si vous ne le faites pas:

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() 

Vous obtiendrez:

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 

Ainsi, le code approprié serait:

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() 

Cette sortie sera le résultat attendu:

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

Bien sûr, dans le cas où les fonctions ne peuvent pas être utilisé seul, mais peut toujours être lié à toute autre instance d'un objet, sans aucune limitation d'interface.

8

Pour plus de clarté, je continue à utiliser un pseudo-interface:

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!" 
+0

Il est un modèle de modèle, pas la stratégie que l'on – Twisty

29

Vous avez raison, l'exemple wikipedia est pas utile. Cela amalgame deux choses.

  1. Stratégie.

  2. Caractéristiques de Python qui simplifient la mise en œuvre de Stratégie. L'instruction "il n'est pas nécessaire d'implémenter ce modèle explicitement" est incorrecte. Vous avez souvent besoin d'implémenter Stratégie, mais Python simplifie cela en vous permettant d'utiliser une fonction sans la surcharge d'un wrapper de classe autour d'une fonction.

En premier lieu, Stratégie.

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 

Maintenant, vous pouvez faire des choses comme ça.

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

Et il utilisera l'objet de stratégie que nous avons créé.

Deuxièmement, les alternatives 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 

Nous pouvons le faire.

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

Ceci utilisera également une fonction de stratégie que nous avons fournie.

+0

utilisez-vous? Que recommanderiez-vous d'utiliser? – Escualo

+3

@Arrieta: utilisez les deux. Recommandez les deux. Ils ont différentes applications. Si la * Stratégie * a plusieurs méthodes ou est (en quelque sorte) en état, alors vous avez besoin de la première. Dans le cas le plus typique, le second fonctionne bien. Si vous travaillez avec beaucoup de programmeurs Java ou C++, vous devez utiliser le premier parce que le second les désoriente. –

35

Répondre à une question ancienne pour les Googlers qui ont cherché « modèle de stratégie python » et débarquèrent ...

Ce modèle est pratiquement inexistante dans les langues qui prennent en charge les fonctions premières de classe. Vous voudrez peut-être envisager de tirer parti de cette fonctionnalité en 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) 

Cette approche est très propre et simple.

Aussi, assurez-vous de vérifier PyCon Joe Gregorio 2009 de parler de modèles Python et de conception (ou son absence): http://pyvideo.org/video/146/pycon-2009--the--lack-of--design-patterns-in-pyth

+1

Alors pourquoi avons-nous des classes en python? vous avez parfois des modèles de conception sont utiles dans les projets énormes, vous ne pouvez pas échapper à cela :) – vivek

+2

Travailler [link] (http://pyvideo.org/video/146/pycon-2009--the--lack-of- -design-patterns-in-pyth). Dans l'autre vidéo est supprimée. –

+0

@vivek On n'a pas de classes en python ou dans un autre langage spécifiquement pour faciliter les modèles de conception. Beaucoup de choses sont bien représentées par les classes. Cependant, il faut que les classes représentent par ex. commande ou modèle de stratégie est une faiblesse. Toute langue avec des fermetures appropriées peut s'en passer. Et d'énormes projets ne sont pas une exigence pour les classes en particulier. Beaucoup de langues n'ont pas de classes du tout et sont pourtant utilisées pour créer de grands projets. Bien sûr, les classes sont le seul moyen d'exprimer des types de données en Python, donc vous ne pouvez pas vous en passer. –

0

J'ai essayé de convertir l'exemple « Duck » du 1er chapitre (stratégie couvrant motif) de Head First Design Pattern en 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() 
+0

Il serait préférable de définir un comportement de vol dans le constructeur ou au moins de mettre une valeur par défaut dans le constructeur. Vous pourriez oublier de le définir et d'obtenir une erreur sinon. –

Questions connexes