Зарегистрируйтесь, чтобы продолжить обучение

Fluent Interface Python: Объектно-ориентированный дизайн

Fluent Interface или текучий интерфейс — это подход проектирования объектно-ориентированных API. Он симулирует естественный язык, благодаря чему повышает читаемость кода. Этот подход может быть особенно полезным в Python, который известен своей читаемостью и простотой.

В этом уроке мы рассмотрим применение Fluent Interface для повышения читаемости и гибкости кода.

Fluent Interface

Рассмотрим пример обработки коллекций:

names = Collection(['taylor', 'abigail', None])

result = names \
        # Переводим в верхний регистр
    .map(lambda name: str(name).upper() if name else '') \
        # Отфильтровываем пустые
    .reject(lambda name: name == '')

# Выводим коллекцию на экран
print(result.all())  # => ['TAYLOR', 'ABIGAIL']

Здесь мы строим цепочку вызовов, где каждый метод возвращает объект того же типа, что и первоначальный объект, но в измененной форме. Этот подход позволяет строить цепочки вызовов произвольной длины, что и обозначается термином fluent interface.

Схематически цепочка выглядит так: collection.map(...).reject(...). Это схоже с тем, как работают цепочки вызовов в JavaScript, где такой подход — основной способ строить вычисления на коллекциях.

Fluent Interface упрощает чтение и обработку данных. Для его реализации в Python существуют разные подходы. Начнем с самого простого — использования self.

self

Первый способ создания Fluent Interface основан на возврате self из методов, которые участвуют в построении цепочек. self — ссылка на тот объект, в контексте которого вызывается метод, поэтому его можно возвращать как обычное значение:

class Collection:
    def __init__(self, coll):
        self.coll = coll

    def map(self, fn):
        self.coll = list(map(fn, self.coll))
        return self

    def filter(self, fn):
        self.coll = list(filter(fn, self.coll))
        return self

    # Возвращает саму коллекцию, а не self.
    # Этот метод всегда последний в цепочке вызовов Collection.
    def all(self):
        return self.coll

cars = Collection([
    {'model': 'rapid', 'year': 2016},
    {'model': 'rio', 'year': 2013},
    {'model': 'mondeo', 'year': 2011},
    {'model': 'octavia', 'year': 2014}
])

cars.filter(lambda car: car['year'] > 2013).map(lambda car: car['model'])
print(cars.all()) # ['rapid', 'octavia']

У этого способа есть один недостаток — объект изменяется. Это значит, что нельзя взять и просто переиспользовать объект-коллекцию для разных выборок, потому что они начнут накладываться друг на друга.

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

class Collection:
    def __init__(self, coll):
        self.coll = coll

    def map(self, fn):
        return Collection(list(map(fn, self.coll)))

    def filter(self, fn):
        return Collection(list(filter(fn, self.coll)))

    # Возвращает саму коллекцию, а не self.
    # Этот метод всегда последний в цепочке вызовов Collection.
    def all(self):
        return self.coll

cars = Collection([
    {'model': 'rapid', 'year': 2016},
    {'model': 'rio', 'year': 2013},
    {'model': 'mondeo', 'year': 2011},
    {'model': 'octavia', 'year': 2014}
])

filtered_сars = cars.filter(lambda car: car['year'] > 2013)
mapped_сars = filtered_сars.map(lambda car: car['model'])
print(mapped_сars.all()) # ['rapid', 'octavia']
print(cars.all())
# [
#   {'model': 'rapid', 'year': 2016},
#   {'model': 'rio', 'year': 2013},
#   {'model': 'mondeo', 'year': 2011},
#   {'model': 'octavia', 'year': 2014}
# ]

Теперь каждый вызов возвращает новый объект. Такой код значительно безопаснее в использовании и позволяет без проблем переиспользовать новые коллекции. Изменение одной не приведет к автоматическому изменению всех остальных.

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

self.class

В каждом методе, который участвует в создании текучего интерфейса, последняя строчка всегда содержит один и тот же вызов: Collection(coll). Ее можно записать проще, не дублируя названия класса. Вместо возврата нового экземпляра класса Collection напрямую можно воспользоваться self.

В Python self используется для обозначения текущего экземпляра класса. Когда вызывается self.__class__(coll), создается новый экземпляр текущего класса, что идентично вызову Collection(coll):

class Collection:
    # ...

    def map(self, fn):
        return self.__class__(list(map(fn, self.coll)))

    # ...

Этот прием обеспечивает большую гибкость при наследовании классов, так как self.__class__ всегда ссылается на класс текущего экземпляра, а не на конкретно указанный класс.

Выводы

Применение Fluent Interface может значительно улучшить читаемость и гибкость кода. Однако следует быть осторожным при выборе между изменяемым и неизменяемым вариантами, так как оба подхода имеют свои преимущества и недостатки. Важно выбирать подход в соответствии с требованиями вашего приложения.


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
Программирование на Python, Разработка веб-приложений и сервисов используя Django, проектирование и реализация REST API
10 месяцев
с нуля
Старт 26 декабря

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»