Classe con metodo singolo - approccio migliore?


173

Supponiamo che io abbia una classe che intende svolgere una singola funzione. Dopo aver eseguito la funzione, può essere distrutta. C'è qualche motivo per preferire uno di questi approcci?

// Initialize arguments in constructor
MyClass myObject = new MyClass(arg1, arg2, arg3);
myObject.myMethod();

// Pass arguments to method
MyClass myObject = new MyClass();
myObject.myMethod(arg1, arg2, arg3);

// Pass arguments to static method
MyClass.myMethod(arg1, arg2, arg3);

Ero intenzionalmente vago sui dettagli, per cercare di ottenere linee guida per situazioni diverse. Ma non avevo in mente semplici funzioni di libreria come Math.random (). Sto pensando a più classi che svolgono compiti specifici e complessi, ma richiedono solo un metodo (pubblico) per farlo.

Risposte:


264

Adoravo le classi di utilità riempite con metodi statici. Fecero un grande consolidamento dei metodi di aiuto che altrimenti sarebbero dovuti causare ridondanza e inferno di manutenzione. Sono molto facili da usare, nessuna istanza, nessuno smaltimento, solo il fuoco e lo scordo. Immagino che questo sia stato il mio primo inconsapevole tentativo di creare un'architettura orientata ai servizi - molti servizi senza stato che hanno fatto il loro lavoro e nient'altro. Man mano che cresce un sistema, tuttavia, arrivano i draghi.

Polimorfismo
Supponiamo di avere il metodo UtilityClass.SomeMethod che ronza felicemente. All'improvviso dobbiamo modificare leggermente la funzionalità. La maggior parte delle funzionalità è la stessa, ma dobbiamo comunque cambiare un paio di parti. Se non fosse stato un metodo statico, avremmo potuto creare una classe derivata e modificare i contenuti del metodo secondo necessità. Essendo un metodo statico, non possiamo. Certo, se abbiamo solo bisogno di aggiungere funzionalità prima o dopo il vecchio metodo, possiamo creare una nuova classe e chiamare quella vecchia al suo interno - ma è semplicemente grossolano.

Problemi di interfaccia
I metodi statici non possono essere definiti tramite interfacce per motivi logici. E poiché non possiamo ignorare i metodi statici, le classi statiche sono inutili quando dobbiamo passarle in giro dalla loro interfaccia. Questo ci rende incapaci di usare le classi statiche come parte di un modello di strategia. Potremmo correggere alcuni problemi passando delegati anziché interfacce .

Test
Fondamentalmente questo va di pari passo con i problemi dell'interfaccia menzionati sopra. Poiché la nostra capacità di scambiare le implementazioni è molto limitata, avremo anche problemi a sostituire il codice di produzione con il codice di prova. Ancora una volta, possiamo racchiuderli ma ci richiederà di modificare grandi parti del nostro codice solo per poter accettare wrapper invece degli oggetti reali.

Favorisce i BLOB
Poiché i metodi statici vengono generalmente utilizzati come metodi di utilità e i metodi di utilità di solito hanno scopi diversi, finiremo rapidamente con una grande classe piena di funzionalità non coerente - idealmente, ogni classe dovrebbe avere un unico scopo all'interno del sistema . Preferirei avere un numero di lezioni cinque volte maggiore se i loro scopi sono ben definiti.

Creep di parametro
Per cominciare, quel piccolo metodo statico carino e innocente potrebbe prendere un singolo parametro. Man mano che la funzionalità aumenta, vengono aggiunti un paio di nuovi parametri. Presto verranno aggiunti ulteriori parametri che sono facoltativi, quindi creiamo sovraccarichi del metodo (o semplicemente aggiungiamo valori predefiniti, nelle lingue che li supportano). In poco tempo, abbiamo un metodo che accetta 10 parametri. Sono richiesti solo i primi tre, i parametri 4-7 sono opzionali. Ma se viene specificato il parametro 6, è necessario compilare anche 7-9 ... Se avessimo creato una classe con il solo scopo di fare ciò che ha fatto questo metodo statico, potremmo risolverlo prendendo i parametri richiesti nel costruttore e consentire all'utente di impostare valori opzionali tramite proprietà o metodi per impostare più valori interdipendenti contemporaneamente. Inoltre, se un metodo è diventato così complesso,

