Domanda sul polimorfismo aperto (motivazione - dati di output per i tipi di file diff)

0

Diciamo che ho qualche classe, e all'interno di istanze di quella classe voglio essere in grado di produrre determinati dati in vari tipi di file, ad es. CSV, SQL, PDF, ecc.

Il modo più semplice è solo una serie di istruzioni if, ma generalmente è un sistema chiuso. Chiunque desideri estendere il codice deve avere il controllo della classe e la possibilità di modificare il codice esistente.

Ecco un esempio davvero ingegnoso solo per dare qualcosa di più concreto:

class Animal:
    def __init__(self,species,eats):
        self.species = species
        self.eats    = eats
        self.data_outputs {'sql':(server,table), 'csv':path}    

    def is_carnivorous(self):
        self.eats == 'meat'

    def is_hippie(self):
        self.eats == 'wheatgrass'

    def write(self,output_type,data):
        output_loc = self.data_outputs[output_type]
        if output_type   == 'sql':
            cursor = connection.cursor()
            cursor.execute(some_insert_statement,data)
        elif output_type  == 'csv':
            with open(path,"r") as out:
                out.write(data)

Se la logica di write è molto coinvolta, forse la delega a una classe per gestire la logica potrebbe rendere il metodo un po 'più pulito:

class Animal:
    def __init__(self,species,eats):
        self.species = species
        self.eats    = eats
        self.data_outputs {'sql':(server,table), 'csv':path}    

    def is_carnivorous(self):
        self.eats == 'meat'

    def is_hippie(self):
        self.eats == 'wheatgrass'

    def write(self,output_type,data):
            output_loc = self.data_outputs[output_type]
            if output_type   == 'sql':
                SQLWriter.write(*output_loc,self.data)
            elif output_type  == 'csv':
                CSVWriter.write(output_loc,data)


class SQLWriter:
    def write(self,server,table,data):
        cursor = connection.cursor()
        cursor.execute(some_insert_statement,data)


class CSVWriter:
    def write(self,path,data):
        with open(path,"r") as out:
            out.write(data)    

In entrambi i casi, entrambi i sistemi sono chiusi, poiché la modifica del comportamento di scrittura richiede una sottoclasse dell'intera classe Animal solo per sovrascrivere write . Probabilmente è ancora peggio nell'esempio di delega della classe perché ora hai accoppiato la creazione della classe con il metodo write così ora ogni volta che vuoi creare un nuovo tipo di output devi modificare il metodo write_data , e crea la classe appropriata.

La soluzione pitonica è simile a questa per usare solo SQLWriter e CSVWriter come mixins?:

class Animal(CSVWriter):
    def __init__(self,species,eats):
        self.species = species
        self.eats    = eats

    def is_carnivorous(self):
        self.eats == 'meat'

    def is_hippie(self):
        self.eats == 'wheatgrass'

Ciò mantiene il sistema aperto, nel senso che chiunque desideri creare un nuovo tipo di output ha semplicemente bisogno di scrivere un nuovo mixin ed ereditare da esso, e nessun codice esistente deve essere toccato per farlo. Tuttavia, gli utenti di Animal è necessario essere consapevoli che devono ereditare da un mixin per ottenere la capacità write , che non sembra ideale.

Se il metodo di scrittura dipendeva dal tipo effettivo di classe contenente i dati ( Animal in questo esempio), sarebbe semplice che avessi solo un metodo write in quella classe che qualsiasi sottoclasse può oltrepassare. Ma in questo esempio voglio che il metodo write sia polimorfico su dati specifici all'interno della classe, non sulla classe stessa.

Qual è la soluzione standard Pythonic (o più generalmente Object Oriented) per questo?

MODIFICA : se stai andando a downvotare, almeno spiegaci qual è il tuo problema con la domanda, così posso correggerlo di conseguenza.     
posta Solaxun 30.07.2018 - 21:35
fonte

2 risposte

3

In entrambi i tuoi esempi, la classe Animal è strettamente accoppiata agli scrittori, poiché Animal utilizza direttamente SQLWriter e CSVWriter . Se si desidera aggiungere un nuovo formato di output, è necessario toccare la classe Animal . All'inizio questo potrebbe non sembrare così male, ma una volta che ottieni più classi scrivibili, l'aggiunta di nuovi formati diventa un enorme carico di lavoro perché tutte queste classi devono essere cambiate. In termini di complessità, l'aggiunta di un nuovo formato è O (n), dove n è il numero di classi scrivibili.

Una soluzione migliore è passare lo scrittore come argomento alla funzione di scrittura. Quindi puoi cambiare il formato di output semplicemente passando uno scrittore diverso e le classi scrivibili rimangono intatte. Il seguente esempio dovrebbe darti un'idea:

class Animal(object):
    def __init__(self, species, eats):
        self.species = species
        self.eats = eats

    def store(self, writer):
        writer.write(self.species)
        writer.write(self.eats)


class SQLWriter(object):
    def __init__(self, server, table):
        self.server = server
        self.table = table

    def write(self, data):
        print("Storing", data, "in SQL", self.server, self.table)


class CSVWriter(object):
    def __init__(self, path):
        self.path = path

    def write(self, data):
        print("Storing", data, "in CSV file", self.path)


def main():
    my_animal = Animal("some_species", "some_food")

    csv_writer = CSVWriter("some_file.csv")
    my_animal.store(csv_writer)

    sql_writer = SQLWriter("some_server", "some_table")
    my_animal.store(sql_writer)


if __name__ == "__main__":
    main()
    
risposta data 31.07.2018 - 13:58
fonte
0

Il sistema non consente commenti come novità del sito!

Questo suona molto come metodo / sovraccarico di funzioni.

Vengo dallo sfondo C ++, che supporta funzioni complete e sovraccarico di metodi. So che anche Python dovrebbe supportarlo, ma non posso sapere fino a che punto.

L'idea è nella tua classe Animal puoi definire quante versioni di Writer () vuoi, tutte con lo stesso nome "writer" ma firme diverse per tipo e numero di parametri.

Utilizzato nella sua forma più semplice, se sai che i tuoi dati sono SQL quindi invoca Writer ("SQL", connessione ecc.) e se sai che i tuoi dati sono CSV invoca Writer ("CSV", percorso file ecc.)

Con un po 'di pensiero questo potrebbe essere reso automatico e il suggerimento del pattern Visitor o più probabilmente "double dispatch" ti porterebbe nella giusta direzione.

Il sovraccarico di nomi di funzioni può essere ciò che intendi quando chiedi "polimorfismo sui dati". (Oppure no se ho frainteso quello che stai chiedendo :-))

    
risposta data 31.07.2018 - 11:38
fonte

Leggi altre domande sui tag