Le classi di utilità sono cattive? [chiuso]


96

Ho visto questo thread

Se una classe "Utilità" è malvagia, dove metto il mio codice generico?

e ho pensato perché le classi di utilità sono cattive?

Diciamo che ho un modello di dominio che è profondo dozzine di classi. Devo essere in grado di eseguire istanze xml-ify. Faccio un metodo toXml sul genitore? Creo una classe di supporto MyDomainXmlUtility.toXml? Questo è un caso in cui l'esigenza aziendale copre l'intero modello di dominio: appartiene davvero a un metodo di istanza? E se ci sono un sacco di metodi ausiliari sulla funzionalità xml dell'applicazione?


27
La svalutazione del termine "male" è male!
Matthew Lock

1
@matthew ho conservato i termini del post su cui si basa la mia domanda ...;)
hvgotcodes

Le classi di utilità sono una cattiva idea per gli stessi motivi per cui lo sono i singleton.
CurtainDog

1
Il dibattito sull'opportunità di avere un metodo toXML è incentrato sui modelli di dominio ricchi e anemici. codeflow.blogspot.com/2007/05/anemic-vs-rich-domain-models.html
James P.

@james, toXML è solo un esempio ... che dire di alcune funzionalità regex che vengono utilizzate ovunque? Ad esempio, devi fare alcune cose con le stringhe nel tuo modello di dominio, ma non puoi usare la sottoclasse a causa di un'altra preoccupazione prevalente che utilizzava la tua unica superclasse (in java)
hvgotcodes

Risposte:


128

Le classi di utilità non sono esattamente malvagie, ma possono violare i principi che compongono un buon design orientato agli oggetti. In una buona progettazione orientata agli oggetti, la maggior parte delle classi dovrebbe rappresentare una singola cosa e tutti i suoi attributi e operazioni. Se stai operando su una cosa, quel metodo dovrebbe probabilmente essere un membro di quella cosa.

Tuttavia, ci sono momenti in cui è possibile utilizzare classi di utilità per raggruppare un numero di metodi insieme - un esempio è la java.util.Collectionsclasse che fornisce un numero di utilità che possono essere utilizzati su qualsiasi raccolta Java. Questi non sono specifici per un particolare tipo di raccolta, ma implementano invece algoritmi che possono essere utilizzati su qualsiasi raccolta.

In realtà, quello che devi fare è pensare al tuo progetto e determinare dove ha più senso mettere i metodi. Di solito, è come operazioni all'interno di una classe. Tuttavia, a volte, è davvero come una classe di utilità. Quando si utilizza una classe di utilità, tuttavia, non limitarsi a lanciare metodi casuali, ma organizzare i metodi per scopo e funzionalità.


si collega bene con questo articolo sul controllo della classe di utilità / aiuto rispetto al principio SOLID oo e readability.com/articles/rk1vvcqy
melaos

8
Se il linguaggio non offre alcun meccanismo di spazio dei nomi diverso dalle classi, non hai altra scelta che abusare di una classe dove farebbe uno spazio dei nomi. In C ++, puoi inserire una funzione indipendente, non correlata ad altre funzioni, in uno spazio dei nomi. In Java, è necessario inserirlo in una classe come membro statico (classe).
Unslander Monica

1
Il raggruppamento come metodi può essere canonico da un punto di vista OOP. Tuttavia, l'OOP è raramente la soluzione (tra le eccezioni ci sono l'architettura di alto livello e i toolkit dei widget come uno dei casi in cui la semantica incredibilmente rotta del riutilizzo del codice per ereditarietà si adatta effettivamente) e generalmente i metodi aumentano l'accoppiamento e le dipendenze. Esempio banale: se fornisci metodi di stampante / scanner alla tua classe come metodi, accoppi la classe alle librerie di stampanti / scanner. Inoltre, il numero di implementazioni possibili viene fissato a uno effettivamente. A meno che non si inserisca l'interfaccia e si aumentino ulteriormente le dipendenze.
Jo So

D'altra parte, sono d'accordo con il tuo sentimento "raggruppa per scopo e funzionalità". Rivisitiamo di nuovo l'esempio di stampante / scanner e iniziamo con una serie di classi concertate, progettate per uno scopo comune. Potresti scrivere del codice di debug e progettare una rappresentazione testuale per le tue classi. Puoi implementare le tue stampanti di debug in un singolo file che dipende da tutte quelle classi e da una libreria di stampanti. Il codice non di debug non sarà gravato dalle dipendenze di questa implementazione.
Jo So

