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