- Обобщение методов для разных классов
- Применение утиной типизации
- Параметрический полиморфизм и полиморфизм подтипов
- Выводы
В программировании мы часто сталкиваемся с задачами, которые требуют разнообразных решений. Например, работа с объектами разных классов, у которых похожие или одинаковые методы.
Допустим, у нас могут быть классы Article
и Topic
, оба из которых имеют методы для добавления и получения комментариев. Нам нужно написать функцию, которая работает с обоими классами.
Для этого нам нужно найти способ обобщить код так, чтобы он мог работать с любым классом, который имеет нужные методы. В этом уроке мы рассмотрим эффективный способ решения этой задачи.
Обобщение методов для разных классов
Представим, что нам нужно написать функцию, которая проверяет, есть ли комментарии у статьи или топика. Статья в коде представлена объектом класса Article
, а топик — Topic
. На первый взгляд решить данную задачу можно так:
def has_comments(commentable):
# Если это статья
if isinstance(commentable, Article):
return len(commentable.get_article_comments()) > 0
# Если это топик
elif isinstance(commentable, Topic):
return len(commentable.get_topic_comments()) > 0
class Article:
# some code
def get_article_comments(self):
return self.comments
class Topic:
# some code
def get_topic_comments(self):
return self.comments
# метод first возвращает первый объект класса Article
article = Article.first()
print(has_comments(article))
С подобным кодом мы уже сталкивались ранее. В нем мы используем условную конструкцию для определения типа объекта и вызова соответствующего метода.
Функция has_comments
принимает объект commentable
и в зависимости от его типа (Article
или Topic
) вызывает метод для получения комментариев (get_article_comments
или get_topic_comments
соответственно). Результатом выполнения функции является указание на наличие комментариев у объекта.
Классы Article
и Topic
имеют свои методы для получения комментариев: get_article_comments
и get_topic_comments
. Оба метода возвращают список комментариев к соответствующему объекту.
Подход, использованный в этом примере, не является оптимальным. При добавлении нового класса, имеющего комментарии, придется модифицировать функцию has_comments
и добавить еще одну ветку условной конструкции. Это ведет к дублированию кода и увеличению сложности его поддержки.
Мы можем заменить этот подход на диспетчеризацию по ключу, но ситуация особо не улучшится. В любом случае придется описывать поведение для каждого класса в дополнение к тому, что поведение и так описано внутри каждого класса.
В данном случае мы сталкиваемся с проблемой: у нас есть разные классы объектов, которые имеют общее поведение. При этом мы не хотим, чтобы они наследовались от общего предка.
В такой ситуации нужно использовать утиную типизацию. Она помогает сделать код более гибким и обобщенным. Также она позволяет объектам взаимодействовать друг с другом, основываясь на поведении, а не на типе данных.
Применение утиной типизации
Утиная типизация или Duck typing — это подход, который используется в динамически типизированных языках, например Python. Он описывается следующим принципом: «Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка».
В контексте программирования это означает, что важен не сам тип объекта, а его поведение или методы, которые он реализует. Если объект ведет себя определенным образом, то он может быть использован вместо других объектов, которые ведут себя так же — независимо от их фактического типа.
Рассмотрим пример с двумя классами: Article
и Topic
. Оба класса имеют методы add_comment
и get_comments
, которые добавляют комментарии и возвращают список комментариев соответственно:
class Article:
def __init__(self):
self.comments = []
def add_comment(self, comment):
self.comments.append(comment)
def get_comments(self):
return self.comments
class Topic:
def __init__(self):
self.comments = []
def add_comment(self, comment):
self.comments.append(comment)
def get_comments(self):
return self.comments
Мы можем написать функцию has_comments
, которая проверяет, есть ли комментарии у объекта, используя метод get_comments
. Эта функция будет работать как для объектов класса Article
, так и для объектов класса Topic
, поскольку оба класса реализуют метод get_comments
:
def has_comments(item):
return len(item.get_comments()) > 0
Теперь посмотрим на практике, как эта функция работает в действии. В следующем примере has_comments
будет применяться к объектам Article
и Topic
, демонстрируя принцип утиной типизации:
article = Article()
topic = Topic()
print(has_comments(article)) # => False
article.add_comment("Great article!")
print(has_comments(article)) # => True
print(has_comments(topic)) # => False
topic.add_comment("Interesting topic!")
print(has_comments(topic)) # => True
Здесь мы используем функцию has_comments
к объектам классов Article
и Topic
и видим, что она корректно работает с обоими классами, так как они оба поддерживают метод get_comments
. Это и есть утиная типизация в действии: функция has_comments
работает с любым объектом, который «крякает как утка» — реализует метод get_comments
.
Параметрический полиморфизм и полиморфизм подтипов
Способность функции обрабатывать объекты разных типов одинаковым образом, называется полиморфизмом подтипов, а сама функция – полиморфной функцией.
Как видно из кода выше, для реализации такого полиморфизма, в Python не нужно наследование и интерфейсы. В мире такой подход называют "утиной типизацией". Если что-то ходит как утка и крякает как утка, то это утка. Позже мы рассмотрим полиморфизм подтипов вместе с интерфейсами.
Технически, самое простое и понятное что делает полиморфизм подтипов (для клиентского кода) – убирает условные конструкции. Любую условную конструкцию можно заменить полиморфизмом и любую полиморфную функцию можно заменить ифами. Другими словами, полиморфизм подтипов не является неотъемлемой частью разработки, код можно писать и без него. С другой стороны, иногда встречаются ситуации, в которых он здорово помогает, но не сказать, что это происходит постоянно.
В чем же разница между параметрическим полиморфизмом и полиморфизмом подтипов? В первом случае, реализуется общий алгоритм для контейнера (например списка), который содержит значение или значения типа T. Этот алгоритм не зависит от T и для любых T выполняется идентично. Во втором, алгоритм построен вокруг самого объекта и использует его методы. В полиморфизме подтипов, полиморфная функция работает только с теми объектами, которые имеют необходимые для реализации алгоритма методы.
Выводы
Утиная типизация – это мощный инструмент в динамически типизированных языках, таких как Python. Она позволяет писать более гибкий и универсальный код, который работает с любыми объектами, предоставляющими определенный интерфейс, независимо от их типа.
Утиная типизация помогает писать более чистый, гибкий и модульный код. Но при этом стоит помнить, что не всегда нужно жертвовать ясностью и читаемостью кода ради его универсальности.
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.