Disaccoppiamento dei componenti - Design

1

Ho creato un certo numero di script non banali in Python che eseguono alcuni report a lungo termine. Inizialmente questi rapporti iniziarono come script con un file di configurazione. Poi ho aggiunto un componente del database per mantenere i dati. Ora sto costruendo una GUI per sedermi sopra a tutti loro.

La mia domanda riguarda il design. Ogni volta che aggiungo un requisito aggiuntivo, mi ritrovo a desiderare di aver disaccoppiato i componenti di questi script più di quanto abbia fatto io. Quando considero come ricodificare il mio codice per essere più modulare, trovo che devo passare parecchi riferimenti attorno al codice, al punto che gli inizializzatori di classi e le firme di funzioni hanno una tonnellata di parametri. Inoltre mi trovo ad avere parametri che sono semplicemente "pass-through", in quanto un oggetto li richiede solo per passare a un altro oggetto, senza fare nulla con loro.

Qual è il modo migliore per avvicinarsi alla modularità in Python riducendo al minimo la necessità di passare i riferimenti dappertutto?

Capisco che questo sia molto concettuale e varierà in base alla progettazione, ma sto cercando dei principi guida.

    
posta Patrick D 29.07.2013 - 11:48
fonte

5 risposte

3

Non tendo a incappare in questo problema quando sto provando a modulare. Quindi chiaramente sto facendo qualcosa di diverso, ma senza vedere quello che stai facendo, posso solo indovinare cosa.

Quindi mi limiterò alle banalità generali.

Prima di tutto, quando scrivi un numero di script correlati, di solito è meglio creare piccoli script e una libreria comune. Questo tipo di design tende a sopravvivere meglio a molti refactoring.

In secondo luogo, se si dispone di molti parametri per una funzione, utilizzare parametri con nome e valori predefiniti sensibili. (Vedi link se non sai come usare i parametri con nome.)

In terzo luogo, non c'è nulla di sbagliato nell'avere metodi nelle classi che passano semplicemente le cose in un'altra classe. Finché le classi servono davvero a scopi diversi, va bene.

In quarto luogo, c'è qualcosa di sbagliato nell'avere un sacco di lezioni in giro semplicemente perché serve a fini estetici. Per ogni classe, a meno che tu non possa spiegare esattamente cosa sta facendo questa classe per semplificarti la vita, puoi rimuoverla.

In quinto luogo, il libro Codice completo contiene una quantità enorme di consigli molto dettagliati su tutto, dall'appropriata denominazione delle variabili alla modularizzazione efficace. Se non ci sei riuscito, ti raccomando caldamente l'esercizio.

Sesto, se puoi davvero, vuoi davvero un mentore di cui ti fidi per iniziare a rivedere il tuo codice. Sarei disposto a scommettere che ciò di cui hai più bisogno di essere insegnato è qualcosa che non vedresti mai (motivo per cui hai bisogno di aiuto) che è abbastanza ovvio per una persona più esperta. Insegnare a qualcuno senza essere in grado di vedere quello che stanno facendo non è più efficace della guida di una macchina senza poter vedere la strada.

    
risposta data 29.07.2013 - 17:55
fonte
3

Quando usi l'inversione delle tecniche di controllo come l'iniezione di dipendenza, puoi dare alle classi solo i componenti di cui hanno bisogno per svolgere il lavoro in modo che non debbano preoccuparsi di cosa dipendono dai loro dipendenti. Ciò ridurrà notevolmente il numero di parametri che si passano alle funzioni, ridurrà la complessità degli script e introdurrà un accoppiamento lento tra i componenti. Per dare un buon esempio nel codice psudo, diciamo che stiamo costruendo una macchina.

Potresti passare tutte le parti di un'auto nel costruttore, ma poi deve conoscere il pistone, la bobina e le candele anche se non ha bisogno di conoscerle. Ha solo bisogno di loro per assemblare il motore.

Car(Tires tires, Pistons piston, Coil coil)
{
    SparkPlugs sparkPlugs = new SparkPlugs(piston, coil)
    Engine = new Engine(sparkPlugs, tires)
    Tires = tires;
}

Un modo migliore per farlo è costruire il motore in anticipo e passarlo alla macchina. Quindi l'auto deve solo conoscere le parti che utilizza

Car(Engine e, Tires t)
{
    Engine = e;
    Tires = t;
}
    
risposta data 29.07.2013 - 22:14
fonte
3

Questa è una domanda interessante, ho fatto esattamente la stessa cosa, ma in Ruby. Fondamentalmente la mia soluzione di reporting si è evoluta in questo modo:

  1. Uno script temporaneo per mostrare alla direzione cosa sta succedendo. Lo script ha funzionato solo come app per console, quindi ho copiato e incollato i dati risultanti in Excel e aggiunto una buona formattazione e grafici.
  2. La soluzione temporanea è diventata un rapporto settimanale e poiché le vacanze erano vicine, ho aggiunto un output HTML tramite uno script separato. Le funzioni di uso comune sono state inserite in un file di libreria comune.
  3. Alcuni software aziendali che producono l'input sono cambiati, ma i report erano popolari. A quel punto ho portato il report su Go e cambiato da uno stile procedurale / funzionale a uno stile Object Oriented. (Diventa la lingua franca nel mio lavoro, puoi fare la stessa cosa anche in Python)