1
Le classi Util e Helper sono uno sfortunato artefatto di tali vincoli e coloro che non capiscono l'OOP o non comprendono il dominio del problema hanno continuato a scrivere codice procedurale.
bilelovitch

57

Penso che il consenso generale sia che le classi di utilità non siano il male di per sé . Devi solo usarli con giudizio:

  • Progettare i metodi di utilità statica in modo che siano generali e riutilizzabili. Assicurati che siano apolidi; cioè nessuna variabile statica.

  • Se disponi di molti metodi di utilità, suddividili in classi in modo da renderli facilmente reperibili per gli sviluppatori.

  • Non utilizzare classi di utilità in cui metodi statici o di istanza in una classe di dominio rappresenterebbero una soluzione migliore. Si consideri, ad esempio, se i metodi in una classe base astratta o in una classe helper istanziabile siano una soluzione migliore.

  • Da Java 8 in poi, i "metodi predefiniti" in un'interfaccia potrebbero essere un'opzione migliore rispetto alle classi di utilità. (Vedere Interfaccia con metodi predefiniti vs Classe astratta in Java 8 per esempio.)


L'altro modo di guardare a questa Domanda è osservare che nella Domanda citata, "Se le classi di utilità sono" cattive "" è un argomento di paglia. È come se chiedessi:

"Se i maiali possono volare, devo portare un ombrello?".

Nella domanda precedente non sto effettivamente dicendo che i maiali possono volare ... o che sono d'accordo con l'affermazione che potrebbero volare.

Le tipiche affermazioni "xyz è male" sono dispositivi retorici che hanno lo scopo di farti pensare ponendo un punto di vista estremo. Sono raramente (se non mai) intesi come dichiarazioni di fatti letterali.


16

Le classi di utilità sono problematiche perché non riescono a raggruppare le responsabilità con i dati che le supportano.

Sono comunque estremamente utili e li costruisco continuamente come strutture permanenti o come trampolini di lancio durante un refactoring più approfondito.

Dal punto di vista del codice pulito , le classi di utilità violano la responsabilità unica e il principio aperto-chiuso. Hanno molti motivi per cambiare e non sono estensibili in base alla progettazione. Dovrebbero esistere solo durante il refactoring come cruft intermedio.


2
Lo definirei un problema pratico poiché ad esempio in Java non è possibile creare funzioni che non siano metodi in una classe. In un tale linguaggio la "classe senza variabili e con solo metodi statici" è un idioma che sta per uno spazio dei nomi di funzioni di utilità ... Di fronte a una tale classe in Java, IMHO non è valido e inutile parlare di una classe , anche se classviene utilizzata la parola chiave. Ciarlatano come uno spazio dei nomi, cammina come uno spazio dei nomi - è uno ...
Unslander Monica

2
Mi dispiace essere schietto, ma "metodi statici senza stato [...] stravaganti in un problema filosofico [...] del sistema OOP" è estremamente fuorviante. È FANTASTICO se puoi evitare lo stato perché ciò semplifica la scrittura del codice corretto. È ROTTO quando devo scrivere x.equals(y)perché 1) il fatto che questo in realtà non dovrebbe modificare alcuno stato non è trasmesso (e non posso fare affidamento su di esso non conoscendo l'implementazione) 2) Non ho mai avuto intenzione di inserire xun " posizione favorita "o" su cui si è agito "rispetto a y3) Non voglio considerare le" identità "di xo yma sono semplicemente interessato ai loro valori.
Jo So

4
La maggior parte delle funzionalità nel mondo reale viene espressa al meglio come funzioni matematiche statiche, senza stato. Math.PI è un oggetto che dovrebbe essere mutato? Devo davvero istanziare un AbstractSineCalculator che implementa IAbstractRealOperation solo per ottenere il seno di un numero?
Jo So

1
@JoSo sono completamente d'accordo sul valore dell'apolidia e del calcolo diretto. Naive OO è filosoficamente opposto a questo e penso che lo renda profondamente imperfetto. Incoraggia l'astrazione di cose che non ne hanno bisogno (come hai dimostrato) e non riesce a fornire astrazioni significative per il calcolo effettivo. Tuttavia, un approccio equilibrato all'uso di un linguaggio OO tende all'immutabilità e all'apolidia in quanto promuovono la modularità e la manutenibilità.
Alain O'Dea

2
@ AlainO'Dea: mi rendo conto di aver letto male il tuo commento in quanto contrario alla funzionalità apolidi. Mi scuso per il mio tono: sono attualmente coinvolto nel mio primo progetto Java (dopo aver evitato l'OOP per la maggior parte dei miei 10 anni di programmazione) e sono sepolto sotto strati di cose astratte con uno stato e significati poco chiari. E ho solo bisogno di un po 'd'aria fresca :-).
Jo So

