Evitare una grande quantità di sovraccarichi

1

Attualmente sto scrivendo un'interfaccia di mailing per la nostra applicazione aziendale. A seconda della modalità di invio (email normale, email di massa, modelli, mailmerge, ...) il nostro metodo Send() richiede un sacco di parametri.

Ora abbiamo questo metodo Send() che offre circa 20 parametri, con quasi 10 sovraccarichi e valori predefiniti.

Ovviamente è possibile utilizzare la funzionalità C # 4.0 dei parametri denominati come descritto qui , ma anche quello sarebbe un casino quando per la maggior parte devono essere usati 10 dei 15 parametri sovraccaricati.

C'è qualche pratica migliore?

    
posta SeToY 30.01.2015 - 13:54
fonte

3 risposte

7

Sembra che il tuo metodo Send() stia facendo troppo e chiedendo troppe informazioni.

Che cosa succede quando deve gestire un modello o una stampa unione? È davvero che invia qualcosa? Io non la penso così, e così Send è un nome male scelto. Ma non è la scelta del nome che è male qui, ma, ancora una volta, il fatto che Send() faccia troppo: invia e-mail, memorizza modelli o fa una fusione e memorizza i risultati in un database (suppongo ).

Quando dividi il metodo in molti più piccoli, il tuo Send() diventa più piccolo:

public void Send() // Actually sends regular e-mails or bulk e-mails.
public void StoreTemplate()
public void DoMailMerge()

Torniamo a Send() . Se ha ancora troppi argomenti, puoi dargli informazioni sulla configurazione. Invece, questa informazione dovrebbe essere caricata da un file di configurazione o essere passata attraverso le proprietà all'istanza dell'oggetto che contiene Send() .

Invece di:

sender.Send(from, bulkTo, title, body, timeout, mailsPerSecond);

dovresti fare:

sender.bulkMailsPerSecond = this.mailsPerSecond;
sender.Send(bulkTo, title, body);

dato che:

  • from è ora nella configurazione, caricata direttamente da Sender quando necessario,
  • timeout diventa una proprietà di Sender con un valore predefinito.

Il prossimo passo è ridurre la dimensione di Sender : potresti finire per notare che la classe è troppo grande e fa troppo. Ad esempio, si può finire per esportare materiale correlato agli schemi in una classe separata, o usare l'ereditarietà per applicare la logica personalizzata per e-mail regolari e e-mail in blocco:

public abstract class Sender
{
    protected Address From { get { ... } }
    protected TimeSpan Timeout { get { ... } set { ... } }
}

public class RegularEmailSender : Sender
{
    public void Send(Address to, string title, string body) { ... }
}

public class BulkEmailSender : Sender
{
    private int BulkMailsPerSecond { get { ... } set { ... } }
    public void Send(IEnumerable<Address> bulkTo, string title, string body) { ... }
}
    
risposta data 30.01.2015 - 14:22
fonte
3

Dai tuoi commenti sembra che tu abbia qualcosa di simile a questo:

Send(Data data, Address address, A a, B b, C c) { ... }

Send(Data data, Address address, A a, D d, E e, F f) { ... }

Send(Data data, Address address, B b, H h, J j) { ... }

Dove A , B , C , ... sono tipi di valori utilizzati per la configurazione. Stai implicitamente lavorando con un tipo Configuration che può venire in un insieme finito di "forme" o combinazioni. Desideri un unione taggata , ma C # non li supporta direttamente. Tuttavia, puoi implementarne uno con ereditarietà . La chiave è riconoscere che è possibile creare una classe astratta che abbia un insieme finito di sottoclassi di:

  1. Assegnare alla classe astratta un costruttore privato.
  2. Rendere le sottoclassi classi sigillate interne della classe astratta.

Le sottoclassi interne hanno accesso al costruttore privato della classe astratta, ma le classi di livello superiore no, quindi nessuno può aggiungere una nuova sottoclasse "dall'esterno".

Quindi avresti qualcosa di simile a questo:

abstract class Configuration {
    private Configuration() {
        // Prevent outside subclassing
    }

    public sealed class Conf1 : Configuration {
         public A a { get; set; }
         public B b { get; set; }
         public C c { get; set; }

         // Constructor boilerplate
    }

    public sealed class Conf2 : Configuration {
         // Different fields
         // Constructor boilerplate
    }

    // ... more subclasses
}

(Non lavoro regolarmente con C #, spero di non aver commesso gravi errori di sintassi.)

Quindi tutto ciò di cui hai bisogno è un modo sicuro per esaminare quale tipo di Configuration sottoclasse hai ottenuto. Questo è essenzialmente il pattern del visitatore, ma puoi renderlo più conciso con i delegati:

abstract class Configuration {
    ...
    public abstract R Match<R>(Func<Conf1, R> ifConf1, Func<Conf2, R> ifConf2, ...);
    ...
    public sealed class Conf1 : Configuration {
        ...
        public override R Match<R>(...) {
            // Each subclass calls the delegate corresponding to it
            return ifConf1(this);
        }
        ...
    }

    // And so on with every other subclass
 }

Ora puoi ridurre Send in:

Send(Data data, Address address, Configuration config) {
    return config.Match(
        ifConf1: conf1 => { ... },
        ifConf2: conf2 => { ... },
        ...
    );
}

Tutto presuppone che il numero di combinazioni di valori di configurazione sia limitato e non cambi molto spesso. Ho assunto che, ad esempio, tutti i metodi Send utilizzino lo stesso meccanismo sottostante e che possano essere semplicemente configurati in vari modi. Se volessi astrarre diversi tipi di servizi di invio (ad es. E-mail o qualche tipo di IPC vs SOAP), utilizzerei un interface simile a MainMa's Sender class poiché questa soluzione ti consente sempre di aggiungere facilmente nuovi tipi di" mittenti ".

    
risposta data 30.01.2015 - 14:39
fonte
0

Sembra che tu stia facendo troppo nel metodo send, dove idealmente dovrebbe essere usato solo per inviare email ma non per processare come l'unione, Tuttavia senza vedere le firme del tuo metodo non posso dirlo con certezza.

L'utilizzo di molti overload con valori predefiniti spesso causa molta confusione, specialmente se non viene eseguito correttamente.

Un modo per evitare ciò è utilizzare un oggetto DTO per passare al metodo invece di utilizzare gli overload. È possibile creare un singolo metodo e utilizzare un DTO per passare valori al metodo.

    
risposta data 01.02.2015 - 05:50
fonte

Leggi altre domande sui tag