是否将过多的参数传递给构造函数被认为是反模式?

时间:2015-06-02 14:51:36

标签: python factory-boy

我正在考虑使用factory_boy库进行API测试。文档中的一个例子是:

class UserFactory(factory.Factory):
    class Meta:
        model = base.User

    first_name = "John"
    last_name = "Doe"

要实现这一点,我们需要将first_namelast_name等作为参数传递给__init__()的{​​{1}}方法。但是,如果您有许多参数,则会导致类似:

base.User() class

现在的问题是,这种结构是否被视为反模式,如果是,我有哪些替代方案?

由于

5 个答案:

答案 0 :(得分:7)

您可以将__init__方法的关键字参数打包到一个dict中,并直接更改实例的__dict__

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

但是,这样做有一个缺点,即如果不提及参数就不允许你提供参数 - x = User("Mr", "Kevin")可以使用原始代码,但不能使用此代码。

答案 1 :(得分:3)

在Python 3.7中,添加了dataclasses(在PEP557中指定)。由于构造函数是为您量身定制的,因此,您只能在构造函数中编写一次这些参数,而不能再次写入这些参数。

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

它还会向班级和其他班级中添加__repr__。请注意,Python 3不再需要从object显式继承,因为默认情况下所有类都是新样式类。

尽管有一些缺点。类定义的速度稍慢一些(因为需要生成这些方法)。您需要设置默认值或添加type annotation,否则会出现名称错误。如果要使用可变对象(如列表)作为默认参数,则需要使用dataclass.field(default_factory=list)(通常不建议编写def f(x=[]),但实际上会引发异常)。

答案 2 :(得分:2)

是的,有太多的论据是反模式(如RObert C. Martin的清洁法典中所述)

为避免这种情况,您有两种设计方法:

The essence pattern

The fluent interface/builder pattern

这些都是意图相似的,因为我们慢慢建立一个中间对象,然后在一个步骤中创建我们的目标对象。

我推荐使用构建器模式,它使代码易于阅读。

答案 3 :(得分:1)

最大的风险是如果你有大量的位置参数,然后最终不知道哪个是哪个..关键字参数肯定会让这更好。

正如其他人所建议的那样,构建器模式也可以很好地工作。 如果你有很多字段,你也可以做一些更通用的东西,如:

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

答案 4 :(得分:0)

如果重载不是问题,那么python中的每个类都可以简化为一个方法,我们可以将其称为doIt(....)。与所有内容一样,最好适度地进行操作。重载带有多个参数的任何方法都是不好的做法。相反,允许用户以一口大小的相关数据块来构建对象。这更合乎逻辑。在您的情况下,您可以将通话分为名称,通讯和其他内容。