Domare le classi delle "funzioni di utilità"


15

Nella nostra base di codice Java continuo a vedere il seguente modello:

/**
 This is a stateless utility class
 that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
  public int foo(...) {...}
  public void bar(...) {...}
}

/**
 This class does applied foo-related things.
*/
class FooSomething {
  int DoBusinessWithFoo(FooUtil fooUtil, ...) {
    if (fooUtil.foo(...)) fooUtil.bar(...);
  }
}

Ciò che mi preoccupa è che devo passare un'istanza di FooUtilovunque, a causa dei test.

  • Non posso rendere FooUtilstatici i metodi, perché non sarò in grado di deriderli per i test di entrambe FooUtile delle sue classi client.
  • Non riesco a creare un'istanza FooUtilnel luogo di consumo con a new, di nuovo perché non sarò in grado di deriderlo per i test.

Suppongo che la mia scommessa migliore sia usare l'iniezione (e lo faccio), ma aggiunge il suo set di problemi. Inoltre, il passaggio di più istanze util aumenta la dimensione degli elenchi di parametri del metodo.

C'è un modo per gestirlo meglio che non vedo?

Aggiornamento: poiché le classi di utilità sono senza stato, probabilmente potrei aggiungere un INSTANCEmembro singleton statico o un getInstance()metodo statico , pur mantenendo la possibilità di aggiornare il campo statico sottostante della classe per il test. Non sembra troppo pulito.


4
E la tua supposizione che tutte le dipendenze debbano essere prese in giro per fare test unitari in modo efficace non è corretta (anche se sto ancora cercando la domanda che ne discute).
Telastyn,

4
Perché questi metodi non sono membri della classe di cui manipolano i dati?
Jules,

3
Avere un set separato di Junit che chiama i dati statici se FooUtility. Quindi puoi usarlo senza deridere. Se è qualcosa che devi deridere, allora sospetto che non sia solo un'utilità ma che faccia un po 'di I / O o una logica aziendale e dovrebbe in effetti essere un riferimento di un membro della classe e inizializzato tramite il metodo di costruzione, init o setter.
tgkprog,

2
+1 per il commento di Jules. Le classi utili sono un odore di codice. A seconda della semantica dovrebbe esserci una soluzione migliore. Forse i FooUtilmetodi dovrebbero essere messi in FooSomething, forse ci sono proprietà FooSomethingche dovrebbero essere modificate / estese in modo che i FooUtilmetodi non siano più necessari. Vi preghiamo di darci un esempio più concreto di cosa FooSomethingci può aiutare a fornire una buona risposta a queste domande.
valenterry,

13
@valenterry: l'unica ragione per cui le classi util sono un odore di codice è perché Java non fornisce un buon modo per avere funzioni autonome. Ci sono ottime ragioni per avere funzioni che non sono specifiche delle classi.
Robert Harvey,

Risposte:


16

Prima di tutto, lasciatemi dire che esistono diversi approcci di programmazione per problemi diversi. Per il mio lavoro preferisco un forte design OO per l'orginazazione e la manutenzione e la mia risposta riflette questo. Un approccio funzionale avrebbe una risposta molto diversa.

Tendo a scoprire che la maggior parte delle classi di utilità sono create perché l'oggetto su cui dovrebbero essere i metodi non esiste. Questo deriva quasi sempre da uno dei due casi, o qualcuno sta passando in giro collezioni o qualcuno sta passando fagioli (questi sono spesso chiamati POJO ma credo che un gruppo di setter e getter sia meglio descritto come un fagiolo).

Quando hai una raccolta che stai passando, ci deve essere un codice che vuole far parte di quella raccolta, e questo finisce per essere spruzzato in tutto il sistema e alla fine raccolto in classi di utilità.

Il mio suggerimento è di racchiudere le vostre raccolte (o bean) con qualsiasi altro dato strettamente associato in nuove classi e inserire il codice "utility" in oggetti reali.

Potresti estendere la raccolta e aggiungere lì i metodi ma perdi il controllo dello stato del tuo oggetto in quel modo - ti suggerisco di incapsularlo completamente ed esporre solo i metodi di logica aziendale necessari (Pensa "Non chiedere ai tuoi oggetti i loro dati, dai i tuoi oggetti comandi e lasciali agire sui loro dati privati ​​", un importante inquilino OO e uno che, quando ci pensi, richiede l'approccio che sto suggerendo qui).

Questo "wrapping" è utile per tutti gli oggetti della libreria per scopi generici che contengono dati ma ai quali non è possibile aggiungere codice: inserire il codice con i dati che manipola è un altro concetto importante nella progettazione di OO.

Ci sono molti stili di codifica in cui questo concetto di wrapping sarebbe una cattiva idea - chi vuole scrivere un'intera classe quando implementa solo uno script o un test una tantum? Ma penso che l'incapsulamento di tutti i tipi / raccolte di base che non è possibile aggiungere codice a te stesso sia obbligatorio per OO.


Questa risposta è fantastica. Ho avuto questo problema (ovviamente), ho letto questa risposta con un po 'di dubbio, sono tornato indietro e ho guardato il mio codice e ho chiesto "quale oggetto mancasse che dovrebbe avere questi metodi". Ecco, è stata trovata una soluzione elegante e riempito il divario tra oggetti e modello.
GreenAsJade,


@Deduplicator In 40 anni di programmazione ho visto raramente (mai?) Il caso in cui sono stato bloccato da troppi livelli di indiretta (a parte la lotta occasionale con il mio IDE per passare a un'implementazione piuttosto che a un'interfaccia) ma io ' ho visto spesso (molto spesso) il caso in cui le persone scrivevano un codice orribile piuttosto che creare una classe chiaramente necessaria. Anche se credo che troppa indiretta possa aver morso alcune persone, commetterei un errore a parte i principi OO corretti, come ogni classe che fa bene una cosa, contenimento adeguato, ecc.
Bill K

@BillK In generale, una buona idea. Ma senza una via di fuga, l'incapsulamento può davvero ostacolare. E c'è una certa bellezza negli algoritmi gratuiti che puoi abbinare a qualsiasi tipo tu voglia, anche se è vero che nei linguaggi OO rigorosi qualsiasi cosa tranne OO è piuttosto imbarazzante.
Deduplicatore

@Deduplicator Quando si scrivono funzioni 'utility' che devono essere scritte al di fuori di specifici requisiti aziendali, si è corretti. Le classi di utilità piene di funzioni (come le raccolte) sono semplicemente scomode in OO perché non sono associate ai dati. questi sono i "Escape Hatch" che le persone che non si sentono a proprio agio con OO usano (anche i produttori di strumenti devono usarli per funzionalità molto generiche). Il mio suggerimento sopra era di avvolgere questi hack in classe quando si lavora sulla logica aziendale in modo da poter associare nuovamente il codice ai dati.
Bill K,

1

Si potrebbe utilizzare un modello di provider e iniettare il vostro FooSomethingcon un FooUtilProvideroggetto (potrebbe essere in un costruttore; solo una volta).

FooUtilProvideravrebbe un solo metodo: FooUtils get();. La classe userebbe quindi qualunque FooUtilsistanza fornisca.

Ora è a un passo dall'utilizzo di un framework di Dependency Injection, quando dovresti cablare DI cointainer solo due volte: per il codice di produzione e per la tua suite di test. Basta collegare l' FooUtilsinterfaccia a uno RealFooUtilso MockedFooUtils, e il resto avviene automagicamente. Ogni oggetto da cui dipende FooUtilsProviderottiene la versione corretta di FooUtils.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.