
Java — первый язык, который я выучил в своей карьере. Его структура является основополагающей в мои ранние годы понимания концепций программирования. Пройдя через несколько других языков с очень разными подходами, я расширил свою точку зрения. Сегодня я хочу поразмышлять над идеей наследования .
В Java идея наследования тесно связана с концепцией подтипирования . Подтипирование — это реализация отношения IS A. Например, класс Rabbit
является подтипом класса Mammal
. Отныне экземпляр Rabbit
имеет все поведение Mammal
: он наследует поведение.
Из-за этого вы можете передать экземпляр Rabbit
, когда метод вызывает параметр Mammal
, или вернуть экземпляр Rabbit
, когда тип возвращаемого значения метода — Mammal
. Если вы изучали Java, .Net или что-то отдаленно похожее, то именно так вы видите наследование, и оно становится новой нормой.
Это явное наследование .
class Animal { void feed(); } class Rabbit extends Animal { //1 }
Rabbit
IS A
Animal
, его можно feed()
Когда я впервые посмотрел на Go, я был поражен тем, что в нем нет подтипов, но при этом есть наследование. Go использует утиную типизацию:
Если что-то выглядит как утка, плавает как утка и крякает как утка, то, скорее всего, так оно и есть.
Если struct
Go реализует те же функции, что и интерфейс, она неявно реализует интерфейс.
type Animal interface { feed() //1 } type Rabbit struct { } func (rabbit Rabbit) feed() { //2 // feeds }
Animal
может кормитьfeed()
, которая принимает Rabbit
в качестве параметра, Rabbit
реализует Animal
Мне не нравится Go за его подход к обработке ошибок, но у меня было два мнения о неявной реализации. С одной стороны, я понимал, что это новая концепция, и старался оставаться открытым; с другой стороны, я думаю, что явные вещи всегда лучше неявных, как в разработке ПО, так и в реальной жизни.
Python — самый интересный из известных мне языков с точки зрения наследования.
Подтипирование и наследование на основе типов присутствуют в Python с момента его создания.
class Animal: def feed(self): #1 pass #2 class Rabbit(Animal): #3 pass
Animal
может кормитьRabbit
IS A
Animal
, его можно feed()
В этом отношении Python работает так же, как Java с точки зрения наследования. Python также предлагает утиную типизацию, которую я описал как магические методы . Например, чтобы сделать что-то итерируемым , например , что может возвращать итератор , вам нужно всего лишь реализовать __iter__()
и __next__()
:
class SingleValueIterable(): done = False def __init__(self, value): self.value = value def __iter__(self): #1 return self def __next__(self): #1 if self.done: raise StopIteration else: self.done = True return self.value svi = SingleValueIterable(5) sviter = iter(svi) #2 for x in sviter: print(x) #3
Iterator
— Python знает, как это сделать, поскольку мы реализовали методы выше5
Проблема с этим подходом утиной типизации в том, что он работает только для предопределенных магических методов Python. Что делать, если вы хотите предложить класс, от которого третья сторона могла бы наследовать неявно?
class Animal: def feed(): pass class Rabbit: def feed(): pass
В приведенном выше фрагменте Rabbit
не является Animal
, к нашему большому огорчению. Введите PEP 544 под названием Protocols: Structural subtyping (static duck typing). PEP решает невозможность определения магических методов для наших классов. Он определяет простой класс Protocol
: как только вы наследуете от него, методы, определенные в классе, становятся доступными для duck typing, отсюда и название — static duck typing.
from typing import Protocol class Animal(Protocol): #1 def feed(): #2 pass class Rabbit: def feed(): #2 pass class VenusFlytrap: def feed(): #2 pass
Protocol
Animal
— это Protocol
, любой класс, определяющий feed()
становится Animal
, к лучшему или к худшему.Объектно-ориентированное программирование, наследование и подтипирование могут иметь особые значения, которые не переводятся на другие языки, в зависимости от первого изучаемого вами языка. Java позиционирует себя как объектно-ориентированный язык и предлагает полный пакет. Go не является объектно-ориентированным языком, но он все еще предлагает подтипирование с помощью утиной типизации. Python предлагает как явное, так и неявное наследование, но не имеет интерфейсов.
Вы изучаете новый язык программирования, сравнивая его с тем(ами), которые вы уже знаете. Знание особенностей языка является ключом к написанию идиоматического кода на вашем целевом языке. Ознакомьтесь с особенностями, которых нет в известных вам языках: они расширят ваше понимание программирования в целом.
Идем дальше:
Первоначально опубликовано в A Java Geek 26 января 2025 г.