Qu'est-ce qu'une “désadaptation d'impédance” en programmation ?

0
235
Shutterstock/oatawa

Un décalage d'impédance de programmation se produit lorsque les données doivent être transformées en un paradigme architectural différent. L'exemple le plus frappant concerne les bases de code orientées objet et les bases de données relationnelles.

Une incompatibilité d'impédance survient lorsque des données sont extraites ou insérées dans une base de données. Les propriétés des objets ou des classes dans la base de code doivent être mappées à leurs champs de table de base de données correspondants.

Mapping et relations

Vos classes ne seront pas nécessairement mappées directement aux tables de base de données individuelles. La construction d'un objet peut nécessiter l'utilisation agrégée des données de plusieurs tables.

Vous devrez également gérer les relations entre vos données. Les bases de données relationnelles simplifient cette tâche en vous permettant de référencer d'autres enregistrements. Vous pouvez utiliser un JOIN pour accéder à toutes les données encapsulées par la relation.

CREATE TABLE productTypes( ProductTypeUuid VARCHAR(32) CLÉ PRIMAIRE, ProductTypeName VARCHAR(255) NON NULL UNIQUE );   CREATE TABLE produits( ProductUuid VARCHAR(32) CLÉ PRIMAIRE, ProductName VARCHAR(255) NON NULL UNIQUE, Type de produit VARCHAR(32) NON NULL, CLÉ ÉTRANGÈRE (ProductType) RÉFÉRENCES productTypes(ProductTypeUuid) SUR SUPPRIMER CASCADE );

En utilisant du SQL brut, vous pouvez obtenir les propriétés combinées d'un produit et de son type de produit avec cette requête simple :

SELECT * FROM products INNER JOIN productTypes ON ProductTypeUuid = ProductType ; Publicité

Ensuite, les propriétés du produit et de son type de produit sont accessibles à partir de la même structure plate :

echo $record["ProductName"]; echo $record["ProductTypeName"];

Ce flat array devient vite contraignant dans les applications complexes. Les développeurs modélisent naturellement les entités Product et ProductType en tant que classes distinctes. La classe Product pourrait alors contenir une instance d'un ProductType. Voici à quoi cela ressemble :

classe finale ProductType {   fonction publique __construct( chaîne publique $Uuid, chaîne publique $Nom) {}   }   classe finale Produit {   fonction publique __construct( chaîne publique $Uuid, chaîne publique $Name, public ProductType $ProductType) {}   }

Il y a maintenant une différence d'impédance significative dans le code. Une certaine forme de mappage spécialisé est requise avant que les enregistrements de la requête de base de données puissent être représentés en tant qu'instances de produit.

D'autres complications surviennent lorsque vous souhaitez accéder à tous les produits d'un type particulier. Voici comment procéder dans le code :

classe finale ProductType {   fonction publique __construct( chaîne publique $Uuid, chaîne publique $Name, ProductCollection $Products) {}   }

Maintenant, le ProductType contient une ProductCollection, qui contiendrait finalement un tableau d'instances de Product. Cela crée une référence relationnelle bidirectionnelle – ProductType contient tous ses produits et chaque produit contient son type de produit.

Cette forme de modélisation n'existe pas dans le paradigme relationnel. Chaque forme de connexion est représentée par un seul enregistrement de lien relationnel. L'utilisation de références bidirectionnelles simplifie l'accès du développeur aux propriétés de l'objet. Cependant, il nécessite un mappage plus complexe lorsqu'il est transféré vers et depuis la base de données. En effet, SQL ne comprend pas nativement la sémantique du modèle.

Prise en compte de la hiérarchie

Publicité

Le modèle expliqué ci-dessus crée une hiérarchie dans la base de code : Product se trouve en dessous de ProductType. Cela semble logique et correspond à nos attentes du monde réel.

Les bases de données relationnelles ne respectent pas les hiérarchies. Comme toutes les relations sont équivalentes, les bases de données relationnelles ont une forme intrinsèquement « plate » structure. Nous l'avons vu précédemment lors de la récupération de données avec un JOIN.

L'absence de hiérarchie dans SQL signifie que toutes les tables possèdent une priorité équivalente les unes aux autres. Un effet de ceci est que vous pouvez facilement accéder aux propriétés des enregistrements imbriqués profondément dans votre hiérarchie d'objets logiques. De plus, le risque de dépendances cycliques est moindre.

L'exemple ci-dessus montre que Product et ProductType peuvent finir par se référer dans le code ; la nature plate des bases de données relationnelles empêcherait cet exemple spécifique de se produire. Les cycles peuvent toujours survenir en SQL brut, mais vous êtes moins susceptible de les rencontrer que lors de la modélisation avec du code orienté objet.

La programmation orientée objet repose sur la composition d'objets simples en objets plus complexes. Les modèles relationnels n'ont pas une telle notion de composition ou de “simple” et “complexe” – n'importe quel enregistrement peut référencer n'importe quel autre.

