Строитель

Описание
Строитель — порождающий шаблон проектирования, который предоставляет способ создания составного объекта для поэтапного конструирования. Предназначен для решения проблемы антипаттерна «Телескопический конструктор».

Шаблон позволяет вам создавать различные виды объекта, избегая засорения конструктора. Он полезен, когда может быть несколько видов объекта или когда необходимо множество шагов, связанных с его созданием.
Пример
Построим класс Package, который имеет 6 параметров для инициализации. На практике количество параметром может измеряться десятками.

Используем паттерн строитель и построим класс PackageBuilder, который является базовым классом для создания других строителей.

Создадим отдельные классы для предоставления группированного занесения параметров в класс Package, которые отражают свои объекты. Это класс описания посылки PackageDescription и класс получателя PackageAddressBuilder. Эти оба класса наследуются от базового класса строителя PackageBuilder. В методах классов используется команда return self, чтобы создать "текучий" способ инициализации (вызов методов в цепочки).

Таким образом создание объекта выглядит более выразительно и позволяет избегать больших конструкторов.

Теперь различные части объекта могут быть построены классами "Строителями", которые наследуются от базового класса "Строителя".

При первом вызове Строителя инициализируется объект, при последующих уже передается ссылка на существующий. За это отвечает код расположенный в методе __post_init__(self)
Пример создания класса Строителя

from dataclasses import dataclass


@dataclass
class Package:
    street_address: str = None
    postcode: str = None
    city: str = None
    specification: str = None
    weight: int = None
    size: int = None

    def __str__(self) -> str:
        return f'Адрес поставки: {self.street_address}, ' +\
               f'индекс {self.postcode}, город {self.city}\n' +\
               f'Описание {self.specification}, ' +\
               f'масса {self.weight}, размер {self.size}'


@dataclass
class PackageBuilder:
    package: Package = None

    def __post_init__(self):
        if self.package is None:
            self.package = Package()
        else:
            self.package = self.package

    @property
    def recipient(self):
        return PackageAddressBuilder(self.package)

    @property
    def description(self):
        return PackageDescription(self.package)

    def build(self):
        return self.package


@dataclass
class PackageDescription(PackageBuilder):
    package: Package

    def specification_text(self, specification):
        self.package.specification = specification
        return self

    def set_weight(self, weight):
        self.package.weight = weight
        return self

    def set_size(self, size):
        self.package.size = size
        return self


@dataclass
class PackageAddressBuilder(PackageBuilder):
    package: Package

    def recipient_street(self, street_address):
        self.package.street_address = street_address
        return self

    def set_postcode(self, postcode):
        self.package.postcode = postcode
        return self

    def set_city(self, city):
        self.package.city = city
        return self


if __name__ == '__main__':
    package = PackageBuilder()
    p = package\
            .recipient\
                .recipient_street('Сезам д.5')\
                .set_city('Екатеринбург')\
                .set_postcode('620000')\
            .description\
                .specification_text('Хрупкий')\
                .set_weight(2000)\
                .set_size(150)\
        .build()
    print(p)