¿Pasar demasiados argumentos al constructor es considerado un anti-patrón?

Estoy considerando usar la librería factory_boy para las pruebas de API. Un ejemplo de la documentación es:

class UserFactory(factory.Factory): class Meta: model = base.User first_name = "John" last_name = "Doe" 

Para que esto funcione, necesitamos que first_name , last_name , etc. se pasen como parámetros al método __init__() de la base.User() class . Sin embargo, si tienes muchos parámetros esto lleva a algo como:

 class User(object): GENDER_MALE = 'mr' GENDER_FEMALE = 'ms' def __init__(self, title=None, first_name=None, last_name=None, is_guest=None, company_name=None, mobile=None, landline=None, email=None, password=None, fax=None, wants_sms_notification=None, wants_email_notification=None, wants_newsletter=None, street_address=None): self. title = title self.first_name = first_name self.last_name = last_name self.company_name = company_name self.mobile = mobile self.landline = landline self.email = email self.password = password self.fax = fax self.is_guest = is_guest self.wants_sms_notification = wants_sms_notification self.wants_email_notification = wants_email_notification self.wants_newsletter = wants_newsletter self.company_name = company_name self.street_address = street_address 

Ahora la pregunta es, ¿esta construcción se considera antipatrón y, en caso afirmativo, qué alternativas tengo?

Gracias

    Puede empaquetar los __init__ las palabras clave del método __init__ en un dict, y alterar los __dict__ la instancia directamente:

     class User(object): GENDER_MALE = 'mr' GENDER_FEMALE = 'ms' def __init__(self, **kwargs): valid_keys = ["title", "first_name", "last_name", "is_guest", "company_name", "mobile", "landline", "email", "password", "fax", "wants_sms_notification", "wants_email_notification", "wants_newsletter","street_address"] for key in valid_keys: self.__dict__[key] = kwargs.get(key) x = User(first_name="Kevin", password="hunter2") print x.first_name, x.password, x.mobile 

    Sin embargo, esto tiene el inconveniente de no permitirle proporcionar argumentos sin nombrarlos – x = User("Mr", "Kevin") trabaja con su código original, pero no con este código.

    En Python 3.7, se agregaron las clases de datos (especificadas en PEP557 ). Esto le permite escribir solo estos argumentos una vez y no nuevamente en el constructor, ya que el constructor está hecho para usted:

     from dataclasses import dataclass @dataclass class User: title: str = None first_name: str = None last_name: str = None company_name: str = None mobile: str = None landline: str = None email: str = None password: str = None fax: str = None is_guest: bool = True wants_sms_notification: bool = False wants_email_notification: bool = False wants_newsletter: bool = False street_address: str = None 

    También agrega un __repr__ a la clase así como a algunos otros. Tenga en cuenta que en Python 3 ya no es necesario heredar explícitamente de un object , ya que todas las clases son clases de estilo nuevo de forma predeterminada.

    Sin embargo, hay algunos inconvenientes. Es un poco más lento en la definición de clase (ya que estos métodos deben generarse). Debe establecer un valor predeterminado o agregar una anotación de tipo , de lo contrario obtendrá un error de nombre. Si desea usar un objeto mutable, como una lista, como un argumento predeterminado, necesita usar dataclass.field(default_factory=list) (normalmente no se recomienda escribir, por ejemplo, def f(x=[]) , pero aquí está En realidad plantea una excepción).

    Sí, demasiados argumentos es un antipattern (según lo establecido en Clean Code por RObert C. Martin)

    Para evitar esto, tienes dos enfoques de diseño:

    El patrón de la esencia

    La interfaz fluida / patrón de constructor

    Ambos son similares en su intención, ya que construimos lentamente un objeto intermedio y luego creamos nuestro objeto objective en un solo paso.

    Recomiendo el patrón de construcción, hace que el código sea fácil de leer.

    El mayor riesgo sería si tuviera un gran número de argumentos posicionales y terminara sin saber cuál es cuál. Los argumentos de palabras clave definitivamente lo mejoran.

    Como lo sugieren otros, el patrón de construcción también funciona bastante bien. Si tiene una gran cantidad de campos, también puede hacer algo más genérico, como por ejemplo:

     class Builder(object): def __init__(self, cls): self.attrs = {} self.cls = cls def __getattr__(self, name): if name[0:3] == 'set': def setter(x): field_name = name[3].lower() + name[4:] self.attrs[field_name] = x return self return setter else: return super(UserBuilder, self).__getattribute__(name) def build(self): return self.cls(**self.attrs) class User(object): def __str__(self): return "%s %s" % (self.firstName, self.lastName) def __init__(self, **kwargs): # TODO: validate fields for key in kwargs: setattr(self, key, kwargs[key]) @classmethod def builder(cls): return Builder(cls) print (User.builder() .setFirstName('John') .setLastName('Doe') .build()) # prints John Doe