Qual è il posto giusto per la logica aziendale complessa

1

Ho un oggetto che descrive un grafico bidimensionale:

class Graph {
  var points; // Array of points
}

Ora ho bisogno di trovare alcuni pattern complessi in questo grafico, come (ma più complessi):

  • Trova due punti consecutivi con le maggiori differenze.
  • Calcola per ogni punto la media degli X punti precedenti.

Mi chiedo dove dovrebbe essere questa logica:

  1. In classe Graph per connettersi tra i dati alla sua logica.
  2. Il grafico contiene solo i dati e la funzione per gestirlo, e la logica sarà in un'altra classe (e) con funzioni statiche che otterranno il Graph come argomento.
  3. La funzione grafica fornirà un'API che ottiene l'istanza della classe logica aziendale.

    class Graph {
        var points; // Array of points
        public function findHighestDifferent(instance:HighestDiffFinder) {
            var filteredPoints = points.filter(...);
            instance.find(filteredPoints);
        }
    }
    
  4. Un modo migliore:)

posta Michael 24.06.2018 - 16:15
fonte

4 risposte

13

"Grafico" è un termine ambiguo, riferito a diversi tipi di oggetti matematici.

Ma qualunque sia il tipo di grafico che hai in mente, ognuno di questi oggetti è un'astrazione a sé stante, con determinate operazioni che sono essenziali per definire l'astrazione. Ad esempio, se pensi a un grafico nel senso di un grafico a linee , le operazioni tipiche potrebbero essere

  • aggiungi alcuni nuovi punti che vengono automaticamente inseriti in un modo per mantenere ordinati i punti in base alle loro coordinate X

  • estrai i punti nell'ordine delle coordinate X

  • forse cancella o sostituisci alcuni punti

  • forse operazioni per calcolare le linee intermedie tra due punti vicini.

Queste sono le public operazioni che dovrebbero far parte della classe Graph stessa - un grafico che è solo un contenitore per un elenco di punti, come mostrato nell'esempio, senza un'API pubblica che forma l'astrazione, è piuttosto inutile. D'altra parte, ogni operazione che non fa parte di questa astrazione è meglio posizionata altrove.

Dove esattamente, dipende . Dipende dal modo in cui il grafico verrà utilizzato nel sistema, da quali altri punti del sistema sono disponibili o potrebbero essere disponibili e dalla singola operazione. A volte una classe da sola va bene. A volte una funzione statica è ok. A volte una funzione all'interno di un'altra classe è sufficiente, se quell'altra classe è il posto unico nel sistema in cui è necessaria l'operazione. E a volte va bene fare un'eccezione alla regola, rimanere pragmatici e posizionare la funzione direttamente nella classe Graph , solo perché al momento non hai un posto migliore.

Nota che non esiste una regola ferrea che dice "questo o quel luogo è meglio", questo è in qualche modo supponente. Cerca di seguire il principio SOLID, cerca di creare astrazioni coerenti con responsabilità disgiunte e refactoring man mano che il programma si evolve. Spesso un'operazione molto semplice, abbastanza semplice da essere solo una funzione, inizia a diventare più complessa nel tempo, il che rende necessario rifattorizzare la logica della funzione in molte funzioni più piccole che verranno quindi ridotte a una classe autonoma.

    
risposta data 24.06.2018 - 16:52
fonte
2

Separazione di preoccupazione

Il principio di design chiave qui è separazione delle preoccupazioni :

  • Da un lato ci sono grafici, che per definizione dovrebbero fornire nodi, bordi e la navigazione tra di essi.
  • Dall'altra parte ci sono algoritmi complessi che funzionano su grafici, ad esempio per esplorarli, annotarli o costruire percorsi.

