Étant donné que Python permet l'héritage multiple, à quoi ressemble l'héritage idiomatique en Python?
Dans les langages à héritage unique, comme Java, l'héritage serait utilisé lorsque vous pourriez dire qu'un objet "est-a" d'un autre objet et que vous souhaitez partager du code entre les objets (de l'objet parent à l'objet enfant). Par exemple, vous pourriez dire que Dog
c'est Animal
:
public class Animal {...}
public class Dog extends Animal {...}
Mais comme Python prend en charge l'héritage multiple, nous pouvons créer un objet en composant de nombreux autres objets ensemble. Considérez l'exemple ci-dessous:
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
Est-ce une utilisation acceptable ou bonne de l'héritage multiple en Python? Au lieu de dire que l'héritage est lorsqu'un objet "est-a" d'un autre objet, nous créons un User
modèle composé de UserService
et LoggingService
.
Toute la logique des opérations de base de données ou de réseau peut être séparée du User
modèle en les plaçant dans l' UserService
objet et en conservant toute la logique de connexion dans le LoggingService
.
Je vois certains problèmes avec cette approche:
- Est-ce que cela crée un objet divin? Puisqu'il
User
hérite de, ou est composé de,UserService
etLoggingService
suit-il vraiment le principe de la responsabilité unique? - Afin d'accéder aux méthodes sur un objet parent / suivant (par exemple,
UserService.validate_credentials
nous devons utilisersuper
. Cela rend un peu plus difficile de voir quel objet va gérer cette méthode et n'est pas aussi clair que, par exemple , instancierUserService
et faire quelque chose commeself.user_service.validate_credentials
Quelle serait la façon Pythonique d'implémenter le code ci-dessus?
User
objets ont dans leurs interfaces non seulement les membres définis dans laUser
classe, mais aussi ceux définis dansUserService
etLoggingService
. Ce n'est pas une relation "has-a", car l'interface publique est copiée (bien que ce ne soit pas via une copie directe, mais plutôt par une recherche indirecte des interfaces des superclasses).Outre le fait qu'il autorise plusieurs superclasses, l'héritage de Python n'est pas substantiellement différent de Java, c'est-à-dire que les membres d'une sous-classe sont également membres de chacun de leurs supertypes [1]. Le fait que Python utilise le type de canard ne fait aucune différence non plus: votre sous-classe a tous les membres de ses superclasses, elle peut donc être utilisée par n'importe quel code qui pourrait utiliser ces sous-classes. Le fait que l'héritage multiple soit effectivement implémenté en utilisant la composition est un redingue: cette copie automatisée des propriétés d'une classe à une autre est le problème, et peu importe qu'il utilise la composition ou devine comme par magie comment les membres sont supposés au travail: les avoir c'est mal.
Oui, cela viole une seule responsabilité, car vous donnez à vos objets la possibilité d'effectuer des actions qui ne font pas logiquement partie de ce pour quoi ils sont conçus. Oui, cela crée des objets "dieu", ce qui est essentiellement une autre façon de dire la même chose.
Lors de la conception de systèmes orientés objet en Python, la même maxime prêchée par les livres de conception Java s'applique également: préférez la composition à l'héritage. Il en va de même pour (la plupart [2]) des autres systèmes à héritage multiple.
[1]: on pourrait appeler cela une relation "est-une", bien que je n'aime pas personnellement le terme car il suggère l'idée de modéliser le monde réel, et la modélisation orientée objet n'est pas la même chose que le monde réel.
[2]: Je ne suis pas sûr de C ++. C ++ prend en charge "l'héritage privé" qui est essentiellement une composition sans avoir besoin de spécifier un nom de champ lorsque vous souhaitez utiliser les membres publics de la classe héritée. Cela n'affecte pas du tout l'interface publique de la classe. Je ne comme l' utiliser, mais je ne vois aucune bonne raison de ne pas.
la source