Qual è il vantaggio del passaggio di un delegato al costruttore invece del solo fatto che il codice client viene creato e passato a ParserSettings?

5

Quando rispondi a una domanda su Stack Overflow , la libreria sembrava avere un modo strano per specificare la configurazione, attraverso un Action passato al costruttore:

public Parser(Action<ParserSettings> configuration)
{
    if (configuration == null) throw new ArgumentNullException("configuration");
    this.settings = new ParserSettings();
    configuration(this.settings);
    this.settings.Consumed = true;
}

internal Parser(ParserSettings settings)
{
    this.settings = settings;
    this.settings.Consumed = true;
}

Quindi per specificare le impostazioni fornite a Action<ParserSettings> che modifica le impostazioni:

var parser = new Parser( s => { s.CaseSensitive = false; } );

Non capisco cosa questo modello compia. Qual è il vantaggio del passaggio di un delegato al costruttore invece del solo fatto che il codice del client viene creato e passa il ParserSettings al costruttore?

    
posta clcto 09.09.2014 - 18:32
fonte

4 risposte

2

Il passaggio di un delegato consente al chiamante di creare un'istanza dell'oggetto per iniettare il comportamento di inizializzazione personalizzato . nota la chiamata configuration(this.settings) nel codice costruttore, che esegue il comportamento fornito dal delegato del costruttore.

Posso pensare a una serie di motivi per cui potrebbe essere utile. Un esempio ipotetico potrebbe essere quello di fornire l'indipendenza dalla piattaforma. Questo è un parser da riga di comando; se il parser può essere richiamato su sistemi Windows o Unix, passare una funzione di prima classe al costruttore consente all'inizializzazione di rilevare la piattaforma e regolare le impostazioni del parser in base alla piattaforma su cui è in esecuzione.

Ulteriori letture
Principi Open-Closed
Inversion of Control

    
risposta data 09.09.2014 - 18:36
fonte
1

Nel tuo esempio, il costruttore crea un nuovo ParserSettings oggetti e lo passa non modificato al delegato fornito. Questo sembra troppo complicato - perché non solo il client ha creato l'oggetto ParserSettings e lo passa come argomento normale al costruttore?

Pensa però se gli sviluppatori di librerie volessero cambiare il modo in cui vengono create le impostazioni iniziali, ad esempio caricali da un file di configurazione o inizializzali utilizzando alcune logiche condizionali. L'utilizzo di un delegato nasconde questa complessità per l'utente dell'API e consente alla logica di cambiare senza influire sui client.

Potresti ancora evitare l'uso di un delegato recuperando le impostazioni in una chiamata al metodo separata, ad esempio:

ParserSettings s = Parser.GetDefaultSettings(); 
s.CaseSensitive = false;
var parser = new Parser(s);

Ma questo ha diversi svantaggi rispetto al delegato. La superficie dell'API è più grande in quanto è necessario esporre come caricare la configurazione predefinita, qualcosa che può essere completamente incapsulato nella versione delegata. Introduce inutilmente il rischio di passare un oggetto impostazioni errato (ad esempio è permesso riutilizzare le stesse impostazioni per più parser?). L'API è meno individuabile: mentre è ovvio che devi chiamare new Parser per inizializzare un parser, non è immediatamente ovvio come ottenere l'oggetto delle impostazioni requred. Soprattutto se non hai bisogno di modificare le impostazioni, sembra eccessivamente convoluto.

In breve, fornire un delegato che consenta al cliente di ispezionare e modificare le impostazioni offre flessibilità e semplicità.

    
risposta data 06.04.2018 - 08:42
fonte
0

Invece di dover fornire una configurazione completa, il chiamante ha la possibilità di modificare una determinata configurazione. Potrebbe decidere di modificare solo la configurazione esistente.

var parser = new Parser( s => { 
    s.IgnoreUnknownArguments = s.IgnoreUnknownArguments || s.CaseSensitive;
} );

Il delegato può essere memorizzato dal parser e richiamato ogni volta che vengono apportate modifiche alla configurazione.

    
risposta data 09.09.2014 - 18:54
fonte
0

Il modello di comando relè ampiamente utilizzato in WPF è un buon esempio. Viewmodel fornisce l'implementazione di ICommand tramite RealyCommand . Pertanto, il modello di visualizzazione inietta l'azione da eseguire ogni volta che viene attivato il comando dall'interfaccia utente.

public class RelayCommand : ICommand    
{    
    private Action<object> execute;    
    private Func<object, bool> canExecute;    

    public event EventHandler CanExecuteChanged    
    {    
        add { CommandManager.RequerySuggested += value; }    
        remove { CommandManager.RequerySuggested -= value; }    
    }    

    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)    
    {    
        this.execute = execute;    
        this.canExecute = canExecute;    
    }    

    public bool CanExecute(object parameter)    
    {    
        return this.canExecute == null || this.canExecute(parameter);    
    }    

    public void Execute(object parameter)    
    {    
        this.execute(parameter);    
    }    
}  

Il tuo ViewModel può esporre la proprietà (ad es. Salva) di tipo ICommand che è associata alla proprietà del comando del pulsante. Mi piace:

public ICommand Save{
get {return new RelayCommand(o => { /* do something 1 */ }, o => true); }
    
risposta data 05.04.2018 - 17:40
fonte

Leggi altre domande sui tag