Héritage

Une autre fonction exclusive à la POO est l'héritage. Il est courant qu'une classe en étende une autre, ajoutant des comportements supplémentaires. Les bases de données relationnelles ne peuvent pas répliquer cela. Il est impossible pour une table de s'étendre. une autre table.

Publicité

Une base de code qui utilise l'héritage rencontrera des difficultés lorsque les objets enfants sont persistants ou hydratés via une base de données relationnelle. Dans la base de données, vous aurez généralement besoin de deux tables. L'un stocke les propriétés de l'objet de base (que vous étendez), l'autre gérant les propriétés de l'enfant.

Le code de mappage itère ensuite toutes les propriétés de l'objet. Les propriétés dérivant de la classe étendue seront insérées dans le premier code. Ceux directement définis sur l'enfant se retrouveront dans la deuxième table.

Un système similaire est requis lors du mappage vers la base de code à partir de la base de données. Un SQL JOIN pourrait être utilisé pour obtenir tous les enregistrements correspondant à la classe de base (étendue), avec les propriétés enfants incluses. Les enregistrements contenant les propriétés enfants seraient ensuite mappés sur des instances de classe enfant.

CREATE TABLE parent(Id INTEGER PRIMARY KEY, A INTEGER); CREATE TABLE child(Id INTEGER PRIMARY KEY, B INTEGER, ParentId INTEGER); classe Parent { fonction publique __construct(int $A) {} }   classe finale Enfant étend Parent { fonction publique __construct(int $A, int $B) {} }   //Obtenir des enregistrements //SELECT * FROM parent INNER JOIN child ON child.ParentId = parent.Id; $objs = []; foreach ($enregistre comme $record) { si (isset($record["B"])) { $objs[] = nouveau Enfant($enregistrement["A"], $enregistrement["B"]); } sinon $objs[] = nouvel enregistrement parent($record["A"]); }

L'introduction de l'héritage nécessite l'utilisation d'une logique de mappage plus complexe. L'incapacité des bases de données relationnelles à modéliser les capacités d'héritage des langages orientés objet introduit cette inadéquation d'impédance.

Visibilité et encapsulation

Un principe fondamental de la programmation orientée objet est la visibilité et l'encapsulation. Les propriétés sont déclarées comme publiques ou privées. La représentation interne d'un objet peut être dissimulée derrière une interface qui pré-formate les données pour le consommateur.

Les bases de données relationnelles manquent de ces contrôles. Il n'y a aucun moyen de déclarer un champ comme privé, et il n'y a pas non plus de besoin évident de le faire. Chaque donnée stockée dans une base de données a sa propre finalité ; il ne devrait y avoir aucune redondance.

Publicité

Ce n'est pas nécessairement vrai lorsqu'il est transposé à un paradigme orienté objet. Un objet peut utiliser deux champs de base de données agrégés pour exposer une nouvelle information calculée. Les valeurs des deux champs individuels peuvent ne pas être pertinentes pour l'application et par conséquent être masquées.

CREATE TABLE products(Prix INTEGER, TaxTate INTEGER); classe finale Produit {   fonction publique __construct( protected int $Price, protected int $TaxRate) {}   fonction publique getTotalPrice() : entier { renvoie ($this -> Prix ​​* ($ce -> TaxTaux/100)); } }

L'application ne se soucie que du prix total du produit, taxes comprises. Le prix unitaire et le taux de taxe sont encapsulés par la classe de produit. Les contrôles de visibilité (protégés) masquent les valeurs. L'interface publique consiste en une seule méthode qui utilise les deux champs pour calculer une nouvelle valeur.

Plus généralement, la programmation orientée objet prône la programmation aux interfaces. L'accès direct aux propriétés est déconseillé. L'utilisation de méthodes de classe qui implémentent une interface permet de construire des implémentations alternatives dans le futur.

Il n'y a pas de contrepartie directe à cela dans le monde relationnel. Les vues de base de données offrent un moyen de combiner des tables et des champs abstraits dans de nouveaux formulaires. Cependant, vous travaillez toujours directement avec les valeurs de champ.

Résumé

Des décalages d'impédance relationnelle-objet se produisent lorsqu'une base de code orientée objet échange des données avec une base de données relationnelle. base de données. Il existe des différences fondamentales dans la manière dont les données sont modélisées. Cela nécessite l'introduction d'une couche de mappage qui transpose les données entre les formats.

Le problème de non-concordance d'impédance est l'un des principaux facteurs de motivation dans l'adoption des ORM dans les langages orientés objet. Ceux-ci permettent l'hydratation automatique d'objets de base de code complexes à partir de sources de données relationnelles. Sans couche de mappage de données dédiée, seules les applications les plus simples auront un chemin direct vers et depuis une base de données relationnelle.