Creazione di astrazione su API

1

Sto cercando di capire come creare l'astrazione su diverse API, che hanno cose comuni. Ad esempio, prendiamo le piattaforme mobili che hai Android, Windows Phone e IOS. Diciamo che voglio creare e API o programma o linguaggio specifico del dominio, che mi consentirà di scrivere un programma e lavorare su tutte le piattaforme indicate. Ovviamente questo è solo un esempio e alla fine tutte le piattaforme hanno molto in comune che mi permettono di scrivere programmi e librerie senza pensare troppo all'astrazione, ad esempio tutti hanno un compilatore C. Un esempio migliore sono i sistemi ERP, diciamo che voglio scrivere lo stesso plug-in per 3 diversi sistemi ERP, come potrei creare astrazioni al di sopra delle loro API che mi permetteranno di scriverlo una volta e di compilarlo (compilarlo o qualunque cosa tu possa chiamare it), e usarlo su tutti i sistemi. Per semplicità diciamo che quei sistemi ERP stanno scrivendo tutti nello stesso linguaggio di programmazione, ma le loro API non hanno nulla o poco in comune in termini di nomi di funzioni ecc. Hanno solo funzionalità simili. Ho pensato di usare la metaprogrammazione, in modo tale che io dica al programma ciò che voglio, e l'utilizzo di una serie di regole diverse per ogni API produrrà programmi per ciascuna delle API. Un altro pensa a ciò che ho pensato è incapsulare tutte le caratteristiche comuni nelle classi, che manterrà il codice (dati) per ogni API, e lo produrrà quando richiederò la funzionalità per una determinata API. Darò un esempio. Diciamo che ho 3 API che hanno tutte classe per la stampa, e i nomi sono come segue:

  1. Stampante - PrintToLog (Dati)
  2. Outputer - OutputToLog (Data, lunghezza)
  3. IO - PrntLogData (Data, logPtr)

Per semplicità, tutte queste classi sono statiche. Potrei creare una classe come questa (pseudo-codice)

Class Printer{
    api //holds for which API the code is requested
    constructor(API){
        this.api = API
    }
    func logData(Data){
        switch API{
            case API_1 //let's say the API are stored in ENUM or something similar
                return "PrintToLog(" + Data + ")"
            case API_2
                return "OutputToLog(" + Data + "," + len(Data) + ")" // len returns the lenght of data
            case API_3
                return "PrntLogData(" + Data + ", /default/log/location)"
        }
    }
}

Tuttavia non sono sicuro di quanto siano pratici e affidabili quei metodi. Inoltre non ero in grado di trovare molte informazioni per questi tipi per creare astrazioni. Ci sono serie di schemi, metodologie o principi per tali astrazioni?

    
posta nameLess 30.10.2016 - 21:40
fonte

2 risposte

2

