Che cos'è un “disadattamento di impedenza” nella programmazione?

0
231
Shutterstock/oatawa

Una mancata corrispondenza dell'impedenza di programmazione si verifica quando i dati devono essere trasformati in un paradigma architetturale diverso. L'esempio più importante riguarda basi di codice orientate agli oggetti e database relazionali.

Una mancata corrispondenza di impedenza si verifica quando i dati vengono recuperati o inseriti in un database. Le proprietà degli oggetti o delle classi all'interno della codebase devono essere mappate ai corrispondenti campi della tabella del database.

Mappatura e relazioni

Le tue classi non verranno necessariamente associate direttamente alle singole tabelle del database. La costruzione di un oggetto potrebbe richiedere l'utilizzo aggregato dei dati di diverse tabelle.

Dovete anche gestire le relazioni tra i vostri dati. I database relazionali lo rendono semplice consentendo di fare riferimento ad altri record. Puoi utilizzare un JOIN per accedere a tutti i dati incapsulati dalla relazione.

CREATE TABLE productTypes( ProductTypeUuid VARCHAR(32) CHIAVE PRIMARIA, ProductTypeName VARCHAR(255) NON NULL UNICO );   CREA TABELLA prodotti( ProductUuid VARCHAR(32) CHIAVE PRIMARIA, Nome prodotto VARCHAR(255) NOT NULL UNIQUE, ProductType VARCHAR(32) NOT NULL, CHIAVE ESTERA (ProductType) REFERENZE productTypes(ProductTypeUuid) SU ELIMINA CASCATA );

Utilizzando SQL semplice, puoi ottenere le proprietà combinate di un prodotto e del suo ProductType con questa semplice query:

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

Quindi si accede alle proprietà del Prodotto e al suo ProductType dalla stessa struttura flat:

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

Questo array piatto diventa rapidamente limitante in applicazioni complesse. Gli sviluppatori modellano naturalmente le entità Product e ProductType come classi separate. La classe Product potrebbe quindi contenere un'istanza di ProductType. Ecco come appare:

classe finale ProductType {   funzione pubblica __construct( stringa pubblica $Uuid, stringa pubblica $Nome) {}   }   Classe finale Prodotto {   funzione pubblica __construct( stringa pubblica $Uuid, stringa pubblica $Name, ProductType pubblico $ProductType) {}   }

Ora c'è una significativa discrepanza di impedenza nel codice. È necessaria una forma di mappatura specializzata prima che i record della query del database possano essere rappresentati come istanze del prodotto.

Ulteriori complicazioni sorgono quando si desidera accedere a tutti i prodotti di un determinato tipo. Ecco come potresti farlo nel codice:

final class ProductType {   funzione pubblica __construct( stringa pubblica $Uuid, stringa pubblica $Name, ProductCollection $Prodotti) {}   }

Ora ProductType contiene una ProductCollection, che alla fine conterrebbe un array di istanze Product. Questo crea un riferimento relazionale bidirezionale – ProductType contiene tutti i suoi prodotti e ogni Prodotto contiene il suo tipo di prodotto.

Questa forma di modellazione non esiste all'interno del paradigma relazionale. Ogni forma di connessione è rappresentata con un singolo record di collegamento relazionale. L'uso di riferimenti bidirezionali semplifica l'accesso degli sviluppatori alle proprietà degli oggetti. Tuttavia, richiede una mappatura più complessa quando viene trasferita da e verso il database. Questo perché SQL non comprende nativamente la semantica del modello.

Considering Hierarchy

Il modello spiegato sopra crea una gerarchia nella codebase: Product si trova sotto ProductType. Sembra logico e corrisponde alle nostre aspettative sul mondo reale.

I database relazionali non rispettano le gerarchie. Poiché tutte le relazioni sono equivalenti, i database relazionali hanno intrinsecamente un “flat” struttura. Lo abbiamo visto in precedenza durante il recupero dei dati con un JOIN.

La mancanza di gerarchia in SQL significa che tutte le tabelle possiedono una priorità equivalente l'una all'altra. Un effetto di ciò è che puoi accedere facilmente alle proprietà dei record annidati in profondità nella tua gerarchia di oggetti logici. Inoltre, c'è un minor rischio di dipendenze cicliche.

L'esempio sopra mostra che Product e ProductType possono finire per fare riferimento l'uno all'altro nel codice; la natura piatta dei database relazionali impedirebbe il verificarsi di questo esempio specifico. I cicli possono ancora presentarsi in semplice SQL, ma è meno probabile che li incontriate rispetto alla modellazione con codice orientato agli oggetti.

La programmazione orientata agli oggetti si basa sulla composizione di oggetti semplici in oggetti più complessi. I modelli relazionali non hanno tale nozione di composizione o il “semplice” e “complesso” – qualsiasi record può fare riferimento a qualsiasi altro.

