Devo utilizzare la composizione o l'ereditarietà per condividere elementi di configurazione?

2

Sto lavorando a un progetto abbastanza nuovo in cui mi piacerebbe condividere alcuni elementi di configurazione usati da un plugin JavaScript. Nello specifico, il progetto utilizza jQuery dataTables e voglio trovare un buon modo per condividere gli elementi di configurazione.

Al momento, il codice che ho ricevuto è abbastanza semplice. È approssimativamente equivalente a:

// It might not be super-essential to read this

function TableSuperClass(tableElement, moreOptions) {
    var options = {
         pagination: true,    // turn on pagination
         iDisplayLength : 50, // pages are fifty rows long
         lengthChange: false, // stop users changing length of pages
         searching : false    // disables searching
    };

    jQuery.extend(options, moreOptions); // merge moreOptions into options
    jQuery(tableElement).DataTable(options); // we create a table with the extended options

    // This is a super-handy method we'd like to use everywhere
    this.repopulate = function usefulRepopulateFunction() {...};
}

function ObjectOwningTable() {
    // this object does other things too

    this.table = TableSuperClass({
         // various column mappings for a particular table type and
         // what to order the table on
         columns : [...],
         // implements row highlighting, partly based on column mappings
         fnRowCallback : function() {...} 
    });
}

function AnotherObjectOwningTable() {
    // this object does other things too

    this.table = TableSuperClass({
         // various column mappings for a different type of table, switched
         // based on some logic inside this class
         columns : someTest ? [...] : [...],
         // implements ordering a different way
         aDesc : [...] // aDesc is a legacy API call that lets you specify the col to order on             
    });     

}

Il problema è che ora ho un altro tipo di tabella. È utilizzato in una vista di stampa. Ha gli stessi mapping di colonne di ObjectOwningTable, ha bisogno del superfluo ripopolamento e disabilita la ricerca da TableSuperClass, ma non ha l'impaginazione, poiché una stampa deve mostrare l'intero set di dati.

Posso pensare a due soluzioni di base:

  1. Risolvi il problema con l'ereditarietà . Make TableSuperClass fornisce la comoda interfaccia ripopolata e la ricerca disabilitata, quindi una sottoclasse con paginazione, quindi chiamata in ObjectOwningTable e AnotherObjectOwningTable. Per la mia tabella di stampa, chiamo TableSuperClass con gli stessi mapping di colonne utilizzati in ObjectOwningTable (forse li estro a una costante globale o qualcosa del genere).
  2. Risolvi il problema con la composizione . Ho un singolo DynamicTable che fornisce solo l'interfaccia ripopolata, quindi passa un elenco di oggetti semplici che vengono applicati alle opzioni in ordine. Questi semplici oggetti vivono tutti su un enum da qualche parte, quindi posso vedere come potrebbero scontrarsi mentre li scrivo e mi permettono di combinare le funzionalità più avanti sulla linea.

Quindi, a livello di consumatore, la prima soluzione sembrerebbe essere

function ObjectOwningTable() {
    this.table = PagedTable(FOO_REPORT_COLUMN_MAPPINGS);
}

function AnotherObjectOwningTable() {
    this.table = PagedTable(someTest? BAR_COLUMN_MAPPINGS : BAZ_COLUMN_MAPPINGS);
}

function PrintObjectOwningTable() {
    this.table = TableSuperClass(FOO_REPORT_COLUMN_MAPPINGS);
}

Mentre il secondo apparirebbe come

function ObjectOwningTable() {
    this.table = DynamicTable([
        DATA_TABLES_OPTIONS.pagingFixedAtFiftyRows,
        DATA_TABLES_OPTIONS.disableSearching,
        DATA_TABLES_OPTIONS.fooColumnsAndHighlighting
    ]);
}

