[英]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_name
、 last_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.