Если вы когда-нибудь читали Шаблоны проектирования раньше, возможно, вы слышали о классическом шаблоне, который называется Заводской метод. Этот шаблон делает базовый класс более гибким, позволяя его дочерним элементам указывать, какие объекты создавать. Вместо того, чтобы жестко запрограммировать имена объектов в родительском классе, они передаются потомкам. Этот шаблон упрощает расширение класса и обеспечивает более слабосвязанные объекты.

Например, у вас есть родительский класс Bicycle. Вы хотите, чтобы у каждого Bicycle был комплект шин (надеюсь). Таким образом, каждый раз, когда инициализируется новый Bicycle, вы автоматически присваиваете tires GenericTires. Кажется, все в порядке? Но как быть, если вам нужен другой тип Bicycle с другим типом tires? Что делать, если интерфейс GenericTires изменится или вы захотите полностью его заменить? Вам необходимо обновить родительский класс Bicycle. Необходимость прочесывать код и обновлять жестко запрограммированные элементы, как это, утомительно, негибко и подвержено ошибкам.

Цель использования фабричного метода - сделать объекты более расширяемыми и абстрактными, чтобы их было проще использовать. Практическое правило:

Создавайте объекты в отдельной операции, чтобы подклассы могли переопределить способ их создания. - Шаблоны проектирования (1994)

Именно это мы и исследуем, как делать в Python.

Первоначальный дизайн

В этом первом примере мы рассмотрим, как обычно выглядит фрагмент кода, в котором можно применить фабричный метод. Мы воспользуемся нашим удобным Bicycle примером, потому что кто не любит прокатиться на велосипеде. Мне также легче понять подобные концепции, когда классы моделируют физические вещи, с которыми мы знакомы. Sandi Metz также часто использует сборку велосипеда в примере кода, и я большой поклонник аналогии. Давайте погрузимся.

class Bicycle:
    def __init__(self):
        self.tires = GenericTires()
    def get_tire_type(self):
        return self.tires.tire_type()
class GenericTires:
    def tire_type(self):
        return 'generic'
bike = Bicycle()
print(bike.get_tire_type())

В этом примере родительский класс Bicycle инициализируется набором GenericTires путем прямой ссылки на класс. Это делает соединение между Bicycle и GenericTires более плотным и жестким. Возможно создание различных типов велосипедов, но в этом примере замена их шин не является интуитивно понятной.

Допустим, мы хотим построить новый вид велосипеда. Горный велосипед. Наверное, у него должны быть специальные шины, чтобы справиться с бездорожьем, верно? Как мы можем настроить базовый Bicycle класс, чтобы сделать его более гибким для разных типов шин?

Вот немного более гибкий дизайн:

class Bicycle:
    def __init__(self):
        self.tires = self.add_tires()
    def add_tires(self):
        return GenericTires()
    def get_tire_type(self):
        return self.tires.tire_type()
class MountainBike(Bicycle):
    def add_tires(self):
        return MountainTires()
class GenericTires:
    def tire_type(self):
        return 'generic'
class MountainTires:
    def tire_type(self):
        return 'mountain'
mountain_bike = MountainBike()
print(mountain_bike.get_tire_type())

В этом примере мы добавили новый add_tires() метод для возврата правильного класса шины. Это позволяет нам переопределить этот метод в любом из подклассов Bicycle и предоставить другой класс шин. Чтобы добавить новый велосипед и шину, вам нужно только добавить новые классы и переопределить метод. Вам не нужно вносить изменения в базовый класс. Затем давайте посмотрим, как расширить это действие, когда у вас есть более сложные детали велосипеда.

Более гибкий дизайн

Здесь мы демонстрируем более гибкий и «дружественный к клиенту» дизайн, аналогичный паттерну «Абстрактная фабрика». В этом примере к родительскому классу добавляется параметр factory, который принимает своего рода «фабрику деталей» для указания типа шин или других деталей, которые необходимо создать.

class Bicycle:
    def __init__(self, factory):
        self.tires = factory().add_tires()
        self.frame = factory().add_frame()
class GenericFactory:
    def add_tires(self):
        return GenericTires()
    def add_frame(self):
        return GenericFrame()
class MountainFactory:
    def add_tires(self):
        return RuggedTires()
    def add_frame(self):
        return SturdyFrame()
class RoadFactory:
    def add_tires(self):
        return RoadTires()
    def add_frame(self):
        return LightFrame()
class GenericTires:
    def part_type(self):
        return 'generic_tires'
class RuggedTires:
    def part_type(self):
        return 'rugged_tires'
class RoadTires:
    def part_type(self):
        return 'road_tires'
class GenericFrame:
    def part_type(self):
        return 'generic_frame'
class SturdyFrame:
    def part_type(self):
        return 'sturdy_frame'
class LightFrame:
    def part_type(self):
        return 'light_frame'
bike = Bicycle(GenericFactory)
print(bike.tires.part_type())
print(bike.frame.part_type())
mountain_bike = Bicycle(MountainFactory)
print(mountain_bike.tires.part_type())
print(mountain_bike.frame.part_type())
road_bike = Bicycle(RoadFactory)
print(road_bike.tires.part_type())
print(road_bike.frame.part_type())

Мы больше не указываем тип компонентов для установки в родительском классе. Добавление деталей обрабатывается каждым соответствующим методом add_tires() и add_frame() внутри фабрики деталей. Фабрика деталей - это просто отдельный класс, который обрабатывает добавление каждой конкретной детали. Родительский класс Bicycle вызывает методы для добавления деталей в класс фабрики деталей. Каждый экземпляр по-прежнему Bicycle, но построен из разных частей.

Есть также некоторые дополнительные шаги, которые вы можете предпринять, чтобы еще больше абстрагироваться от предыдущего набора классов:

  • Обобщить add_tires() и add_frame() до add_part('type')
  • Создайте метод в родительском классе Bicycle для отображения всех частей, связанных с велосипедом. Вы можете добиться этого, просто получив каждый part_type() или переместив self.tires и self.frame в список или карту частей для перебора.
  • Прямо сейчас Bicycle ожидает, что будет передана фабрика деталей. Вы можете включить GenericFactory внутри класса в качестве назначений по умолчанию, которые могут быть отменены новыми фабриками.

Эти примеры - лишь некоторые из способов написания и расширения шаблонов Factory в Python. Есть и другие варианты. Какой из них вы выберете, полностью зависит от размера проекта и сложности класса. В некоторых случаях, возможно, вам просто нужно переопределить один метод в родительском, в других вам понадобится специальный фабричный класс для создания экземпляров нескольких объектов. В конечном итоге, какой бы вариант вы ни выбрали, он должен казаться естественным и обеспечивать простой расширяемый интерфейс для клиентов.

Спасибо за внимание! Надеюсь, вам понравилось узнать, как работает шаблон проектирования Factory Method в Python. Если вам не нужны более увлекательные руководства по Python, ознакомьтесь с 5 строковыми методами Python для лучшего форматирования.