Consumatori esigenti di creare un'istanza di classi senza motivo
Uno degli argomenti più comuni è, perché chiedere ai consumatori della nostra classe di creare un'istanza per invocare questo singolo metodo, senza averne successivamente bisogno? La creazione di un'istanza di una classe è un'operazione molto economica nella maggior parte delle lingue, quindi la velocità non è un problema. L'aggiunta di una riga aggiuntiva di codice al consumatore è un costo basso per porre le basi di una soluzione molto più sostenibile in futuro. Infine, se si desidera evitare la creazione di istanze, è sufficiente creare un wrapper singleton della propria classe che consenta un facile riutilizzo, sebbene ciò richieda che la classe sia apolide. Se non è stateless, è comunque possibile creare metodi wrapper statici che gestiscono tutto, offrendo comunque tutti i vantaggi a lungo termine. Finalmente,

Solo un Sith si occupa di assoluti
Naturalmente, ci sono eccezioni alla mia antipatia per i metodi statici. Le vere classi di utilità che non comportano alcun rischio di gonfiamento sono casi eccellenti per metodi statici, come System.Convert. Se il tuo progetto è unico e non richiede requisiti per la manutenzione futura, l'architettura complessiva non è davvero molto importante - statica o non statica, non importa - la velocità di sviluppo è comunque importante.

Standard, standard, standard!
L'uso dei metodi di istanza non impedisce di utilizzare anche metodi statici e viceversa. Finché c'è un ragionamento dietro la differenziazione ed è standardizzata. Non c'è niente di peggio che guardare un livello aziendale tentacolare con diversi metodi di implementazione.


Come dice Mark, il polimorfismo, la capacità di passare i metodi come parametri di "strategia" e la possibilità di configurare / impostare le opzioni sono tutti motivi per usare un'istanza . Ad esempio: un ListDelimiter o DelimiterParser potrebbe essere configurato su quali delimitatori utilizzare / accettare, se tagliare gli spazi bianchi dai token analizzati, se raggruppare l'elenco, come trattare un elenco vuoto / null ... ecc.
Thomas W

4
"Man mano che un sistema cresce ..." rifletti?
Rodney Gitzel,

3
@ user3667089 Argomenti generali che sono necessari affinché la classe esista logicamente, quelli che vorrei passare nel costruttore. Questi sarebbero tipicamente usati nella maggior parte / tutti i metodi. Argomenti specifici del metodo Passerei in quel metodo specifico.
Mark S. Rasmussen,

1
In realtà quello che stai facendo con una classe statica sta tornando a C grezzo, non orientato agli oggetti (anche se gestito dalla memoria) - tutte le funzioni sono in uno spazio globale, non c'è this, devi gestire lo stato globale ecc. se ti piace la programmazione procedurale, fallo. Ma ti rendo conto che perderai molti dei vantaggi strutturali di OO.
Ingegnere

1
La maggior parte di questi motivi ha a che fare con una cattiva gestione del codice, non dall'uso di metodi statici. In F # e in altri linguaggi funzionali utilizziamo ovunque metodi sostanzialmente statici (funzioni senza stato di istanza) e non presentano questi problemi. Detto questo, F # fornisce un paradigma migliore per l'utilizzo delle funzioni (le funzioni sono di prima classe, le funzioni possono essere curry e parzialmente applicate), il che le rende più fattibili che in C #. Un motivo più grande per usare le classi è perché è per questo che C # è costruito. Tutti i costrutti di Dependency Injection in .NET Core ruotano attorno a classi di istanze con deps.
Justin J Stark,

89

Preferisco il modo statico. Poiché la classe non rappresenta un oggetto, non ha senso crearne un'istanza.

Le classi che esistono solo per i loro metodi devono essere lasciate statiche.


19
-1 "Le classi che esistono solo per i loro metodi devono essere lasciate statiche."
Rookian,

8
@Rookian, perché non sei d'accordo?
jjnguy,