function AnotherObjectOwningTable() {
    var columnMappings = someTest ? DATA_TABLES_OPTIONS.barColumnMappings : DATA_TABLES_OPTIONS.bazColumnMappings;
    this.table = DynamicTable([
       DATA_TABLES_OPTIONS.pagingFixedAtFiftyRows,
       DATA_TABLES_OPTIONS.disableSearching,
       columnMappings
    ]);
}

function PrintObjectOwningTable() {
    this.table = DynamicTable([
       DATA_TABLES_OPTIONS.disableSearching,
       DATA_TABLES_OPTIONS.fooColumnsAndHighlighting,
       DATA_TABLES_OPTIONS.disableSortingControls
    ]);
}

Non sono sicuro di cosa scegliere. L'approccio ereditario si sente molto problematico. Sembra molto inflessibile, e posso vedere molti modi per gli sviluppatori futuri di "intrappolare" la funzionalità in particolari tassonomie di classe. Penso anche che le catene di eredità lunghe siano più difficili da ragionare rispetto alle strutture piatte. Ma l'approccio compositivo è un po 'più prolisso, e penso che i miei colleghi potrebbero obiettare alla ripetizione. Eppure penso che il mixaggio e l'abbinamento siano davvero utili.

Esiste una terza opzione, che consiste nel creare una sottoclasse di DynamicTable che associa le opzioni pagingFixedAtFiftyRows e disableSearching e viene quindi chiamata dagli oggetti che la utilizzano, passando nelle loro peculiari mappature di colonne. Ma mi chiedo se questo rende le cose più complicate, usando sia sottoclassi che mixin. Mi chiedo anche se crei la tentazione per altri sviluppatori di aggiungere funzionalità alla sottoclasse quando dovrebbero davvero provare e creare mixin.

Che cosa consiglieresti?

    
posta Jimmy Breck-McKye 07.11.2014 - 22:58
fonte

1 risposta

1

Considerate solo le due possibilità che presenti, penso che la prima soluzione di ereditarietà sia migliore, anche se generalmente non penso che le soluzioni di ereditarietà siano migliori.

Se capisco il codice correttamente, la seconda soluzione riguarda solo un tipo DynamicTable che ha un gran numero di booleani in esso per personalizzare il suo comportamento. Ciò significa che ogni volta che qualcuno ha bisogno di una tabella con un comportamento diverso, dovrà andare in DynamicTable per aggiungere un altro booleano e condizionare in modo condizionale il comportamento delle tabelle in base al nuovo booleano.

Meglio, penso, sarebbe quello di seguire il Principio Aperto / Chiuso. DynamicTable dovrebbe impiegare un uso giudizioso del modello di strategia in modo tale che i vari altri tipi possano personalizzare il proprio comportamento inserendo blocchi di codice al suo interno, piuttosto che semplicemente impostando i booleani.

Ad esempio, hai la nozione di pagingFixedAtFiftyRows . Presumibilmente, si ha anche la nozione di non di paging fissata a cinquanta righe, e ciò apre anche la porta all'idea che il paging sia fisso, ma a qualcosa di diverso da una cinquantina di righe. Esprimere la nozione di avere pagingFixedAtFIftyRows come booleano è un po 'sciocco a questo riguardo. Al minimo, dovrebbe essere un numero intero (dove 0 significherebbe che il paging non è stato risolto).

Un altro esempio, hai la nozione di disableSearching . Presumibilmente, nel DynamicTable hai almeno un'istruzione if che controlla questo bool e fa qualcosa di diverso a seconda che sia vero o meno. Sarebbe meglio avere un modulo di ricerca separato che contenga il codice condizionale. Se un utente vuole effettuare una ricerca, inserisce il modulo di ricerca nella tabella. Questo apre anche la porta per fare altre cose interessanti per filtrare l'output della tabella che non è esattamente come la ricerca.

Buona fortuna con qualsiasi soluzione tu decida, ma ti suggerisco di esaminare il modello di strategia, probabilmente otterrai più idee link

    
risposta data 08.11.2014 - 15:43
fonte

Leggi altre domande sui tag