En Java, vous pouvez utiliser le modèle de générateur pour fournir un moyen plus lisible d'instancier une classe avec de nombreux paramètres. Dans le modèle de générateur, on construit un objet de configuration avec des méthodes pour définir des attributs nommés, puis l'utilise pour construire un autre objet.
Quel est l'équivalent en Python? La meilleure façon d'imiter la même implémentation?
Les modèles de conception peuvent souvent être remplacés par des fonctionnalités de langage intégrées.
Vous dites "je voulais avoir un moyen plus lisible" pour "instancier une classe avec de nombreux paramètres". Dans le cas de Java:
Mais Python prend en charge les paramètres nommés , ce n'est donc pas nécessaire. Vous pouvez simplement définir constructeur d'une classe:
class SomeClass(object):
def __init__(self, foo="default foo", bar="default bar", baz="default baz"):
# do something
et appelez-le en utilisant des paramètres nommés:
s = SomeClass(bar=1, foo=0)
Notez que vous pouvez réorganiser et omettre librement les arguments, tout comme avec un générateur dans Java vous pouvez omettre ou réorganiser les appels aux méthodes set
sur l'objet générateur.
Il convient également de préciser que la nature dynamique de Python vous donne plus de liberté sur la construction d'objets (en utilisant __new__
Etc.), ce qui peut remplacer d'autres utilisations du modèle de générateur.
vous pouvez utiliser collections.namedtuple
comme objet de configuration. namedtuple()
renvoie un nouveau type représentant un Tuple, dont chacun des paramètres a un nom donné, sans avoir à écrire une classe passe-partout. Vous pouvez utiliser des objets du type résultant d'une manière similaire à Java builders. (Merci à Paul McGuire de l'avoir suggéré.)
StringBuilder
Un modèle associé est le StringBuilder de Java, qui est utilisé pour construire efficacement un String
(immuable) par étapes. En Python, cela peut être remplacé par str.join
. Par exemple:
final StringBuilder sb = new StringBuilder();
for(int i = 0; i < 100; i++)
sb.append("Hello(" + i + ")");
return sb.toString();
peut être remplacé par
return "".join("Hello({})".format(i) for i in range(100))
L'OP s'est mis en place pour une chute en lançant le modèle Builder comme Java spécifique. Ce n'est pas le cas. Il est dans le Livre de Gang of Four et est potentiellement pertinent pour tout objet langage orienté.
Malheureusement, même le article Wikipedia sur le modèle Builder ne lui donne pas assez de crédit. Ce n'est pas simplement utile pour l'élégance du code. Les modèles de générateur sont un excellent moyen de créer des objets immuables qui doivent être mutables jusqu'à ce qu'ils soient utilisés. L'état immuable est particulièrement critique dans les paradigmes fonctionnels, ce qui rend le Builder un excellent modèle orienté objet pour python.
J'ai fourni un exemple d'implémentation Builder + ImmutableObject ci-dessous en utilisant collections.namedtuple , emprunté et modifié de " Comment faire un objet immuable en python ". J'ai gardé le Builder assez simple. Cependant, des fonctions de définition peuvent être fournies qui renvoient le générateur lui-même pour permettre le chaînage d'appels. Ou la syntaxe @property peut être utilisée dans le générateur pour fournir des fixateurs d'attributs qui vérifient la validité des attributs avant de les définir.
from collections import namedtuple
IMMUTABLE_OBJECT_FIELDS = ['required_function_result', 'required_parameter', 'default_parameter']
class ImmutableObjectBuilder(object):
def __init__(self, required_function, required_parameter, default_parameter="foo"):
self.required_function = required_function
self.required_parameter = required_parameter
self.default_parameter = default_parameter
def build(self):
return ImmutableObject(self.required_function(self.required_parameter),
self.required_parameter,
self.default_parameter)
class ImmutableObject(namedtuple('ImmutableObject', IMMUTABLE_OBJECT_FIELDS)):
__slots__ = ()
@property
def foo_property(self):
return self.required_function_result + self.required_parameter
def foo_function(self):
return self.required_function_result - self.required_parameter
def __str__(self):
return str(self.__dict__)
Exemple d'utilisation:
my_builder = ImmutableObjectBuilder(lambda x: x+1, 2)
obj1 = my_builder.build()
my_builder.default_parameter = "bar"
my_builder.required_parameter = 1
obj2 = my_builder.build()
my_builder.required_function = lambda x: x-1
obj3 = my_builder.build()
print obj1
# prints "OrderedDict([('required_function_result', 3), ('required_parameter', 2), ('default_parameter', 'foo')])"
print obj1.required_function_result
# prints 3
print obj1.foo_property
# prints 5
print obj1.foo_function()
# prints 1
print obj2
# prints "OrderedDict([('required_function_result', 2), ('required_parameter', 1), ('default_parameter', 'bar')])"
print obj3
# prints "OrderedDict([('required_function_result', 0), ('required_parameter', 1), ('default_parameter', 'bar')])"
Dans cet exemple, j'ai créé trois ImmutableObjects, tous avec des paramètres différents. J'ai donné à l'appelant la possibilité de copier, modifier et faire circuler une configuration modifiable sous la forme du générateur tout en garantissant l'immuabilité des objets construits. La définition et la suppression d'attributs sur les ImmutableObjects entraîneront des erreurs.
Conclusion: Les constructeurs sont un excellent moyen de faire circuler quelque chose avec un état mutable qui fournit un objet avec un état immuable lorsque vous êtes prêt à l'utiliser. Ou, autrement dit, les constructeurs sont un excellent moyen de fournir des régleurs d'attributs tout en garantissant un état immuable. Ceci est particulièrement précieux dans les paradigmes fonctionnels.
Je ne suis pas d'accord avec @MechanicalSnail. Je pense qu'une implémentation de générateur similaire à celle référencée par l'affiche est toujours très utile dans certains cas. Les paramètres nommés vous permettront uniquement de définir simplement des variables membres. Si vous voulez faire quelque chose d'un peu plus compliqué, vous n'avez pas de chance. Dans mon exemple, j'utilise le modèle de générateur classique pour créer un tableau.
class Row_Builder(object):
def __init__(self):
self.row = ['' for i in range(170)]
def with_fy(self, fiscal_year):
self.row[FISCAL_YEAR] = fiscal_year
return self
def with_id(self, batch_id):
self.row[BATCH_ID] = batch_id
return self
def build(self):
return self.row
En l'utilisant:
row_FY13_888 = Row_Builder().with_fy('FY13').with_id('888').build()
Le modèle de générateur dans Java peut être facilement réalisé dans python en utilisant une variante de:
MyClass(self, required=True, someNumber=<default>, *args, **kwargs)
où required
et someNumber
sont un exemple pour afficher les paramètres requis avec une valeur par défaut, puis lire pour les arguments variables tout en gérant le cas où il pourrait y avoir None
Si vous n'avez jamais utilisé d'arguments variables auparavant, reportez-vous à this