Commençons par rappeler qu'en
python
Copier le code
1 2 class Cat(Animal, Mammal): pass
Pour déterminer si un lien entre une classe enfant et classe(s) parent(s) est cohérent, on peut utiliser le verbe être.
Le chat est un animal et est un mammifère
En mettant en place de l'héritage, on souhaite lier de manière forte les classes entre elles avec un contrat d'interface. Ces classes parents définissent un comportement et ont une interaction systématique avec l'enfant.
Et le mixin dans tout cela ?
C'est une classe qui va étendre les fonctionnalités de la classe qui hérite de ce mixin. L'exemple que l'on trouve souvent est la sérialisation d'un objet
Copier le code
1 2 3 4 5 6 7 8 9 10 class JSONSerializableMixin: def to_json(self, dict_data) -> str: return json.dumps(dict_data) class User(JSONSerializableMixin): ... def save(): with open('file.txt') as f: f.write(self.to_json(self.data))
Cette approche permet de découper le rôle de chacun. Évidemment, dans cet exemple la méthode
save()User
Il faut toujours garder en tête les principes de base de la programmation comme S.O.L.I.D. Ici le Single Responsability Principle est bien en place.
On donne plus de responsabilités à User avec la sérialisation de son objet, mais cela ne repose pas sur son fonctionnement en lui-même. En effet, on ne manipule pas son instance (méthode ou attribut de l'enfant), on effectue une transformation. On ne change pas la donnée initiale.
C'est un principe pour un mixin qui est souvent stateless
La relation entre les objets est faible, comme on peut le voir sur cet exemple sur la mise en place de la configuration d'une application FastApi à partir de variables d'environnement
Copier le code
1 2 3 4 5 6 7 8 9 10 class NeoModelConfigMixin: neo4j_url: str = Field(default="bolt://localhost:7687/neo4j") class AmqpConfigMixin: AMQP_PROTOCOL = r"^(amqps?://).*" amqp_url: str = Field(default=None, pattern=AMQP_PROTOCOL) class Settings(BaseSettings, NeoModelConfigMixin, AmqpConfigMixin): model_config = SettingsConfigDict(env_file=find_env_file())
On étend la classe
Settings
Un mixin n'a pas lieu d'exister seul, il doit forcément être parent de quelqu'un. Car il permet d'acquérir de nouvelles fonctionnalités à l'enfant.
Un mixin est souvent voisin d'un autre mixin, car un mixin doit porter une seule responsabilité (principes S.O.L.I.D.)
Il est important de préciser que l'ordre d'héritage est crucial, de gauche à droite, vous pouvez le connaitre en appelant le MRO. Faites attention quand vous avez beaucoup de mixins à ce que l'un ne surcharge pas l'autre et apporte une erreur silencieuse. Ma préconisation est de limiter à 3 parents (à ne pas généraliser car il dépend du contexte), sinon il faut réfléchir à implémenter un design pattern (Factory, Builder...)
Un autre point important pour le mixin est sa non-substitution, il ne doit pas être covariant. On ne peut pas dire que
Settings
NeoModelConfigMixin.
Mettre en place un mixin évite de généraliser un comportement dans un parent pour un enfant A qui en aurait besoin et pas pour un enfant B.
Un mixin apporte plus de souplesse qu'un héritage classique, il repose sur le fait que python permet de faire du multiple héritage. Son implémentation est bien de l'héritage mais avec une approche plus légère. Il a la capacité à faire quelque chose et non pas d'être quelque chose (héritage classique)
Vous l'avez vu la syntaxe est simple, il suffit de suffixer votre classe par Mixin, python ne suggère pas d'autre structure plus rigoureuse