Card 06/ 09
Builder Classes vs Keyword Arguments in Python
Named parameters with defaults already solve the telescoping constructor in Python.
In Python, keyword arguments with default values give every parameter a name at the call site, which removes the problem the Builder pattern was invented for. This card writes a report with optional sections without a builder, and names the cases where a builder still earns its class.
from dataclasses import dataclass
from typing import Optional
@dataclass(frozen=True)
class Report:
title: str
summary: Optional[str] = None
chart: Optional[str] = None
footer: Optional[str] = None
def __post_init__(self) -> None:
if not self.title:
raise ValueError("a report needs a title")
print(Report("Weekly sales", chart="orders_by_day", footer="Generated on Monday"))
print(Report(title="Stock check"))
try:
Report("", summary="no title")
except ValueError as error:
print("refused:", error)Report(title='Weekly sales', summary=None, chart='orders_by_day', footer='Generated on Monday')
Report(title='Stock check', summary=None, chart=None, footer=None)
refused: a report needs a titleEach optional part has a default of None, so a call names only the parts it sets, in any order. The dataclass is frozen, so the report cannot change after it is created, and __post_init__, which a dataclass calls after its generated constructor, is the one place that checks it, as build() is in a builder. This gives the report everything a builder would, with one class.
Keyword argumentswhen every part is known at the call
There is one class and one call. Names and defaults make a long parameter list readable, and validation goes in __init__ or __post_init__. This is the default for a Python object with many optional parts.
A builderwhen the object is assembled over time
Parts arrive across several statements or a loop, a part can be added many times, such as the conditions of a query, or one sequence of steps must produce more than one representation. A builder holds the unfinished state until build().
Use it when
- The object is put together over several statements, with parts added conditionally.
- A part accumulates, such as filters or rows, rather than being set once.
- The same steps must drive more than one representation, which is the Gang of Four's case.
Avoid it when
- Every part is known where the object is created, which keyword arguments already handle.
- The only goal is readable names at the call site.
← → move through sections and cards