Menu
Coddy logo textTech

Composición vs. Herencia

Parte de la sección Object Oriented Programming del Journey de Python de Coddy — lección 37 de 64.

La programación orientada a objetos ofrece dos enfoques principales para la reutilización de código: la herencia (relación "es-un") y la composición (relación "tiene-un").

Aquí hay un ejemplo de herencia creando una relación "es-un":

class Animal:
    def __init__(self, name):
        self.name = name
    
    def eat(self):
        return f"{self.name} is eating"

class Dog(Animal):  # Dog "es un" Animal
    def bark(self):
        return "Woof!"

dog = Dog("Buddy")
print(dog.eat())   # Método heredado
print(dog.bark())  # Método propio

Aquí hay un ejemplo de composición creando una relación "tiene-un":

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

class Car:  # Car "tiene un" Engine
    def __init__(self):
        self.engine = Engine()  # Composición
    
    def start(self):
        return self.engine.start()

car = Car()
print(car.start())  # Utiliza el motor compuesto

Compara ambos enfoques con un ejemplo más complejo:

# Enfoque de herencia
class Bird:
    def move(self):
        return "Flying"

class Duck(Bird):
    def quack(self):
        return "Quack!"

# Enfoque de composición
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!"

Prueba ambos enfoques:

# Herencia
duck1 = Duck()
print(duck1.move())  # Volando
print(duck1.quack()) # ¡Cuac!

# Composición  
duck2 = VersatileDuck()
print(duck2.fly())   # Volando
print(duck2.swim())  # Nadando
print(duck2.quack()) # ¡Cuac!

Salida:

Buddy is eating
Woof!
Engine started
Flying
Quack!
Flying
Swimming
Quack!

Diferencias clave:

Herencia:

  • Acoplamiento fuerte entre padre e hijo
  • Relación "Es-un"
  • Los cambios en el padre afectan a todos los hijos
  • Ideal para relaciones jerárquicas reales

Composición:

  • Acoplamiento débil entre objetos
  • Relación "Tiene-un"
  • Más flexible: puede cambiar el comportamiento en tiempo de ejecución
  • Más fácil de probar y modificar

Punto clave: Usa la herencia cuando tengas una verdadera relación "es-un". Usa la composición cuando necesites flexibilidad y un acoplamiento débil. El principio "composición sobre herencia" sugiere favorecer la composición en la mayoría de los casos debido a su flexibilidad y mantenibilidad.

challenge icon

Desafío

Intermedio

En este desafío, implementarás un sistema de biblioteca de medios utilizando patrones de herencia y composición.

Debes editar los siguientes archivos, siguiendo los comentarios TODO dentro de cada uno:

  • media.py, book.py, movie.py, musicalbum.py - Para la implementación de la herencia
  • mediaitem.py, bookcomposition.py, moviecomposition.py, musicalbumcomposition.py - Para la implementación de la composición

Cada tipo de medio debe tener:

  • Atributos de título, creador (autor/director/artista) y año
  • El método display_info() apropiado

Hoja de referencia

La programación orientada a objetos ofrece dos enfoques principales para la reutilización de código: la herencia (relación "es-un") y la composición (relación "tiene-un").

Ejemplo de herencia:

class Animal:
    def __init__(self, name):
        self.name = name
    
    def eat(self):
        return f"{self.name} is eating"

class Dog(Animal):  # Dog "es un" Animal
    def bark(self):
        return "Woof!"

dog = Dog("Buddy")
print(dog.eat())   # Método heredado
print(dog.bark())  # Método propio

Ejemplo de composición:

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

class Car:  # Car "tiene un" Engine
    def __init__(self):
        self.engine = Engine()  # Composición
    
    def start(self):
        return self.engine.start()

car = Car()
print(car.start())  # Usa el motor compuesto

Diferencias clave:

Herencia:

  • Acoplamiento fuerte entre padre e hijo
  • Relación "es-un"
  • Los cambios en el padre afectan a todos los hijos
  • Ideal para relaciones jerárquicas reales

Composición:

  • Acoplamiento débil entre objetos
  • Relación "tiene-un"
  • Más flexible: puede cambiar el comportamiento en tiempo de ejecución
  • Más fácil de probar y modificar

Utilice la herencia para relaciones reales de tipo "es-un". Utilice la composición para obtener flexibilidad y un acoplamiento débil. El principio de "composición sobre herencia" sugiere favorecer la composición en la mayoría de los casos.

Pruébalo tú mismo

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

# Manejador integral de casos de prueba
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():
    # Probar el enfoque de herencia
    book = Book("Test Book", "Test Author", 2000)
    assert book.title == "Test Book"
    assert book.creator == "Test Author"
    assert book.year == 2000
    
    # Probar el enfoque de composición
    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():
    # Cadenas vacías
    book = Book("", "", 0)
    assert book.display_info() == "Book:  by  (0)"
    
    # Caracteres especiales - se corrigió el error de sintaxis aquí
    movie = Movie("Test\"Movie", "Test\\nDirector", -1)
    assert movie.display_info() == "Movie: Test\"Movie directed by Test\\nDirector (-1)"
    
    # Años extremos
    album = MusicAlbum("Test Album", "Test Artist", 9999)
    assert album.display_info() == "Music Album: Test Album by Test Artist (9999)"

def test_stress():
    # Crear muchos objetos de medios
    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})"

# Ejecutar la prueba apropiada basada en la entrada
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 iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Object Oriented Programming