8

Suppongo che inizi a diventare cattivo quando

1) Diventa troppo grande (in questo caso raggruppali in categorie significative).
2) Sono presenti metodi che non dovrebbero essere metodi statici

Ma finché queste condizioni non sono soddisfatte, penso che siano molto utili.


"Sono presenti metodi che non dovrebbero essere metodi statici." Com'è possibile?
Koray Tugay

@KorayTugay Considera il non statico Widget.compareTo(Widget widget)rispetto a quello statico WidgetUtils.compare(Widget widgetOne, Widget widgetTwo). Il confronto è un esempio di qualcosa che non dovrebbe essere fatto staticamente.
ndm13

5

Regola del pollice

Puoi guardare questo problema da due prospettive:

  • I *Utilmetodi generali sono spesso un suggerimento di cattiva progettazione del codice o convenzione di denominazione pigra.
  • È una soluzione di progettazione legittima per funzionalità senza stato interdominio riutilizzabili. Si noti che per quasi tutti i problemi comuni esistono soluzioni esistenti.

Esempio 1. Utilizzo corretto di utilclassi / moduli. Esempio di libreria esterna

Supponiamo che tu stia scrivendo un'applicazione che gestisce prestiti e carte di credito. I dati di questi moduli sono esposti tramite servizi web in jsonformato. In teoria puoi convertire manualmente gli oggetti in stringhe che saranno in json, ma questo reinventerebbe la ruota. La soluzione corretta sarebbe includere in entrambi i moduli la libreria esterna utilizzata per convertire gli oggetti Java nel formato desiderato. (nell'immagine di esempio ho mostrato gson )

inserisci qui la descrizione dell'immagine


Esempio 2. Utilizzo corretto di utilclassi / moduli. Scrivendo il tuo utilsenza scuse agli altri membri del team

Come caso d'uso, supponi di dover eseguire alcuni calcoli in due moduli di applicazione, ma entrambi devono sapere quando ci sono giorni festivi in ​​Polonia. In teoria è possibile eseguire questi calcoli all'interno dei moduli, ma sarebbe meglio estrarre questa funzionalità per separare il modulo.

Ecco un piccolo, ma importante dettaglio. La classe / modulo che hai scritto non si chiama HolidayUtil, ma PolishHolidayCalculator. Funzionalmente è una utilclasse, ma siamo riusciti a evitare la parola generica.

inserisci qui la descrizione dell'immagine


1
Vedo un fan di un anno;) App fantastica.
Sridhar Sarnobat

3

Le classi di utilità sono pessime perché significano che eri troppo pigro per pensare a un nome migliore per la classe :)

Detto questo, sono pigro. A volte hai solo bisogno di portare a termine il lavoro e la tua mente è vuota ... è allora che le classi di "Utilità" iniziano a insinuarsi.


3

Guardando indietro a questa domanda ora, direi che i metodi di estensione C # distruggono completamente la necessità di classi di utilità. Ma non tutte le lingue hanno un tale costrutto geniale.

Hai anche JavaScript, dove puoi semplicemente aggiungere una nuova funzione direttamente all'oggetto esistente.

Ma non sono sicuro che esista davvero un modo elegante per risolvere questo problema in un linguaggio vecchio come il C ++ ...

Un buon codice OO è piuttosto difficile da scrivere, ed è difficile da trovare poiché scrivere un buon OO richiede molto più tempo / conoscenza rispetto alla scrittura di codice funzionale decente.

E quando hai un budget limitato, il tuo capo non è sempre felice di vedere che hai passato l'intera giornata a scrivere un sacco di lezioni ...


3

