Progettazione di un'applicazione di disegno simile a un grafico

1

Ho a che fare con un'app di disegno che consente all'utente di disegnare un set di componenti (che sono come i vertici del grafico) e possono essere cablati (come i bordi del grafico) l'uno sull'altro per simulare il modo in cui la connessione si propagano e influenzano ogni componente in base al loro input, come una funzione su ciascun nodo che prende tutti gli input, applica la sua funzione e imposta i suoi output. Quindi, propaga il valore su altri componenti collegati alle uscite.

La difficoltà sta nel fatto che il mio Component è anche un Draw da dipingere su una tela. Solitamente problemi come questo vengono risolti usando la soluzione di auto-disegno come nell'esempio seguente:

abstract class Component {
    // component stuff
    inputs: [] = [];
    outputs: [] = [];

    abstract function(): void;

    // drawing stuff
    x: number = 0;
    y: number = 0;

    draw(drawer: Drawer){
       drawer.drawCircle(this.x, this.y, 20);
    }
}

Ma sembra che mescolare il disegno del con il significato di quel Vertice sia un cattivo design e davvero disordinato quando si cerca di capire il codice.

Ho visto persone suggerire di creare classi diverse per Draw e Component e il disegno contiene il riferimento del componente reale. Sembra ragionevole, ma non so se questa sia la soluzione più pratica perché sembra che devi duplicare l'intera struttura sotto forma di grafico solo per separare il disegno da esso.

Vorrei sapere se esiste un approccio migliore per questo tipo di problema.

    
posta lenilsondc 21.06.2018 - 20:06
fonte

2 risposte

2

Puoi benissimo lasciare che Component e Wire siano entrambi Draw . Funziona molto bene finché c'è solo una singola tela da disegnare. Potrebbe persino funzionare con diverse viste e aree di disegno, purché esista una semplice formula di trasformazione tra di esse (ad esempio ridimensionamento).

Il principale inconveniente di un tale progetto è tuttavia che le classi di componenti e fili potrebbero dover cambiare a causa della logica di business o della logica di disegno: due ragioni per il cambiamento non sembrano totalmente secondo Principio SRS di SOLIDO.

L'approccio alternativo consiste nell'usare Component e Wire solo come oggetti di business, senza una rappresentazione grafica immediata. Dovresti quindi utilizzare DrawableComponent e DrawableWire come Draw come grafico proxy per gli oggetti business .

È un po 'più complesso (cioè più parti), senza essere complicato (cioè è facile capire i ruoli dei diversi oggetti). Ma ci sono molti vantaggi:

  • Puoi facilmente utilizzare diverse visualizzazioni grafiche molto diverse (ad esempio diagrammi che ottimizzano il layout in base agli oggetti visibili, il che significa che le coordinate relative potrebbero non essere le stesse tra le visualizzazioni)
  • È possibile gestire facilmente diverse rappresentazioni grafiche per i componenti, se vengono utilizzati in contesti diversi (ad esempio si può ben rappresentare lo stesso messaggio UML (oggetto business) in diversi diagrammi di interazione UML (rappresentazioni grafiche di tale oggetto).
  • Grazie all'SRP, potresti avere un team responsabile degli oggetti di business e un altro team responsabile dei disegni: l'intero sistema sarà più maneggevole e manutenibile.

Se dovessi essere responsabile di un sistema di informazioni geografiche che rappresenta migliaia di diversi tipi di componenti (tubi del gas e attrezzature, tubi e apparecchiature elettriche, fibra di telecomunicazioni, ...) su mappe (con grafici orizzontali 2D piatti, Grafici verticali sottoterra 2D, grafici 3D, ecc ...), e la tua base di codice sarebbe di diversi milioni di SLOC, apprezzerai particolarmente questo ultimo vantaggio ;-)

    
risposta data 24.06.2018 - 20:25
fonte
1

Dove posizionare la logica di disegno dipende da quanto flessibile / estensibile dovrebbe essere il sistema. Ma separare gli oggetti / componenti dalla loro visualizzazione è in generale una buona idea.

Un modo per implementare questo è fare in modo che gli oggetti forniscano tutti i dati e le (meta) informazioni necessarie per disegnarli, ma non la logica del disegno reale. Ad esempio, un oggetto Component fornisce già x e y, può anche fornire qualcosa come un tipo di forma (cerchio, quadrato, qualsiasi cosa tu voglia), forse una dimensione del disegno. Quindi hai una classe DrawAll separata che interpreta la meta informazione di tutti gli oggetti per disegnarli. Questo approccio va bene per un piccolo numero fisso di tipi di forma con logica di disegno non troppo complessa.

Un altro approccio consiste nel lasciare che il metodo draw sia ora nel tuo esempio, ma creare Drawer un'interfaccia astratta con qualcosa che possa disegnare "da qualche parte". Ciò disaccoppierà anche il livello "oggetti" dal livello "disegno". Ha il vantaggio di semplificare l'aggiunta di nuovi oggetti con una logica di disegno completamente imprevista al sistema, ma lo svantaggio di avere almeno un codice di disegno in ogni classe Component .

E infine, sì, è possibile avere una classe di disegno per tipo di oggetto, e sì, questo ha lo svantaggio di duplicare la struttura della classe in una certa misura. Ma potrebbe essere utile se ti aspetti che la logica del disegno sia così complessa da giustificare una classe a sé stante per ogni specifico algoritmo di disegno.

    
risposta data 21.06.2018 - 23:34
fonte

Leggi altre domande sui tag