Domanda

Una cosa che ho incontrato alcune volte è una classe di servizio (come un servizio JBoss) che è diventata eccessivamente grande a causa delle classi interne di supporto. Devo ancora trovare un buon modo per superare la lezione. Questi helper sono generalmente thread. Ecco un esempio:

/** Asset service keeps track of the metadata about assets that live on other
 * systems. Complications include the fact the assets have a lifecycle and their
 * physical representation lives on other systems that have to be polled to find
 * out if the Asset is still there. */
public class AssetService
{
  //...various private variables
  //...various methods

  public AssetService()
  {
    Job pollerJob = jobService.schedule( new AssetPoller() );
    Job lifeCycleJob = jobService.schedule( AssetLifecycleMonitor() );
  }

  class AssetPoller
  {
    public void run()
    { 
      // contact remote systems and update this service's private variables that
      // track the assets.
    }
  }

  class AssetLifecycleMonitor
  {
    public void run()
    {
      // look for assets that have meet criteria for a lifecycle shift
      // and update this service's private variables as relevant.
    }
  }
}

Quindi, cosa può succedere se ho un paio di aiutanti e sono del tutto complessi, è che il file complessivo della classe può diventare davvero grande. Mi piacciono le classi interne in quanto chiarisce che le classi sono interamente di proprietà del servizio ed esistono solo per aiutare quel servizio. Ho provato a suddividere le classi e passare il servizio genitore come riferimento, che funziona principalmente, ma le cose che non mi piacciono sono:  

  • Finisco per esporre gli accessor a livello di pacchetto in modo che le classi scomposte possano arrivare alle variabili, mentre prima non esponevo affatto i setter poiché le classi interne avevano accesso diretto.  
  • Inoltre, le cose diventano un po 'più prolisse poiché chiamo costantemente accessor piuttosto che le variabili sottostanti. Un pò minore, scontato.  
  • I metodi di convenienza (ad esempio checkAssetIsValid () o alcuni di questi) ora richiedono un'esposizione a livello di pacchetto in modo che le classi helper possano chiamarli, dove prima come classi interne potevano essere private.  
  • Ancora peggio, ho bisogno di passare la classe di implementazione del servizio ai costruttori di classi di helper poiché non voglio esporre questi metodi di helper nell'interfaccia implementata dal servizio perché ciò li costringe a essere pubblici. Questo può creare alcuni problemi di test unit / beffardo.  
  • Ancora peggio, qualsiasi sincronizzazione che volevo fare viene trapelata attraverso un metodo di convenienza esterno (ad esempio lockDownAssets () durante un aggiornamento del poller). Prima, le classi interne avevano accesso a Lock privati.

    Quindi, in breve, interrompere le classi perde parte dell'incapsulamento che mi piace. Ma lasciarli dentro può portare ad alcuni file Java di grandi dimensioni. Devo ancora trovare un buon modo per affrontarlo. Il C ++ aveva il concetto di "amici" che raramente mi mancava, ma in questo caso sarebbe di grande aiuto.

    Pensieri?

        
  • È stato utile?

    Soluzione

    Le classi interne a livello di bytecode sono semplicemente classi Java. Poiché il verificatore del bytecode Java non consente l'accesso ai membri privati, genera metodi di accesso sintetici per ogni campo privato che si utilizza. Inoltre, al fine di collegare la classe interna con la sua istanza che la racchiude, il compilatore aggiunge un puntatore sintetico al 'questo' esterno.

    Considerando questo, le classi interne sono solo uno strato di zucchero di sintassi. Sono convenienti e hai elencato alcuni punti positivi, quindi elencherei alcuni aspetti negativi che potresti prendere in considerazione:

    • La tua classe interna ha una dipendenza nascosta rispetto alla classe genitore intera , che offusca la sua interfaccia in entrata. Se lo estrai come pacchetto privato, hai la possibilità di migliorare il tuo design e renderlo più gestibile. Inizialmente è più dettagliato, ma spesso potresti trovare che:
      • Invece di esporre 10 accessori in realtà vuoi condividere un oggetto a valore singolo. Spesso scopriresti che non hai davvero bisogno di un riferimento a tutta la classe esterna. Funziona bene anche con IoC.
      • Invece di fornire metodi per il blocco esplicito, è più gestibile incapsulare l'operazione con il suo contesto in una classe separata (o spostarla in una delle due classi - esterna o precedentemente interna).
      • I metodi di convenienza appartengono alle classi di utilità private del pacchetto. È possibile utilizzare l'importazione statica Java5 per farli apparire come locali.
    • La tua classe esterna può bypassare qualsiasi livello di protezione e accedere direttamente ai membri privati ??della tua classe interna. Questa non è una cosa negativa di per sé, ma toglie uno dei mezzi linguistici per esprimere il tuo design.
    • Poiché la tua classe interna è incorporata esattamente in una classe esterna, l'unico modo per riutilizzarla è la sottoclasse della classe esterna. Un'alternativa sarebbe passare un riferimento esplicito a un'interfaccia pacchetto-privata implementata dalla classe esterna. Ciò ti permetterebbe di deridere l'esterno e testare meglio la classe interna.
    • Anche se i debugger recenti sono abbastanza buoni, ho già avuto problemi con il debug delle classi interne (confusione dell'ambito del punto di interruzione condizionale, non arresto ai punti di interruzione, ecc.)
    • Le classi private gonfiano il tuo bytecode. Vedi il mio primo paragrafo - spesso c'è un'API che potresti usare e ridurre il numero di cruft sintetici.

    P.S. Sto parlando di classi interne non banali (specialmente quelle che non implementano alcuna interfaccia). Le implementazioni di listener su tre righe sono buone.

    Altri suggerimenti

    Non dimenticare di considerare il motivo per cui stai provando a spezzare la tua grande classe. È per scopi di ingegneria del software? Per esempio. è un hotspot di programmazione e hai un file così grande da causare fusioni complicate nel tuo team di sviluppatori?

    È solo un desiderio generale evitare le classi numerose? Nel qual caso potrebbe essere che il tuo tempo venga speso meglio per migliorare il codice che hai.

    Il codice sta diventando difficile da gestire, ad es. il debug e la garanzia di evitare effetti collaterali indesiderati sta diventando più difficile.

    Il commento di Rick sull'uso dei test unitari per garantire che il comportamento costante e costante sia molto prezioso e una buona idea. È possibile che l'attuale progetto precluda semplicemente il refactoring e che sia meglio implementare nuovamente lo stesso comportamento a partire dall'interfaccia dell'originale. Preparati a numerosi test di regressione!

    La linea tra incapsulamento e separazione può essere difficile da percorrere. Tuttavia, penso che il problema principale qui sia che hai bisogno di una sorta di modello di interazione solido da usare come base per separare le tue classi.

    Penso che sia ragionevole avere classi di utilità di supporto esterne che vengono utilizzate in molti luoghi, purché non abbiano effetti collaterali non vedo alcun problema. È anche ragionevole avere classi helper statiche, purché ben organizzate, che contengano metodi spesso usati come checkAssetIsValid (). Ciò presuppone che checkAssetIsValid non debba accedere ad alcuno stato esterno diverso dall'oggetto che lo sta passando.

    La cosa più importante per la separazione è non avere oggetti che condividono riferimenti permanenti in molte di queste classi. Mi piace guardare alla programmazione funzionale come guida. Ogni classe non dovrebbe raggiungere le viscere di altre classi e cambiare stato. Invece ogni classe che funziona dovrebbe produrre e consumare oggetti contenitore.

    Anche la visualizzazione può essere molto utile. Ho notato una discussione sull'argomento degli strumenti di visualizzazione Java qui . Idealmente, il diagramma di interazione della classe dovrebbe assomigliare più ad un albero che a un grafico.

    Inoltre, voglio solo notare che il refactoring di una grande classe in classi più piccole può essere estremamente difficile. È meglio creare almeno una serie di test unitari per l'interfaccia pubblica in modo che diventi immediatamente ovvio se si rompe qualcosa. So che i test mi hanno risparmiato innumerevoli ore in passato.

    Speriamo che parte di questo sia utile. Sono un po 'sconclusionato qui.

    Non sono un fan dell'uso eccessivo delle classi interne. Penso che non offrano davvero alcun vantaggio (se usato all'estremo) che non metterebbe il codice in una classe normale, e servono solo a rendere il file della classe inutilmente grande e difficile da seguire.

    Qual è il danno se devi aumentare la visibilità di alcuni metodi? Vi romperà completamente l'astrazione o l'interfaccia? Penso che troppo spesso i programmatori tendano a rendere tutto privato per impostazione predefinita, dove non c'è davvero nulla di male nel lasciare che altre classi chiamino i tuoi metodi - se il tuo progetto è veramente basato su OO, cioè.

    Se tutte le tue classi di helper interne " bisogno di accedere ad alcuni degli stessi metodi, considera di inserirli in una classe base in modo che possano essere condivisi tramite ereditarietà.

    Yeap. Probabilmente hai bisogno di ri-refactoring quegli helper e non spostarli tutti come sono. Alcune cose appartengono al servizio, altre all'aiutante. Probabili nuove classi dovrebbero essere utilizzate per incapsulare i dati.

    Una possibilità che puoi usare è di AOP per fornire un accesso a grana fine e includere nel taglio del punto che il metodo dovrebbe essere invocato solo dall'amico "amico". classe. Tuttavia il tuo metodo verrebbe esposto :(

    Immagino non ci sia una soluzione facile per questo.

    Autorizzato sotto: CC-BY-SA insieme a attribuzione
    Non affiliato a StackOverflow
    scroll top