Ereditarietà

Un'altra funzione esclusiva di OOP è l'ereditarietà. È comune per una classe estenderne un'altra, aggiungendo ulteriori comportamenti. I database relazionali non sono in grado di replicarlo. È impossibile che una tabella “estenda” un altro tavolo.

Una codebase che utilizza l'ereditarietà incontrerà difficoltà quando gli oggetti figlio vengono mantenuti o idratati tramite un database relazionale. All'interno del database, di solito sono necessarie due tabelle. Uno memorizza le proprietà dell'oggetto base (che stai estendendo), mentre un altro gestisce le proprietà del figlio.

Il codice di mappatura quindi itera tutte le proprietà dell'oggetto. Le proprietà derivanti dalla classe estesa verranno inserite nel primo codice. Quelli definiti direttamente sul bambino finiranno nella seconda tabella.

Un sistema simile è richiesto quando si esegue il mapping di nuovo alla codebase dal database. È possibile utilizzare un JOIN SQL per ottenere tutti i record corrispondenti alla classe base (estesa), con le proprietà figlio incluse. Quei record che contenevano le proprietà figlio verrebbero quindi mappati alle istanze di classe figlio.

CREATE TABLE parent(Id INTEGER PRIMARY KEY, A INTEGER); CREATE TABLE child(Id INTEGER PRIMARY KEY, B INTEGER, ParentId INTEGER); classe Genitore { funzione pubblica __construct(int $A) {} }   classe finale Il bambino estende il genitore { funzione pubblica __costruisci(int $A, int $B) {} }   //Ottieni i record //SELECT * FROM parent INNER JOIN child ON child.ParentId = parent.Id; $objs = []; per ogni ($record come $record) { se (isset($record["B"])) { $obj[] = nuovo bambino($record["A"], $record["B"]); } altrimenti $objs[] = nuovo genitore($record["A"]); }

L'introduzione dell'ereditarietà richiede l'uso di una logica di mappatura più complessa. L'incapacità dei database relazionali di modellare le capacità di ereditarietà dei linguaggi orientati agli oggetti introduce questa discrepanza di impedenza.

Visibilità e incapsulamento

Un principio fondamentale della programmazione orientata agli oggetti è la visibilità e l'incapsulamento. Le proprietà sono dichiarate pubbliche o private. La rappresentazione interna di un oggetto può essere nascosta dietro un'interfaccia che preformatta i dati per il consumatore.

I database relazionali mancano di questi controlli. Non c'è modo di dichiarare un campo come privato, né c'è un'ovvia necessità di farlo. Ogni pezzo di dati archiviato in un database ha il suo scopo; non dovrebbero esserci ridondanza.

Questo non è necessariamente vero quando viene trasposto in un paradigma orientato agli oggetti. Un oggetto potrebbe utilizzare due campi di database in aggregato per esporre una nuova parte di informazioni calcolate. I valori dei due singoli campi possono essere irrilevanti per l'applicazione e di conseguenza nascosti alla vista.

CREATE TABLE prodotti(Integer Price, TaxRate INTEGER); Classe finale Prodotto {   funzione pubblica __construct( protetto int $Prezzo, protetto int $TaxRate) {}   funzione pubblica getTotalPrice() : int { restituisce ($this -> Prezzo * ($this -> Aliquota fiscale/100)); } }

L'applicazione si preoccupa solo del prezzo totale del prodotto, tasse incluse. Il prezzo unitario e l'aliquota fiscale sono incapsulati dalla classe del prodotto. I controlli di visibilità (protetti) nascondono i valori. L'interfaccia pubblica consiste in un unico metodo che utilizza i due campi per calcolare un nuovo valore.

Più in generale, la programmazione orientata agli oggetti sostiene la programmazione per interfacce. L'accesso diretto alle proprietà è scoraggiato. L'utilizzo di metodi di classe che implementano un'interfaccia consente di costruire implementazioni alternative in futuro.

Non esiste una controparte diretta a questo nel mondo relazionale. Le viste del database forniscono un modo per combinare tabelle e campi astratti in nuovi moduli. Tuttavia, stai ancora lavorando direttamente con i valori dei campi.

Riepilogo

Le discrepanze di impedenza relazionale agli oggetti si verificano quando un codebase orientato agli oggetti scambia dati con un Banca dati. Ci sono differenze fondamentali nel modo in cui i dati sono modellati. Ciò rende necessaria l'introduzione di un livello di mappatura che trasponga i dati tra i formati.

Il problema del disadattamento di impedenza è uno dei fattori motivanti chiave nell'adozione di ORM all'interno dei linguaggi orientati agli oggetti. Questi consentono l'idratazione automatica di oggetti codebase complessi da origini dati relazionali. Senza un livello di mappatura dei dati dedicato, solo le applicazioni più semplici avranno un percorso diretto da e verso un database relazionale.