Dado que Python permite la herencia múltiple, ¿cómo es la herencia idiomática en Python?
En lenguajes con herencia única, como Java, la herencia se usaría cuando se pudiera decir que un objeto "es-a" de otro objeto y se desea compartir el código entre los objetos (desde el objeto primario hasta el objeto secundario). Por ejemplo, podría decir que Dog
es un Animal
:
public class Animal {...}
public class Dog extends Animal {...}
Pero dado que Python admite herencia múltiple, podemos crear un objeto componiendo muchos otros objetos juntos. Considere el siguiente ejemplo:
class UserService(object):
def validate_credentials(self, username, password):
# validate the user credentials are correct
pass
class LoggingService(object):
def log_error(self, error):
# log an error
pass
class User(UserService, LoggingService):
def __init__(self, username, password):
self.username = username
self.password = password
def authenticate(self):
if not super().validate_credentials(self.username, self.password):
super().log_error('Invalid credentials supplied')
return False
return True
¿Es este un uso aceptable o bueno de la herencia múltiple en Python? En lugar de decir que la herencia es cuando un objeto "es-a" de otro objeto, creamos un User
modelo compuesto por UserService
y LoggingService
.
Toda la lógica para las operaciones de la base de datos o la red se puede mantener separada del User
modelo al colocarlas en el UserService
objeto y mantener toda la lógica para iniciar sesión en el LoggingService
.
Veo algunos problemas con este enfoque son:
- ¿Esto crea un objeto de Dios? ¿Ya que
User
hereda de, o está compuesto de,UserService
yLoggingService
realmente está siguiendo el principio de responsabilidad única? - Para acceder a los métodos en un objeto principal / siguiente en línea (por ejemplo,
UserService.validate_credentials
tenemos que usarlosuper
. Esto hace que sea un poco más difícil ver qué objeto va a manejar este método y no es tan claro como, por ejemplo, , instanciandoUserService
y haciendo algo comoself.user_service.validate_credentials
¿Cuál sería la forma Pythonic de implementar el código anterior?
User
objetos tienen en sus interfaces no solo los miembros definidos en laUser
clase, sino también los definidos enUserService
yLoggingService
. Esta no es una relación "tiene-a", porque la interfaz pública se copia (aunque no mediante copia directa, sino mediante una búsqueda indirecta en las interfaces de las superclases).Aparte del hecho de que permite múltiples superclases, la herencia de Python no es sustancialmente diferente a la de Java, es decir, los miembros de una subclase también son miembros de cada uno de sus supertipos [1]. El hecho de que Python use duck-typing tampoco hace ninguna diferencia: su subclase tiene todos los miembros de sus superclases, por lo que puede ser utilizada por cualquier código que pueda usar esas subclases. El hecho de que la herencia múltiple se implemente de manera efectiva mediante el uso de la composición es un error: que el problema es la copia automatizada de las propiedades de una clase a otra, y no importa si usa composición o simplemente adivina mágicamente cómo se supone que los miembros para trabajar: tenerlos está mal.
Sí, esto viola la responsabilidad individual, porque le está dando a sus objetos la capacidad de realizar acciones que no son lógicamente parte de lo que están diseñados para hacer. Sí, crea objetos "dios", que es esencialmente otra forma de decir lo mismo.
Al diseñar sistemas orientados a objetos en Python, también se aplica la misma máxima que se predica en los libros de diseño de Java: prefiera la composición a la herencia. Lo mismo es cierto para (la mayoría [2]) otros sistemas con herencia múltiple.
[1]: se podría llamar a eso una relación "es-a", aunque personalmente no me gusta el término porque sugiere la idea de modelar el mundo real, y el modelado orientado a objetos no es lo mismo que el mundo real.
[2]: No estoy tan seguro acerca de C ++. C ++ admite "herencia privada", que es esencialmente composición sin necesidad de especificar un nombre de campo cuando desea utilizar los miembros públicos de la clase heredada. No afecta a la interfaz pública de la clase en absoluto. No me gusta usarlo, pero no veo ninguna buena razón para no hacerlo.
fuente