Come assicurarsi che le persone chiamino i metodi nell'ordine corretto?

5

Situazione

Sto progettando un livello di astrazione del database per sql (mysql, sqlite) e mongoDb. L'obiettivo è fornire all'utente / sviluppatore una libreria in grado di creare query per diversi database. Senza nemmeno sapere quale tipo di database viene interrogato.

Diciamo che abbiamo una classe che è in grado di creare ad esempio la dove parte (condizione) di una query come (in caso di sql):

name = 'Toni' AND age = 42 AND (status = 'ok' OR status = 'maybe-ok')

Il modo in cui creare questa stringa chiama diversi metodi di un oggetto in una determinata sequenza.

La classe dalla quale l'oggetto è derivato potrebbe apparire (pseudo):

class ConditionCreator:
    private-field condition


    public-function and:
        self.condition += ' AND '
        return self
    end

    public-function or:
        self.condition += ' OR '
        return self
    end

    public-function block:
        self.condition += '('
        return self
    end

    public-function blockend:
        self.condition += ')'
        return self
    end

    public-function comparison(key, value):
        self.condition += key + ' = ' + value
        return self
    end

end

E le chiamate a dovrebbe essere simile a:

creator = new ConditinoCreator
creator.comparison('name', "'Toni'")
    .and()
    .comparison('age', 42)
    .and()
    .block()
        .comparison('status', "'ok'")
        .or()
        .comparison('status', "maybe-ok")
    .blockend()

Problema

Come ci si dovrebbe assicurare che le chiamate siano nell'ordine corretto ?

  • Nessun confronto dopo un confronto
  • Nessun operatore (o, e) dopo un operatore (o, e)
  • Nessun operatore dopo un blocco (
  • Nessun confronto dopo un blocco )
  • Lo stesso numero di ) del blocco e% co_de del blocco
  • ecc. pp.

Cose a cui ho pensato:

Regex

Il mio primo pensiero è stato quello di aggiungere una seconda variabile di classe alla quale ogni metodo (e, o, bloccare, blockend, confronto) aggiunge un carattere in modo che l'esempio produca questa stringa ( dopo che questa stringa è stata generata si potrebbe fare un po 'di regex magic su di esso per verificare che la condizione sia valida.

Programma

Come dice il titolo, è necessario programmare manualmente le regole dalla sezione Problema . Se questa è la strada da percorrere, come si farebbe? Si creerebbe una stringa come quella nella soluzione Regex e si testerebbe contro le regole?

Fai affidamento sull'implementazione del database sottostante

Non testare / convalidare la condizione e affidarsi al database per generare errori (il che è orribile per il debug dato che ogni database fornisce un livello diverso di dettagli di output e fornire messaggi di errore chiari è davvero difficile).

C'è di più?

C'è qualcosa che mi manca completamente?

    
posta aichingm 24.08.2015 - 16:11
fonte

1 risposta

12

L'interfaccia fluente per un linguaggio specifico del dominio interno è costruita sulla parte superiore del pattern Builder. In definitiva, stai costruendo una rappresentazione che deve essere utilizzata.

La soluzione è quindi utilizzare un builder stateful (related: Come dovrei gestire configurazioni incompatibili con il pattern Builder? ). L'oggetto che si restituisce deve essere limitato al set di metodi che è opportuno chiamare su di esso.

Per l'esempio di codice:

creator = new ConditinoCreator
creator.comparison('name', "'Toni'")
    .and()
    .comparison('age', 42)
    .and()
    .block()
        .comparison('status', "'ok'")
        .or()
        .comparison('status', "maybe-ok")
    .blockend()

l'oggetto restituito da comparison ha solo i metodi blockend , and e or . Allo stesso modo, l'oggetto restituito da and e or ha solo i metodi block e comparison .

No, questo non gestirà completamente tutte le situazioni - puoi ancora avere problemi con la chiamata di blockend quando non c'è block (potresti anche rendere il blocco un tipo speciale di oggetto, ma poi entri nel divertimento di blocchi annidati) o la possibilità di avere una percentuale di% co_de o% co_de penzolanti, ma gestirà la maggior parte delle situazioni e imporrà alcune formalità alla struttura. Questo è in effetti usando il sistema di tipi della lingua per fare il lavoro per te. La quantità di abilità che devi controllare dipende dal tipo di sistema.

Tecnicamente, il modo in cui ho descritto i tipi di cui sopra è quello di una tabella di stati finiti (e una lingua normale) e quindi non può fare arbitrarie profondità di annidamento (che la corrispondente lingua interna sembra supportare). Considerare questo come una tabella di stato può aiutarti a capire come usarlo. A seconda della lingua, tuttavia, potrebbe essere possibile ottenere un sistema più potente. Alcune lingue hanno un sistema di tipi molto più potente che ti consente di fare cose pazze o potrebbe essere Turing complete , altri sono molto più limitati. Ora, se vuoi provare a trarre vantaggio da una macchina completa di Turing per riconoscere il linguaggio interno nel sistema di tipi, sembra una buona sfida, anche se si corre il rischio di essere troppo intelligente.

Per evitare alcuni di questi problemi:

  • Hai un and alla fine che restituisce il tipo finalizzato appropriato. Questo può evitare il penzoloni or o .build() .

  • Invece di .and() avere .or() essere la firma del metodo che usa un oggetto di confronto già esistente (probabilmente uno creato lì). Poiché ogni blocco è completo, la nidificazione arbitraria non è qualcosa che il sistema di tipi deve gestire.

Prendendo entrambi questi punti, il codice di esempio dovrebbe essere:

Comparison comparitor = (new ComparisonBuilder())
    .comparison('name', "'Toni'")
    .and()
    .comparison('age', 42)
    .and()
    .block((new ComparisonCreator()).
        .comparison('status', "'ok'")
        .or()
        .comparison('status', "maybe-ok")
        .build()
    ).build()
    
risposta data 24.08.2015 - 17:46
fonte

Leggi altre domande sui tag