Questo è un problema comune nello sviluppo del software. Esistono due tecniche che possono essere utilizzate:

  • È possibile progettare l'interfaccia in modo che contenga solo metodi comuni condivisi da ogni implementazione sottostante.

    Il vantaggio è che passare da un'implementazione all'altra sarebbe estremamente semplice, senza bisogno di modificare alcun codice. Ad esempio, un'interfaccia comune potrebbe rendere possibile l'interazione con Google Maps o Bing Maps pur contenendo metodi supportati dalle API di Google e Microsoft. Poiché entrambe le API sono molto simili, è relativamente facile creare un'interfaccia comune che rimarrebbe comunque molto efficace.

  • È possibile fornire metodi che sono supportati solo in alcune implementazioni, ma non in altri, o non bene. Ciò richiederà che il chiamante sia in grado di determinare se una determinata azione è supportata (senza necessariamente sapere quale sia l'implementazione in uso).

    Il vantaggio è che è possibile fornire molte più funzionalità rispetto al sottoinsieme comune e utilizzare anche i punti di forza di alcune implementazioni. Ad esempio, alcune funzioni sono eseguite molto meglio da iOS, mentre altre sono meglio gestite da Android. Se prendi a malapena le funzioni comuni, non trarrai vantaggio da queste funzioni.

Attuazione

Non mi piace molto la tua dichiarazione switch . Una soluzione comune è per utilizzare l'ereditarietà : nel tuo caso, significa avere un adattatore per implementazione che assicurerà la comunicazione tra la tua API e il sistema o l'API sottostante.

Utilizzando Iniezione delle dipendenze, sarà quindi possibile selezionare l'adattatore da utilizzare in fase di runtime. Ogni adattatore potrebbe essere modificato indipendentemente dagli altri.

Esempio:

interface IGeolocation
{
    Coordinates addressToCoords(string address);
    string coordsToAddress(Coordinates position);
}

class GoogleMapsGeolocationAdapter : IGeolocation
{
    ... // Concrete implementation of addressToCoords and coordsToAddress.
        // Each method calls the Google's API.
}

class BingMapsGeolocationAdapter : IGeolocation
{
    ... // Concrete implementation of addressToCoords and coordsToAddress.
        // Each method calls the Microsoft's API.
}

La classe che ha bisogno di ottenere latitudine e longitudine da un indirizzo deve semplicemente richiedere IGeolocation per essere passata al suo costruttore, e quindi chiamare addressToCoords , indipendentemente dall'implementazione concreta che è stata passata attraverso Dipendenza Iniezione. Appartiene all'applicazione stessa per determinare, in base alla configurazione, quale classe deve essere inizializzata: GoogleMapsGeolocationAdapter o BingMapsGeolocationAdapter .

Nel caso di iOS e Android, la scelta dell'implementazione dovrebbe essere basata sulla piattaforma o durante la compilazione dell'applicazione.

    
risposta data 30.10.2016 - 21:51
fonte
1

Come complemento a quanto già detto, userei un approccio con funzione (non nel senso di funzionale di programmazione, ma "qual è lo scopo di cosa sto scrivendo? "," Cosa dovrebbe fare "?)

In altre parole, non avrei solo un approccio dal basso verso l'alto (induttivo), cercando di partire da ciò che le tre API hanno in comune. Bene, immagino che prima dovresti familiarizzare con le implementazioni inferiori, per familiarizzare con ciò che è possibile, difficile o impossibile.

Ma dal momento che desideri creare una astrazione ( abstract significa "pensiero separato da realtà concrete, oggetti specifici o istanze reali"), stai cercando di mettere un pensiero lì che non era lì in primo luogo. Quindi devi davvero dedicare un po 'di riflessione alla tua entità di per sé , a parte le istanze attuali. Come regola generale, " la fonte dell'astrazione si trova in te, non nelle istanze ".

Più in particolare: vuoi creare una classe per la stampante (o qualcosa del genere).

  1. Idealmente, come ti piacerebbe che la stampante si comporti, per soddisfare esattamente le tue esigenze? Come lo descriveresti? Elabora i tuoi casi d'uso, quali metodi e proprietà dovrebbero esportare.

  2. Quindi solo, scopri come implementare il tuo oggetto usando ciascuna delle implementazioni ERP che hai a disposizione (tipicamente per sottoclasse o commutazione). Alcuni potrebbero essere più semplici, altri potrebbero essere più difficili.

  3. Quando ritieni che ci sia qualcosa di irrisolvibile o se vedi un'opportunità per migliorare il tuo modello comune, esegui il backup.

  4. Lavora alternativamente su e giù finché non hai finito.

Se hai la tua visione di ciò che dovrebbe essere la stampante ideale (in base alle tue esigenze specifiche), non sarai influenzato da un'implementazione o da un'altra. Non sarai influenzato nell'implementazione di cose che non ti servono ("just in case"). Il tuo codice sarà breve e al punto. E se il processo è fatto bene, potresti finire per fornire un'API bella e pulita che è 10 volte più facile da usare rispetto a quella originale - almeno per il tuo caso d'uso.

E oltre a salvare uno sviluppatore il tempo necessario per fornire ciascuna piattaforma, sarebbe un secondo merito.

Al contrario, se implementi semplicemente un "massimo comune denominatore" o una "implementazione comune con buchi", il risultato potrebbe essere solo un altro livello , aggiungendo più complessità (e requisiti di documentazione) invece di rimuovendolo.

    
risposta data 31.10.2016 - 14:58
fonte

Leggi altre domande sui tag