Композиция против наследования
Часть раздела Object Oriented Programming путешествия по Python на Coddy — урок 37 из 64.
Объектно-ориентированное программирование предлагает два основных подхода к повторному использованию кода: наследование (отношение «is-a») и композицию (отношение «has-a»).
Вот пример наследования, создающего отношение «является» (is-a):
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
return f"{self.name} is eating"
class Dog(Animal): # Dog «является» Animal
def bark(self):
return "Woof!"
dog = Dog("Buddy")
print(dog.eat()) # Наследуемый метод
print(dog.bark()) # Собственный методВот пример композиции, создающей отношение «has-a» (имеет):
class Engine:
def start(self):
return "Engine started"
def stop(self):
return "Engine stopped"
class Car: # Car "имеет" Engine
def __init__(self):
self.engine = Engine() # Композиция
def start(self):
return self.engine.start()
car = Car()
print(car.start()) # Использует скомпонованный двигательСравните оба подхода на более сложном примере:
# Подход с наследованием
class Bird:
def move(self):
return "Flying"
class Duck(Bird):
def quack(self):
return "Quack!"
# Подход с композицией
class FlyBehavior:
def move(self):
return "Flying"
class SwimBehavior:
def move(self):
return "Swimming"
class VersatileDuck:
def __init__(self):
self.fly_behavior = FlyBehavior()
self.swim_behavior = SwimBehavior()
def fly(self):
return self.fly_behavior.move()
def swim(self):
return self.swim_behavior.move()
def quack(self):
return "Quack!"Протестируйте оба подхода:
# Наследование
duck1 = Duck()
print(duck1.move()) # Летит
print(duck1.quack()) # Кря!
# Композиция
duck2 = VersatileDuck()
print(duck2.fly()) # Летит
print(duck2.swim()) # Плывет
print(duck2.quack()) # Кря!Вывод:
Buddy is eating
Woof!
Engine started
Flying
Quack!
Flying
Swimming
Quack!Ключевые различия:
Наследование:
- Сильная связанность между родителем и потомком
- Отношение «является» ("Is-a")
- Изменения в родителе влияют на всех потомков
- Лучше всего подходит для истинных иерархических отношений
Композиция:
- Слабая связанность между объектами
- Отношение «Has-a»
- Более гибкая — позволяет изменять поведение во время выполнения
- Проще тестировать и изменять
Ключевой момент: используйте наследование, когда у вас есть истинная связь «является» (is-a). Используйте композицию, когда вам нужна гибкость и слабая связанность. Принцип «композиция вместо наследования» (composition over inheritance) предполагает предпочтение композиции в большинстве случаев из-за её гибкости и удобства сопровождения.
Задание
СреднеВ этом испытании вы реализуете систему медиабиблиотеки, используя паттерны наследования и композиции.
Вам нужно отредактировать следующие файлы, следуя комментариям TODO в каждом из них:
media.py,book.py,movie.py,musicalbum.py— для реализации наследованияmediaitem.py,bookcomposition.py,moviecomposition.py,musicalbumcomposition.py— для реализации композиции
Каждый тип медиа должен иметь:
- Атрибуты
title,creator(автор/режиссер/исполнитель) иyear - Соответствующий метод
display_info()
Шпаргалка
Объектно-ориентированное программирование предлагает два основных подхода для повторного использования кода: наследование (отношение «is-a») и композиция (отношение «has-a»).
Пример наследования:
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
return f"{self.name} is eating"
class Dog(Animal): # Dog "is an" Animal (Собака «является» Животным)
def bark(self):
return "Woof!"
dog = Dog("Buddy")
print(dog.eat()) # Inherited method (Унаследованный метод)
print(dog.bark()) # Own method (Собственный метод)Пример композиции:
class Engine:
def start(self):
return "Engine started"
class Car: # Car "has an" Engine (Машина «имеет» Двигатель)
def __init__(self):
self.engine = Engine() # Composition (Композиция)
def start(self):
return self.engine.start()
car = Car()
print(car.start()) # Uses composed engine (Использует встроенный двигатель)Ключевые различия:
Наследование:
- Жесткая связь между родителем и потомком
- Отношение «Is-a» (является)
- Изменения в родителе влияют на всех потомков
- Лучше всего подходит для истинных иерархических отношений
Композиция:
- Слабая связь между объектами
- Отношение «Has-a» (имеет)
- Более гибкая — можно менять поведение во время выполнения
- Проще тестировать и изменять
Используйте наследование для истинных отношений «is-a». Используйте композицию для гибкости и слабой связи. Принцип «композиция вместо наследования» (composition over inheritance) предлагает отдавать предпочтение композиции в большинстве случаев.
Попробуйте сами
from media import Media
from book import Book
from movie import Movie
from musicalbum import MusicAlbum
from mediaitem import MediaItem
from bookcomposition import BookComposition
from moviecomposition import MovieComposition
from musicalbumcomposition import MusicAlbumComposition
# Обработчик комплексных тестовых случаев
test_case = input()
def test_inheritance_basic():
book = Book("The Hobbit", "J.R.R. Tolkien", 1937)
movie = Movie("The Matrix", "Wachowski Sisters", 1999)
album = MusicAlbum("Abbey Road", "The Beatles", 1969)
assert book.display_info() == "Book: The Hobbit by J.R.R. Tolkien (1937)"
assert movie.display_info() == "Movie: The Matrix directed by Wachowski Sisters (1999)"
assert album.display_info() == "Music Album: Abbey Road by The Beatles (1969)"
def test_composition_basic():
book_comp = BookComposition("Dune", "Frank Herbert", 1965)
movie_comp = MovieComposition("Inception", "Christopher Nolan", 2010)
album_comp = MusicAlbumComposition("Thriller", "Michael Jackson", 1982)
assert book_comp.display_info() == "Book: Dune by Frank Herbert (1965)"
assert movie_comp.display_info() == "Movie: Inception directed by Christopher Nolan (2010)"
assert album_comp.display_info() == "Music Album: Thriller by Michael Jackson (1982)"
def test_inheritance_relationships():
book = Book("Test Book", "Test Author", 2000)
movie = Movie("Test Movie", "Test Director", 2001)
album = MusicAlbum("Test Album", "Test Artist", 2002)
assert isinstance(book, Book)
assert isinstance(book, Media)
assert isinstance(movie, Movie)
assert isinstance(movie, Media)
assert isinstance(album, MusicAlbum)
assert isinstance(album, Media)
def test_attribute_access():
# Тестирование подхода через наследование
book = Book("Test Book", "Test Author", 2000)
assert book.title == "Test Book"
assert book.creator == "Test Author"
assert book.year == 2000
# Тестирование подхода через композицию
book_comp = BookComposition("Test Book", "Test Author", 2000)
assert book_comp.media.title == "Test Book"
assert book_comp.media.creator == "Test Author"
assert book_comp.media.year == 2000
def test_polymorphism():
media_list = [
Book("Book1", "Author1", 2001),
Movie("Movie1", "Director1", 2002),
MusicAlbum("Album1", "Artist1", 2003)
]
assert media_list[0].display_info() == "Book: Book1 by Author1 (2001)"
assert media_list[1].display_info() == "Movie: Movie1 directed by Director1 (2002)"
assert media_list[2].display_info() == "Music Album: Album1 by Artist1 (2003)"
def test_edge_cases():
# Пустые строки
book = Book("", "", 0)
assert book.display_info() == "Book: by (0)"
# Специальные символы - здесь исправлена синтаксическая ошибка
movie = Movie("Test\"Movie", "Test\\nDirector", -1)
assert movie.display_info() == "Movie: Test\"Movie directed by Test\\nDirector (-1)"
# Экстремальные значения года
album = MusicAlbum("Test Album", "Test Artist", 9999)
assert album.display_info() == "Music Album: Test Album by Test Artist (9999)"
def test_stress():
# Создание множества медиа-объектов
books = [Book(f"Book{i}", f"Author{i}", 2000+i) for i in range(100)]
movies = [Movie(f"Movie{i}", f"Director{i}", 2000+i) for i in range(100)]
albums = [MusicAlbum(f"Album{i}", f"Artist{i}", 2000+i) for i in range(100)]
for i, book in enumerate(books):
assert book.display_info() == f"Book: Book{i} by Author{i} ({2000+i})"
for i, movie in enumerate(movies):
assert movie.display_info() == f"Movie: Movie{i} directed by Director{i} ({2000+i})"
for i, album in enumerate(albums):
assert album.display_info() == f"Music Album: Album{i} by Artist{i} ({2000+i})"
# Запуск соответствующего теста на основе входных данных
if test_case == "default_test" or test_case == "":
test_inheritance_basic()
test_composition_basic()
print("All tests passed!")
elif test_case == "inheritance_test":
test_inheritance_basic()
test_inheritance_relationships()
print("Inheritance tests passed!")
elif test_case == "composition_test":
test_composition_basic()
print("Composition tests passed!")
elif test_case == "polymorphism_test":
test_polymorphism()
print("Polymorphism tests passed!")
elif test_case == "attribute_test":
test_attribute_access()
print("Attribute tests passed!")
elif test_case == "edge_cases":
test_edge_cases()
print("Edge case tests passed!")
elif test_case == "stress_test":
test_stress()
print("Stress tests passed!")В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыПараметр selfМетодыАтрибутыМетод-конструктор (__init__)Повторение — Простой калькулятор4Наследование
Базовое наследованиеФункция super()Переопределение методовМножественное наследованиеПорядок разрешения методовИтоги — Иерархия сотрудников7Специальные методы
Введение в магические методыПерегрузка операторовМагические методы контейнеровПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy5Полиморфизм
Снова о переопределении методовУтиная типизацияАбстрактные классы и методыПроектирование интерфейсовИтоги — Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияМиксиныСтатические методы и методы классаДекораторы классовМенеджеры контекста11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Свойства классов
Переменные экземпляра и классаДекораторы свойствПриватные атрибутыИтоги — Менеджер банковского счета6Инкапсуляция
Public, Protected, Private члены классаМодификаторы доступаСокрытие данныхДекораторы property: продвинутый уровеньИтоги: Система учета студентов12Проект: Управление библиотекой
Обзор проектаКлассы Book и User