6
un esempio potrebbe essere il modello di repository. La classe contiene solo metodi. Seguire il tuo approccio non consente di utilizzare le interfacce. => aumenta l'accoppiamento
Rookian

1
@Rook, in generale le persone non dovrebbero creare classi utilizzate solo per i metodi. Nell'esempio fornito i metodi statici non sono una buona idea. Ma per semplici metodi di utilità il modo statico è il migliore.
jjnguy,

9
Assolutamente corretto :) Con il tuo conditon "per semplici metodi di utilità" Sono completamente d'accordo con te, ma hai fatto una regola generale nella tua risposta che è sbagliata.
Rookian,

18

Se non vi è alcun motivo per creare un'istanza della classe per eseguire la funzione, utilizzare l'implementazione statica. Perché i consumatori di questa classe creano un'istanza quando non è necessaria.


16

Se non è necessario salvare lo stato dell'oggetto, non è necessario istanziarlo in primo luogo. Andrei con il singolo metodo statico a cui passi i parametri.

Avrei anche messo in guardia contro una gigantesca classe Utils che ha dozzine di metodi statici non correlati. Questo può diventare disorganizzato e ingombrante in fretta. È meglio avere molte classi, ognuna con pochi metodi correlati.


6

Direi che il formato del metodo statico sarebbe l'opzione migliore. E renderei anche la classe statica, in questo modo non dovresti preoccuparti di creare accidentalmente un'istanza della classe.


5

Non so davvero quale sia la situazione qui, ma guarderei a metterla come un metodo in una delle classi a cui appartengono arg1, arg2 o arg3 - Se puoi dire semanticamente che una di quelle classi possederebbe il metodo.


4

Suggerirei che è difficile rispondere in base alle informazioni fornite.

Il mio istinto è che se hai solo un metodo e stai per buttare via immediatamente la classe, allora rendila una classe statica che accetta tutti i parametri.

Naturalmente, è difficile dire esattamente perché è necessario creare una singola classe solo per questo metodo. È la tipica situazione di "classe di utilità" come la maggior parte presume? Oppure stai implementando una sorta di classe di regole, di cui potrebbero essercene altre in futuro.

Ad esempio, avere quella classe può essere plug-in. Quindi vorresti creare un'interfaccia per il tuo unico metodo, e poi vorresti avere tutti i parametri passati nell'interfaccia, piuttosto che nel costruttore, ma non vorrai che fosse statico.


3

La tua classe può essere resa statica?

In tal caso, la trasformerei in una classe "Utilità" in cui inserirò tutte le mie classi con una funzione.


2
Non sono d'accordo. Nei progetti in cui sono coinvolto, la tua classe Utilities potrebbe contenere centinaia di metodi non correlati.
Paul Tomblin,

3
@Paul: generalmente d'accordo. Odio vedere classi con dozzine (o sì, centinaia) di metodi totalmente indipendenti. Se uno deve adottare questo approccio, almeno suddividerli in insiemi di utilità più piccoli e correlati (EG, FooUtilities, BarUtilities, ecc.).
John Rudy,

9
una classe - una responsabilità.
Andreas Petersson,

3

Se questo metodo è stateless e non è necessario passarlo in giro, ha più senso definirlo statico. Se devi passare il metodo, potresti prendere in considerazione l'uso di un delegato piuttosto che uno degli altri approcci proposti.


3

Per semplici applicazioni e internalaiutanti, utilizzerei un metodo statico. Per le applicazioni con componenti, adoro il Managed Extensibility Framework . Ecco un estratto da un documento che sto scrivendo per descrivere gli schemi che troverai nelle mie API.

  • Servizi
    • Definito da I[ServiceName]Serviceun'interfaccia.
    • Esportato e importato dal tipo di interfaccia.
    • La singola implementazione è fornita dall'applicazione host e consumata internamente e / o dalle estensioni.
    • I metodi sull'interfaccia di servizio sono thread-safe.

A titolo esemplificativo:

public interface ISettingsService
{
    string ReadSetting(string name);

    void WriteSetting(string name, string value);
}