Non sono del tutto d'accordo sul fatto che le classi di utilità siano cattive.

Sebbene una classe di utilità possa violare in qualche modo i principali OO, non sono sempre cattivi.

Ad esempio, immagina di volere una funzione che ripulirà una stringa da tutte le sottostringhe corrispondenti al valore x.

stl c ++ (al momento) non lo supporta direttamente.

Potresti creare un'estensione polimorfica di std::string.

Ma il problema è, vuoi davvero che OGNI stringa che usi nel tuo progetto sia la tua classe di stringhe estesa?

Ci sono momenti in cui OO non ha davvero senso, e questo è uno di questi. Vogliamo che il nostro programma sia compatibile con altri programmi, quindi ci atterremo std::stringe creeremo una classe StringUtil_(o qualcosa del genere).

Direi che è meglio se ti attieni a un'utilità per classe. Direi che è sciocco avere un'utilità per tutte le classi o molte utilità per una classe.


2

È molto facile etichettare qualcosa come un'utilità semplicemente perché il progettista non è riuscito a pensare a un posto appropriato per inserire il codice. Ci sono spesso poche vere "utilità".

Come regola pratica, di solito tengo il codice nel pacchetto in cui viene utilizzato per la prima volta, e poi eseguo il refactoring in un luogo più generico se scopro che in seguito è davvero necessario altrove. L'unica eccezione è se ho già un pacchetto che esegue funzionalità simili / correlate e il codice si adatta meglio a lì.


2

Le classi di utilità contenenti metodi statici senza stato possono essere utili. Questi sono spesso molto facili da testare.



1

La maggior parte delle classi Util sono cattive perché:

  1. Ampliano la portata dei metodi. Rendono pubblico il codice che altrimenti sarebbe privato. Se il metodo util è necessario a più chiamanti in classi separate ed è stabile (cioè non necessita di aggiornamento) è meglio, a mio parere, copiare e incollare metodi di supporto privato nella classe chiamante. Una volta che lo esponi come API, rendi più difficile capire qual è il punto di ingresso pubblico a un componente jar (mantieni un albero strutturato chiamato gerarchia con un genitore per metodo. Questo è più facile da separare mentalmente in componenti che è più difficile quando hai metodi chiamati da più metodi genitore).
  2. Si traducono in codice morto. I metodi utilizzati nel tempo diventano inutilizzati man mano che la tua app si evolve e finisci con il codice inutilizzato che inquina la tua base di codice. Se fosse rimasto privato, il tuo compilatore ti direbbe che il metodo non è utilizzato e potresti semplicemente rimuoverlo (il miglior codice non è affatto codice). Una volta che si rende tale metodo non privato, il computer non sarà più in grado di aiutarti a rimuovere il codice inutilizzato. Può essere chiamato da un file jar diverso per tutti i computer a conoscenza.

Esistono alcune analogie con le librerie statiche e dinamiche.


0

Quando non riesco ad aggiungere un metodo a una classe (ad esempio, Accountè bloccato contro le modifiche di Jr. Developers), aggiungo solo alcuni metodi statici alla mia classe Utilities in questo modo:

public static int method01_Account(Object o, String... args) {
    Account acc = (Account)o;
    ...
    return acc.getInt();
}  

1
A me sembra che tu sia il Jr ..: P Il modo non malvagio per farlo è chiedere educatamente al tuo "Jr Developer" di sbloccare il Accountfile. O meglio, vai con sistemi di controllo del codice sorgente non bloccanti.
Sudeep

1
Presumo che volesse dire che il Accountfile è stato bloccato in modo tale che gli sviluppatori Jr. non possano apportarvi modifiche. In qualità di Jr. Developer, per aggirare il problema, ha fatto come sopra. Detto questo, è ancora meglio ottenere l'accesso in scrittura al file e farlo correttamente.
Doc

Aggiungendo +1 a questo perché i voti negativi sembrano duri quando nessuno ha il contesto completo del motivo per cui hai fornito questa risposta
joelc

0

Le classi di utilità non sono sempre cattive. Ma dovrebbero contenere solo i metodi comuni a un'ampia gamma di funzionalità. Se ci sono metodi utilizzabili solo tra un numero limitato di classi, prendi in considerazione la creazione di una classe astratta come genitore comune e inseriscici i metodi.

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.