Qual é o benefício de passar um delegado para o construtor em vez de apenas criar o código do cliente e passar o ParserSettings?

5

Ao responder a uma pergunta sobre estouro de pilha , a biblioteca parecia ter uma maneira estranha de especificar a configuração, através de um Action passado para o construtor:

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;
}

Então, para especificar as configurações, forneça um Action<ParserSettings> que modifique as configurações:

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

Eu não entendo o que esse padrão realiza. Qual é o benefício de passar um delegado para o construtor em vez de apenas criar o código do cliente e passar o ParserSettings para o construtor?

    
por clcto 09.09.2014 / 18:32
fonte

4 respostas

2

Passar um delegado permite que o chamador instancie o objeto para injetar o comportamento de inicialização personalizado. Anote a chamada configuration(this.settings) no código do construtor, que executa o comportamento fornecido pelo representante do construtor.

Posso pensar em várias razões pelas quais isso pode ser útil. Um exemplo hipotético pode ser o de fornecer independência de plataforma. Este é um analisador de linha de comando; Se o analisador puder ser chamado em sistemas Windows ou Unix, passar uma função de primeira classe para o construtor permite que a inicialização detecte a plataforma e ajuste as configurações do analisador com base na plataforma em que está sendo executada.

Leitura adicional
Princípio aberto-fechado
Inversão de Controle

    
por 09.09.2014 / 18:36
fonte
1

No seu exemplo, o construtor cria um novo ParserSettings objetos e passa-o sem modificações para o delegado fornecido. Isso parece excessivamente complicado - por que não apenas criar o objeto ParserSettings e passá-lo como um argumento regular para o construtor?

Considere, porém, se os desenvolvedores de bibliotecas desejavam alterar a forma como as configurações iniciais são criadas, por exemplo, carregá-las a partir de um arquivo de configuração ou inicializá-las usando alguma lógica condicional. O uso de um delegado oculta essa complexidade para o usuário da API e permite que a lógica seja alterada sem afetar os clientes.

Você ainda pode evitar o uso de um delegado buscando as configurações em uma chamada de método separada, por exemplo:

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

Mas isso tem várias desvantagens em comparação com o delegado. A superfície da API é maior, já que você precisa expor como carregar a configuração padrão, algo que pode ser completamente encapsulado na versão delegada. Ele introduz desnecessariamente o risco de passar um objeto de configurações incorreto (por exemplo, é permitido reutilizar as mesmas configurações para múltiplos analisadores?). A API é menos detectável: embora seja óbvio que você precisa chamar new Parser para inicializar um analisador, não é imediatamente óbvio como obter o objeto de configurações requerido. Especialmente se você não precisa modificar as configurações, parece excessivamente complicado.

Em resumo, fornecer um delegado que permita ao cliente inspecionar e modificar as configurações oferece flexibilidade e simplicidade.

    
por 06.04.2018 / 08:42
fonte
0

Em vez de ter que fornecer uma configuração completa, o chamador tem a opção de modificar uma determinada configuração. Ele pode decidir apenas ajustar a configuração existente.

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

O delegado pode ser armazenado pelo Analisador e ser chamado novamente sempre que forem feitas alterações na configuração.

    
por 09.09.2014 / 18:54
fonte
0

Padrão de comando de retransmissão usado extensivamente no WPF é um bom exemplo. O Viewmodel fornece a implementação de ICommand via RealyCommand . Portanto, o modelo de visualização injeta a ação a ser executada sempre que o comando é acionado a partir da interface do usuário.

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);    
    }    
}  

Seu ViewModel pode expor a propriedade (digamos, Salvar) do tipo ICommand , que é vinculado à propriedade do comando button. Como:

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