[Export]
public class ObjectRequiringSettings
{
    [Import]
    private ISettingsService SettingsService
    {
        get;
        set;
    }

    private void Foo()
    {
        if (SettingsService.ReadSetting("PerformFooAction") == bool.TrueString)
        {
            // whatever
        }
    }
}

2

Vorrei solo fare tutto nel costruttore. così:

new MyClass(arg1, arg2, arg3);// the constructor does everything.

o

MyClass my_object(arg1, arg2, arg3);

Cosa succede se è necessario restituire qualcosa oltre a un oggetto di tipo MyClass?
Bill the Lizard,

13
Considero una cattiva pratica mettere la logica di esecuzione in un costruttore. Un buon sviluppo riguarda la semantica. Semanticamente, esiste un costruttore per costruire un oggetto, non per svolgere altre attività.
mstrobl,

bill, se hai bisogno di un valore di ritorno, passa il riferimento al valore ret: MyClass myObject (arg1, arg2, arg3, retvalue); mstrobl, se l'oggetto non è necessario, perché crearlo? e questo trucco può davvero aiutarti in alcuni casi.

Se un oggetto non ha uno stato, vale a dire nessun var, allora l'installazione non produrrà alcuna allocazione, né su heap né su stack. Puoi pensare agli oggetti in C ++ come a semplici chiamate di funzione C con un primo parametro nascosto che punta a una struttura.
mstrobl,

mstrobl, come la funzione ac sugli steroidi perché puoi definire funzioni private che il ctor può usare. puoi anche usare le variabili dei membri della classe per aiutare a passare i dati alle funzioni private.

0

Un altro problema importante da considerare è se il sistema sarebbe in esecuzione in un ambiente multithread e se sarebbe sicuro per i thread avere un metodo statico o variabili ...

È necessario prestare attenzione allo stato del sistema.


0

Potresti essere in grado di evitare la situazione tutti insieme. Prova a refactoring in modo da ottenere arg1.myMethod1(arg2, arg3). Scambia arg1 con arg2 o arg3 se ha più senso.

Se non hai il controllo sulla classe di arg1, decorala:

class Arg1Decorator
    private final T1 arg1;
    public Arg1Decorator(T1 arg1) {
        this.arg1 = arg1;
    }
    public T myMethod(T2 arg2, T3 arg3) {
        ...
    }
 }

 arg1d = new Arg1Decorator(arg1)
 arg1d.myMethod(arg2, arg3)

Il ragionamento è che, in OOP, i dati e i metodi che elaborano tali dati appartengono insieme. Inoltre ottieni tutti i vantaggi citati da Mark.


0

penso che se le proprietà della tua classe o l'istanza della classe non saranno usate nei costruttori o nei tuoi metodi, i metodi non sono suggeriti per essere progettati come pattern 'statici'. il metodo statico dovrebbe essere sempre pensato in modo "aiuto".


0

A seconda che tu voglia fare solo qualcosa o fare e restituire qualcosa, potresti farlo:

public abstract class DoSomethingClass<T>
{
    protected abstract void doSomething(T arg1, T arg2, T arg3);
}

public abstract class ReturnSomethingClass<T, V>
{
    public T value;
    protected abstract void returnSomething(V arg1, V arg2, V arg3);
}

public class DoSomethingInt extends DoSomethingClass<Integer>
{
    public DoSomethingInt(int arg1, int arg2, int arg3)
    {
        doSomething(arg1, arg2, arg3);
    }

    @Override
    protected void doSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        // ...
    }
}

public class ReturnSomethingString extends ReturnSomethingClass<String, Integer>
{
    public ReturnSomethingString(int arg1, int arg2, int arg3)
    {
        returnSomething(arg1, arg2, arg3);
    }

    @Override
    protected void returnSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        String retValue;
        // ...
        value = retValue;
    }
}

public class MainClass
{
    static void main(String[] args)
    {
        int a = 3, b = 4, c = 5;

        Object dummy = new DoSomethingInt(a,b,c);  // doSomething was called, dummy is still around though
        String myReturn = (new ReturnSomethingString(a,b,c)).value; // returnSomething was called and immediately destroyed
    }
}
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.