La separazione delle preoccupazioni suggerisce di implementare gli algoritmi complessi indipendentemente dal grafico (più precisamente, è una separazione tra diversi livelli, quindi sarebbe un " separazione orizzontale "). Ovviamente, ciò richiede innanzitutto di definire un'interfaccia corretta per Graph . Oggi è una raccolta di Points . Ma domani, potresti lasciarlo evolvere come una matrice di adiacenza 2D, o una tabella di elenchi di adiacenze, o qualsiasi altra struttura di dati adatta:

  • Se i tuoi algoritmi verrebbero incorporati in Graph , potrebbero utilizzare la conoscenza della struttura interna di Graph . Pertanto, cambiando Graph , potrebbe essere necessario modificare gli algoritmi. Ciò renderebbe il tuo sistema difficile da mantenere.
  • Mantenere i tuoi algoritmi indipendenti dagli interni di Graph (cioè utilizzando solo l'interfaccia pubblica di Graph ) creerebbe al contrario un'architettura molto più robusta. È possibile modificare l'algoritmo indipendentemente dal grafico e viceversa.
  • L'indipendenza ha un altro ovvio vantaggio: se domani uno dei tuoi complessi algoritmi ti potrebbe aiutare nello sviluppo del software GPS di prossima generazione, sarebbe un gioco da ragazzi implementare un adattatore Graph per la mappa GPS e riutilizzare il tuo codice.

Opzione migliore

La tua opzione 2 sembra implementare la separazione delle preoccupazioni. Quindi questo è il modo di seguire.

Potresti anche pensare di incapsulare gli algoritmi in qualche classe. Ciò, ad esempio, avrebbe il vantaggio di consentire di derivare algoritmi se alcuni di essi hanno parti comuni.

Puoi prendere in considerazione l'utilizzo del modello di strategia per apportare alcune variazioni a un'implementazione utilizzando diverse strategie di runtime (ad es. findHighestDifference e findLowestDifference ).

Non conosco la tua lingua, ma potresti essere interessato a dare un'occhiata al design di incrementa la libreria di grafi , che è molto elegante e completa sotto tutti questi aspetti.

    
risposta data 24.06.2018 - 18:45
fonte
1

Hai difficoltà a rispondere alla tua domanda perché manca il contesto. Tutti i tuoi suggerimenti forniscono un modo per inserire funzionalità in una funzione / metodo. La semplice necessità di questa funzionalità non aiuta a trovare un buon modo per posizionarlo.

  1. In classe Graph per connettersi tra i dati alla sua logica.

    • in modo che tu possa operare su dati privati nell'oggetto Graph
    • no perché non vuoi trasformare questa cosa in un oggetto divino
  2. la logica sarà in un'altra classe (o le classi) con funzioni statiche che otterranno il grafico come argomento.

    • quindi la funzionalità è più modulare, può essere sviluppata / testata individualmente
    • no perché entrambe le funzioni che hai menzionato

      Find two consecutive points with the biggest differences.

      Calculate for each point the average of the X previous points.

      hanno uno stato proprio che potresti voler salvare / caricare o rendere configurabile dall'utente, quindi quelle funzioni non dovrebbero essere static .

Smetti di pensare al bottom-up dalla tua classe di dati quando si tratta di implementare funzionalità. Troverai il posto giusto per implementare le tue funzionalità quando lo guardi da una prospettiva dall'alto verso il basso.

  • Quali dati in quale ambito opera la funzione? Quando cerchi i punti consecutivi, ad esempio, in quel punto del tuo codice, sai che dovrebbe cercare due (e non tre o quattro) punti consecutivi? O è che alcune informazioni fornite dall'utente in un TextBox di una GUI, che preferiresti non essere valutate o meno in quel punto del tuo codice?
  • Quanto è complessa la funzionalità? Dovrebbe funzionare in modo asincrono? Su una discussione diversa? Su una macchina diversa?

Oltre agli aspetti di scrittura / distribuzione / mantenimento del codice.

  • Alcuni sviluppatori svilupperanno le funzioni? La gestione delle modifiche a un singolo file da parte di più utenti è possibile, ma può portare a problemi.
  • Ogni funzione deve essere testata individualmente?
  • Vuoi aggiungere più funzioni in seguito a una versione precedente del software già distribuita?
risposta data 24.06.2018 - 17:32
fonte
1

Gli esempi che hai fornito non sono "complesse logiche di business". Sono domande abbastanza semplici su un "grafico" (che molte persone chiamerebbero un elenco di punti, i grafici hanno nodi e archi). L'implementazione di questi metodi (ad esempio "coppia più vicina consecutiva") non cambierebbe a causa dei mutevoli requisiti aziendali. Quindi potresti ragionevolmente mettere quel codice nella classe Graph. Questa sarebbe la mia prima scelta. Questo è abbastanza buono per una classe utilizzata da una singola applicazione. Se ci fosse il desiderio di riutilizzare quel codice in più artefatti, allora penserei più difficile.

La "complessa logica aziendale" deriva solitamente da vincoli del mondo reale che devono essere rispettati dalla soluzione e che sono soggetti a modifiche. Ad esempio, un programma che pianifica i conducenti commerciali o l'equipaggio dell'aeromobile deve seguire i regolamenti che specificano un tempo di servizio massimo.

Questo è un tipo molto comune di domande su SE. Circa il 99% delle volte la risposta è "Non importa", perché il codice in questione ha un unico chiamante e la decisione è facilmente modificabile. Metti la logica ovunque sia conveniente e non crei una dipendenza circolare tra le classi (perché le classi con dipendenze circolari non possono essere testate unitamente). Quando diventa inopportuno, allora refactoring. Puoi farlo perché hai un buon set di test unitari e test di integrazione, giusto?

    
risposta data 25.06.2018 - 10:46
fonte

Leggi altre domande sui tag