Menu
CoddyTech

Композиция и наследование

Часть раздела Объектно-ориентированное программирование путешествия по Python на Coddy. Урок 37 из 64.

Объектно-ориентированное программирование предлагает два основных подхода к повторному использованию кода: наследование (отношение «является») и композицию (отношение «имеет»).

Вот пример того, как наследование создаёт отношение «является»:

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())   # Унаследованный метод
print(dog.bark())  # Собственный метод

Вот пример композиции, создающей отношение «имеет»:

class Engine:
    def start(self):
        return "Engine started"
    
    def stop(self):
        return "Engine stopped"

class Car:  # Car "has an" 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!

Ключевые различия:

Наследование:

  • Тесная связь между родителем и потомком
  • Отношение «является»
  • Изменения в родителе влияют на всех потомков
  • Лучше всего подходит для настоящих иерархических отношений

Композиция:

  • Слабая связанность объектов
  • Отношение «имеет»
  • Более гибкая — можно менять поведение во время выполнения
  • Легче тестировать и изменять

Ключевой момент: используй наследование, когда у тебя действительно есть отношение «является». Используй композицию, когда тебе нужны гибкость и слабая связанность. Принцип «композиция вместо наследования» предлагает в большинстве случаев отдавать предпочтение композиции благодаря её гибкости и удобству сопровождения.

challenge icon

Задание

Средне

В этом задании тебе предстоит реализовать систему медиатеки, используя наследование и композицию.

Тебе нужно отредактировать следующие файлы, следуя комментариям TODO в каждом из них:

  • media.py, book.py, movie.py, musicalbum.py — для реализации наследования
  • mediaitem.py, bookcomposition.py, moviecomposition.py, musicalbumcomposition.py — для реализации композиции

У каждого типа медиа должны быть:

  • Атрибуты: название, создатель (автор/режиссёр/исполнитель) и год
  • Соответствующий метод display_info()

Попробуйте сами

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!")
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Python