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 propioAquí 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 compuestoCompara 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.
Desafío
IntermedioEn 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 herenciamediaitem.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 propioEjemplo 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 compuestoDiferencias 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!")Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1Fundamentos de la POO
Archivos externosIntroducción a la POOClases vs ObjetosEl parámetro selfMétodosAtributosMétodo constructor (__init__)Resumen - Calculadora simple4Herencia
Herencia básicaLa función super()Sobrescritura de métodosHerencia múltipleOrden de resolución de métodosResumen - Jerarquía de empleados7Métodos especiales
Introducción a los métodos mágicosSobrecarga de operadoresMétodos mágicos de contenedoresRepaso - Lista personalizada10Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy2Decoradores
Introducción a los decoradoresDecorador propertyDecorador de métodos estáticosDecorador de métodos de clase5Polimorfismo
Sobreescritura de métodos revisitadaDuck TypingClases y métodos abstractosDiseño de interfacesResumen - Calculadora de figuras8Conceptos avanzados de POO
Composición vs. HerenciaMixinsMétodos estáticos y de claseDecoradores de claseGestores de contexto11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Propiedades de clase
Variables de instancia vs de claseDecoradores de propiedadesAtributos privadosResumen - Gestor de cuentas bancarias