Presumo che tu sia nel mio mondo al punto 1. o 2. Come hai visto tu stesso, è necessaria la modularizzazione. Alcuni schemi che ricordo quando lo facevo:

  • passando molti parametri ripetutamente a una funzione? Crea una classe che possa contenere tutti quei parametri. Probabilmente alcune funzioni potrebbero diventare metodi di quella classe
  • andare astratto: ho fatto molte operazioni di raggruppamento, conteggio e aggregazione. Ho creato classi (tipi) che eseguono tali operazioni per molti possibili formati di tabella. Se si dispone di una classe di trasformazione di questo tipo, è possibile definire le funzioni di trasformazione / lambdas (funzionale!) Per i tipi di dati necessari. Quindi in poche parole: rimuovi tutto il business dal problema basti pensare ai tipi di dati e alle trasformazioni di cui hai bisogno.

Per il secondo punto: poiché stai usando Python, penso che Numpy sia uno strumento perfetto per quel lavoro. Ha alcuni strumenti di trasformazione molto potenti per i dati tabulari.

    
risposta data 30.07.2013 - 21:35
fonte
1

Se ognuno di questi componenti utilizza un sottoinsieme di tutti i riferimenti possibili, si potrebbe voler considerare la creazione di un oggetto "controller" che abbia un handle per tutti i riferimenti. Dovresti quindi passare il controller alle funzioni e le funzioni richiamano i riferimenti di cui hanno bisogno.

Ad esempio, invece di:

def foo(a,b,c):
    print "a:", a
    bar(b,c):

def bar(b,c):
    print "b:", b
    baz(c)

foo("this is a", "this is b", "this is c")

Dovresti fare:

class Controller(object):
    def __init__(a,b,c):
        self.a = a
        self.b = b
        self.c = c
    }

def foo(controller):
    print "a:", controller["a"]
    bar(controller)

def bar(controller):
    print "b:", controller["b"]
    baz(controller)

controller = Controller("this is a", "this is b", "this is c")
foo(controller)
    
risposta data 29.07.2013 - 21:45
fonte
0

Hai capito correttamente che la modularizzazione è la chiave per un software ben mantenuto. Senza un problema specifico dovrò limitarlo a una risposta generale.

Struttura

Ogni modulo ha una funzione. Detto questo, ci sono alcune classi che rappresentano l'interfaccia esterna per il modulo e altre che vengono utilizzate solo internamente. Quelli che servono come interfaccia devono ottenere tutto ciò che richiedono per un'azione tramite parametro. Se i parametri sono tipi non banali, è necessario utilizzare un'interfaccia per il tipo di parametro. In questo modo la classe conosce solo una determinata interfaccia pubblica degli oggetti che riceve ma nulla sulla classe specifica.

Alcuni moduli necessitano di altri moduli. C'è un approccio semplice a questo. Hai strumenti, servizi, materiali e valori (vedi strumenti e materiali ). Gli strumenti possono utilizzare ogni tipo di classe. Un servizio può utilizzare solo altri servizi, materiali e valori. Un materiale può utilizzare solo materiali e valori e infine un valore può utilizzare solo valori.

I valori rappresentano valori immutabili nell'argomento. Nell'area bancaria questa potrebbe essere una somma di denaro. Sebbene la quantità stessa possa essere variabile (puoi avere 100, 200, x dollari), il valore non lo è. Non cambi da 100 a 200. Prendi semplicemente la rappresentazione del valore di 200. I materiali rappresentano le cose nell'argomento (come un conto bancario).

I servizi vengono utilizzati per cambiare materiali e operare operazioni (ad esempio un servizio di prestito che viene utilizzato per prestare film). Contengono inoltre la logica aziendale per determinare se una determinata operazione è valida. Gli strumenti sono il punto di interazione con l'utente. Lavorano principalmente su un materiale o servizio. Ci possono essere sotto-strumenti. Gli strumenti sono l'interfaccia per la GUI (sia essa basata sul desktop basata sul web).

Detto questo puoi garantire che ogni modulo contenga un solo tipo di classi e una sola responsabilità.

Iniezione di dipendenza

Ma ora come funziona Dependency Injection senza inquinare altre classi? È consentito utilizzare nuove istruzioni all'interno delle classi di strumenti per creare strumenti secondari. Poiché gli strumenti sono per lo più orientati alla tecnologia, non è molto facile testarlo automaticamente (in quanto richiedono ad altre classi di fare il lavoro pesante). Importante è che ogni dipendenza da un altro modulo (che si tratti di servizi, materiali o valori) viene fornita tramite parametro.

Alla fine hai bisogno di una classe che leghi insieme i moduli, inizializzi i servizi necessari nel software e li assegni alle classi appropriate tramite l'iniezione di parametri (sia nel metodo costruttore che setter).

Gli strumenti tra l'altro ottengono i loro riferimenti agli oggetti necessari (se non tramite parametri) dalla GUI, quindi non c'è molto inoltro di riferimenti attraverso il sistema.

    
risposta data 30.07.2013 - 13:17
fonte

Leggi altre domande sui tag