簡體   English   中英

向構造函數傳遞太多參數是否被視為反模式?

[英]Is passing too many arguments to the constructor considered an anti-pattern?

我正在考慮使用 factory_boy 庫進行 API 測試。 文檔中的一個例子是:

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

    first_name = "John"
    last_name = "Doe"

為此,我們需要將first_namelast_name等作為參數傳遞給base.User() class__init__()方法。 但是,如果您有許多參數,則會導致類似以下內容:

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

現在的問題是,這種結構是否被認為是反模式,如果是,我有什么替代方案?

謝謝

在 Python 3.7 中,添加了數據類(在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 ,因為默認情況下所有類都是新式類。

不過,也有一些缺點。 它在類定義上稍慢(因為需要生成這些方法)。 您需要設置默認值或添加類型注釋,否則會出現名稱錯誤。 如果你想使用一個可變對象,比如一個列表,作為默認參數,你需要使用dataclass.field(default_factory=list) (通常不鼓勵寫 eg def f(x=[]) ,但在這里它實際上引發了一個異常)。

例如,當您必須在構造函數中包含所有這些參數時,這很有用,因為它們都屬於同一個對象並且無法提取到子對象。

您可以將__init__方法的關鍵字參數打包到一個字典中,並使用setattr動態設置它們:

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:
            setattr(self, key, kwargs.get(key))

x = User(first_name="Kevin", password="hunter2")
print(x.first_name, x.password, x.mobile)

但是,這有一個缺點,即不允許您在不命名參數的情況下提供參數 - x = User("Mr", "Kevin")適用於您的原始代碼,但不適用於此代碼。

是的,太多的論點是一種反模式(如 RObert C. Martin 在 Clean Code 中所述)

為了避免這種情況,您有兩種設計方法:

本質模式

流暢的界面/構建器模式

它們在意圖上是相似的,因為我們慢慢地建立一個中間對象,然后一步創建我們的目標對象。

我推薦構建器模式,它使代碼易於閱讀。

最大的風險是,如果您有大量的位置參數,然后最終不知道哪個是哪個.. 關鍵字參數肯定會使這更好。

正如其他人所建議的那樣,構建器模式也很有效。 如果您有大量字段,您還可以做一些更通用的事情,如下所示:

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

如果重載不是問題,那么python中的每個類都可以簡化為一個方法,我們可以將其稱為doIt(....)。 與所有事情一樣,最好適度做事。 用大量參數重載任何方法都是不好的做法。 相反,允許用戶在相關數據的一口大小塊中構建對象。 這更合乎邏輯。 在您的情況下,您可以將呼叫拆分為姓名、通信,也許還有其他。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM