Compartilhar via


Este artigo foi traduzido por máquina.

Padrões na prática

Programação funcional para cada dia .NET Development

Jeremy Miller

O que é o mais importante avanço no ecossistema .NET nos últimos anos três ou quatro? Talvez seja tentado para nomear uma nova tecnologia ou estrutura, como o Windows Communication Foundation (WCF) ou o Windows Presentation Foundation (WPF). Para mim pessoalmente, no entanto, eu diria que as adições eficientes dos idiomas translation from VPE for Csharp e o Visual Basic nas duas últimas versões do .NET Framework teve um impacto muito mais significativo em Minhas atividades diárias de desenvolvimento. Neste artigo eu gostaria de examinar em particular, como o novo suporte para técnicas de programação funcionais no .NET 3.5 pode ajudar você faça o seguinte:

  1. Faça seu código mais declarativo.
  2. Reduza erros no código.
  3. Escreva menos linhas de código para várias tarefas comuns.

O recurso de LINQ (Language Integrated Query) em todos os seus muitos incarnations é um uso óbvio e eficiente da programação funcional no. NET, mas que apenas a ponta do iceberg.

Para manter com o tema de "desenvolvimento diário"Eu já baseia a maioria dos meus exemplos de código translation from VPE for Csharp 3.0 com alguns JavaScript distribuída em. Observe que alguns dos mais recentes, outras linguagens de programação para o CLR, como a IronPython, IronRuby e F #, possuem suporte substancialmente mais forte ou sintaxe mais vantajoso para as técnicas de programação funcionais mostrados neste artigo. Infelizmente, a versão atual do Visual Basic não oferece suporte funções lambda várias linhas, portanto, muitas das técnicas mostradas aqui não estão como utilizável no Visual Basic. No entanto, eu seria insistir considerar essas técnicas em preparação para a próxima versão do idioma, os desenvolvedores de Visual Basic de remessa no Visual Studio 2010.

Funções first-Class

Alguns elementos de programação funcional são possíveis no translation from VPE for Csharp ou Visual Basic porque temos agora funções de primeira classe que podem ser passadas ao redor entre os métodos, mantido como variáveis ou até mesmo retornado do método outro. Delegações anônimas do .NET 2.0 e a expressão lambda mais recente do .NET 3.5 são como translation from VPE for Csharp e o Visual Basic implementarem funções de primeira classe, mas "Expressão lambda"significa algo mais específico em ciência da computação. Outro termo comum para uma função de primeira classe é "bloquear". Para o restante deste artigo, usarei o termo "bloco"para indicar funções de primeira classe, em vez de "fechamento"(um tipo específico de primeira classe função discutir próximo) ou "Lambda"Para evitar imprecisões acidentais (e wrath dos especialistas em programação funcionais real). Um fechamento contém variáveis definidas de fora a função do fechamento. Se você usou a biblioteca de cada vez mais popular jQuery para desenvolvimento de JavaScript, você provavelmente usou fechamentos com bastante freqüência. Eis um exemplo do uso de um fechamento, retirado do meu projeto atual:

// Expand/contract body functionality          
var expandLink = $(".expandLink", itemElement);
var hideLink = $(".hideLink", itemElement);
var body = $(".expandBody", itemElement);
body.hide();

// The click handler for expandLink will use the
// body, expandLink, and hideLink variables even
// though the declaring function will have long
// since gone out of scope.
expandLink.click(function() {
    body.toggle();
    expandLink.toggle();
    hideLink.toggle();
});

Esse código é usado para configurar um efeito accordion muito típico para mostrar ou ocultar o conteúdo em nossas páginas da Web, clicando em um < um >elemento. Definimos o manipulador de clique do expandLink passando uma função de fechamento usa variáveis criadas fora o fechamento. A função que contém as variáveis e o manipulador de clique sairá longo antes do expandLink pode ser clicado pelo usuário, mas o manipulador de clique ainda poderá usar as variáveis hideLink e corpo.

Lambdas como dados

Em algumas circunstâncias, você pode usar a sintaxe lambda para indicar uma expressão em código que pode ser usado como dados em vez de execução. Eu particularmente não compreender que a primeira instrução que várias vezes, lê-lo, então, vamos examinar um exemplo de tratar uma lambda como dados de um mapeamento de objeto explícito/relacional usando a biblioteca NHibernate Fluent:

 

public class AddressMap : DomainMap<Address>
    {
        public AddressMap()
        {
            Map(a => a.Address1);
            Map(a => a.Address2);
            Map(a => a.AddressType);
            Map(a => a.City);
            Map(a => a.TimeZone);
            Map(a => a.StateOrProvince);
            Map(a => a.Country);
            Map(a => a.PostalCode);
        }
    }

NHibernate fluentes nunca avalia a expressão a = >a.Address1. Em vez disso, ele analisa a expressão para localizar o nome endereço1 para usar no mapeamento NHibernate subjacente. Essa técnica tem se espalham amplamente por vários projetos de código-fonte aberto recentes no espaço de .NET. Usando expressões lambda apenas para chegar a objetos PropertyInfo e nomes de propriedades com freqüência é chamado de reflexão estático.

Blocos de passagem

Uma das razões práticas para estudar a programação funcional é saber como primeira classe funções permitem que você reduzir a duplicação no código, fornecendo um mecanismo mais refinado de composição que a classe. Você geralmente entram em seqüências de código são essencialmente idênticas no formulário básico, exceto para uma etapa em algum lugar no meio da seqüência. Com a programação orientada a objeto, você pode usar herança com o padrão de método de modelo para tentar eliminar a duplicação. Acho mais e mais blocos de passagem que representa a variável etapa no meio para outro método que implementa a seqüência básica de uma maneira de limpeza para eliminar essa duplicação.

Uma das melhores maneiras de tornar uma API mais fácil de usar e menos propenso a erros é reduzir o código repetitivo. Por exemplo, considere o caso comum de uma API projetado para acessar um serviço remoto ou o recurso como um objeto ADO.NET IDbConnection ou um soquete de ouvinte que requer uma conexão com monitoração de estado ou persistente. Você deve normalmente "abrir"a conexão antes de usar o recurso. Essas conexões monitoradoras são geralmente caros ou escassos em termos de recursos, portanto, geralmente é importante para "Fechar"a conexão assim que terminar para liberar o recurso para outros processos ou segmentos.

O código a seguir mostra uma interface de representante para o gateway para uma conexão com monitoração de estado de algum tipo:

 

public interface IConnectedResource
    {
        void Open();
        void Close();

        // Some methods that manipulate the connected resource
        BigDataOfSomeKind FetchData(RequestObject request);
        void Update(UpdateObject update);
    }

Cada vez que outra classe consome nessa interface IConnectedResource, o método Open tem que ser chamada antes de usar qualquer outro método e o método Close deve sempre ser chamado posteriormente, conforme mostrado no 1 figura.

Em um artigo anterior, abordei a idéia de essência versus cerimônia em nossos designs. (Consulte de msdn.microsoft.com/magazine/dd419655.aspx). A "essência"do ConnectedSystemConsumer responsabilidade da classe é simplesmente usar o recurso conectado para atualizar algumas informações. Infelizmente, a maioria do código no ConnectedSystemConsumer trata "cerimônia"de conexão e desconectar-se da interface de IConnectedResource e tratamento de erros.

Figura 1 usando IConnectedResource

 

public class ConnectedSystemConsumer
{
private readonly IConnectedResource _resource;
public ConnectedSystemConsumer(IConnectedResource resource)
{
_resource = resource;
}
public void ManipulateConnectedResource()
{
try
{
// First, you have to open a connection
_resource.Open();
// Now, manipulate the connected system
_resource.Update(buildUpdateMessage());
}
finally
{
_resource.Close();
}
}
}

Pior ainda é o fato de que o "try, abra/fazer coisas e finalmente/Fechar"cerimônia de código deve ser duplicado para cada uso da interface IConnectedResource. Como já abordei antes, uma das melhores maneiras de melhorar seu design é carimbar saída duplicação sempre que ele creeps em seu código. Vamos tentar uma abordagem diferente para a API IConnectedResource usando um bloco ou fechamento. Primeiro, vou aplicar o princípio de Segregação de interface (consulte objectmentor.com/resources/articles/isp.pdf de para obter mais informações) para extrair uma interface estritamente para invocar o recurso conectado sem os métodos para abrir ou fechar:

 

public interface IResourceInvocation
    {
        BigDataOfSomeKind FetchData(RequestObject request);
        void Update(UpdateObject update);
    }

Em seguida, crio uma interface segunda que é usada estritamente para acessar o recurso conectado representado pela interface IResourceInvocation:

 

public interface IResource
    {
        void Invoke(Action<IResourceInvocation> action);
    }

Agora, vamos reescrever a classe ConnectedSystemConsumer para usar a API mais recente, em estilo funcional:

 

public class ConnectedSystemConsumer
    {
        private readonly IResource _resource;
 
        public ConnectedSystemConsumer(IResource resource)
        {
            _resource = resource;
        }

        public void ManipulateConnectedResource()
        {
            _resource.Invoke(x =>
            {
                x.Update(buildUpdateMessage());
            });
        }
    }

Esta nova versão do ConnectedSystemConsumer não tem mais para sobre como configurar ou subdividir o recurso conectado. Na verdade, ConnectedSystemConsumer informa apenas a interface IResource para "Ir para a primeira IResourceInvocation você consulte e dê a ele estas instruções"passando em um bloco ou fechamento para o método IResource.Invoke. Tudo o que repetitivas "Tente, abra/fazer coisas e finalmente/Fechar"cerimônia código que eu reclamando antes está na implementação concreta do IResource, conforme mostrado no do Figura 2.

Figura 2 Concrete implementação de IResource

 

frepublic
class Resource : IResource
{
public void Invoke(Action<IResourceInvocation> action)
{
IResourceInvocation invocation = null;
try
{
invocation = open();
// Perform the requested action
action(invocation);
}
finally
{
close(invocation);
}
}
private void close(IResourceInvocation invocation)
{
// close and teardown the invocation object
}
private IResourceInvocation open()
{
// acquire or open the connection
}
}

Poderia argumentar que tenha Aperfeiçoamos nosso design e a usabilidade de API colocando a responsabilidade de abrindo e fechando a conexão com o recurso externo para a classe de recurso. Você também Aperfeiçoamos a estrutura do nosso código, encapsulando os detalhes de questões de infra-estrutura de fluxo de trabalho principal do aplicativo. A segunda versão do ConnectedSystemConsumer sabe muito menor, sobre o funcionamento do recurso externo conectado, que foi a primeira versão. O segundo design permite que você alterar mais facilmente como o seu sistema interage com o recurso externo conectado sem alteração e potencialmente desestabilizar o código de fluxo de trabalho principal do sistema.

O design segundo também torna seu sistema menos propenso a erro, eliminando a duplicação do "try/abrir e finalmente/Fechar"ciclo. Toda vez que um desenvolvedor tem que repetir esse código, riscos he fazendo uma codificação engano que foi tecnicamente funcione corretamente, mas esgotar recursos e danificar as características de escalabilidade do aplicativo.

Execução atrasada

Um dos conceitos mais importantes para entender sobre programação funcional é execução atrasada. Felizmente, esse conceito também é relativamente simples. Isso significa é que uma função de bloco que você definiu in-line não necessariamente executa imediatamente. Vamos examinar um uso prático de execução atrasada.

Em um aplicativo WPF relativamente grande, uso uma interface de marcador chamada IStartable para denotar serviços que precisam ser, bem, iniciado como parte do processo de inicialização de aplicativo.

 

public interface IStartable
    {
        void Start();
    }

Todos os serviços para este aplicativo específico são registrados e recuperados pelo aplicativo de um recipiente de inversão de controle (no caso, StructureMap). Na inicialização do aplicativo, tenho o seguinte trecho de código para descobrir todos os serviços no aplicativo que precisam ser iniciados dinamicamente e, em seguida, iniciá-los:

 

// Find all the possible services in the main application
// IoC Container that implements an "IStartable" interface
List<IStartable> startables = container.Model.PluginTypes
    .Where(p => p.IsStartable())
    .Select(x => x.ToStartable()).ToList();
         

// Tell each "IStartable" to Start()
startables.Each(x => x.Start());

Há três expressões lambda nesse código. Digamos que você anexado a cópia de código fonte completo da biblioteca de classes base do .NET a esse código e, em seguida, tentou depurar com o depurador. Quando você tenta entrar em local, selecione, ou cada chama, você deve observar que as expressões lambda não são as próximas linhas de código para executar e que, como esses métodos iterar sobre as estruturas internas do membro container.Model.PluginTypes, as expressões lambda são todos os executado várias vezes. Outra maneira de pensar sobre execução atrasada é que quando você chamar cada método, você estiver apenas solicitando cada método o que fazer sempre que se trata de através de um objeto IStartable.

Memoization

Memoization é uma técnica de otimização usada para evitar executar chamadas de função caras reutilizando os resultados da execução anterior com a mesma entrada. Primeiro fornecida em contato com o termo memoization em relação a programação funcional com F #, mas no decorrer da pesquisa deste artigo percebi que minha equipe usa com freqüência memoization no nosso desenvolvimento translation from VPE for Csharp. Digamos que você freqüentemente precisa recuperar algum tipo de dados financeiros calculados para uma determinada região com um serviço assim:

 

public interface IFinancialDataService
    {
        FinancialData FetchData(string region);
    }

IFinancialDataService acontece ser extremamente lento para executar e os dados financeiros são relativamente estáticos, portanto, aplicar memoization seria muito útil para capacidade de resposta do aplicativo. Você pode criar uma implementação de wrapper do IFinancialDataService implementa memoization para uma classe IFinancialDataService interna, como mostrado na do Figura 3.

Figura 3 Implementando uma classe de IFinancialDataService interna

 

public class MemoizedFinancialDataService : IFinancialDataService
{
private readonly Cache<string, FinancialData> _cache;
// Take in an "inner" IFinancialDataService object that actually
// fetches the financial data
public MemoizedFinancialDataService(IFinancialDataService
innerService)
{
_cache = new Cache<string, FinancialData>(region =>
innerService.FetchData(region));
}
public FinancialData FetchData(string region)
{
return _cache[region];
}
}

O cache < TKey, TValue >classe em si é apenas um wrapper em torno de um dicionário < TKey, TValue >objeto. Figura 4 mostra parte da classe cache.

Figura 4 A classe de cache

public class Cache<TKey, TValue> : IEnumerable<TValue> where TValue :
class
{
private readonly object _locker = new object();
private readonly IDictionary<TKey, TValue> _values;
private Func<TKey, TValue> _onMissing = delegate(TKey key)
{
string message = string.Format(
"Key '{0}' could not be found", key);
throw new KeyNotFoundException(message);
};
public Cache(Func<TKey, TValue> onMissing)
: this(new Dictionary<TKey, TValue>(), onMissing)
{
}
public Cache(IDictionary<TKey, TValue>
dictionary, Func<TKey, TValue>
onMissing)
: this(dictionary)
{
_onMissing = onMissing;
}
public TValue this[TKey key]
{
get
{
// Check first if the value for the requested key
// already exists
if (!_values.ContainsKey(key))
{
lock (_locker)
{
if (!_values.ContainsKey(key))
{
// If the value does not exist, use
// the Func<TKey, TValue> block
// specified in the constructor to
// fetch the value and put it into
// the underlying dictionary
TValue value = _onMissing(key);
_values.Add(key, value);
}
}
}
return _values[key];
}
}
}

Se você estiver interessado em aspectos internos da classe cache, você pode encontrar uma versão dele em vários projetos de código-fonte aberto software, incluindo FubuMVC StructureMap, StoryTeller, e, acredito, NHibernate Fluent.

O padrão de mapa/reduzir

Acontece que muitas tarefas de desenvolvimento comuns são mais simples com técnicas de programação funcionais. Lista em particular, e operações de conjunto no código são muito mais simples desencaixados em idiomas que oferecem suporte padrão “ mapa/reduzir ”. (No LINQ, “ mapa ” é “ selecione ” e “ reduzir ” é “ agregação ”.) Pense em como você deve calcular a soma de uma matriz de inteiros. No .NET 1.1, você tinha iterar sobre a matriz algo assim:

números de [] int = novo [] int {1,2,3,4,5};
Int soma = 0;
para (int i = 0;i <numbers.Length;i ++)
{
soma += números [i];
}

Console.WriteLine(Sum);

A onda de aprimoramentos de linguagem para oferecer suporte a LINQ no .NET 3.5 forneceu os recursos de mapa/reduzir comuns em linguagens de programação funcionais. Hoje em dia, o código acima simplesmente poderia ser escrito como:

números de [] int = novo [] int {1,2,3,4,5};
Int soma = numbers.Aggregate ((x, y) = >x + y);

ou mais simplesmente como:

Int soma = numbers.Sum();
Console.WriteLine(Sum);

Continuação

Coloque aproximadamente, uma continuação em programação é uma abstração de algum tipo que indica "o que fazer em seguida"ou o "rest da computação". Às vezes é útil para concluir a parte de um processo computacional em outro momento, como em um aplicativo assistente no qual um usuário explicitamente pode permitir a próxima etapa ou cancelar o processo inteiro.

Vamos ir diretamente para um exemplo de código. Digamos que você está desenvolvendo um aplicativo da área de trabalho no WPF ou WinForms. Você freqüentemente precisará iniciar algum tipo de processo de execução demorada ou acessar um serviço externo lento de uma ação de tela. Para fins de facilidade de uso, você certamente não deseja bloquear a interface de usuário e torná-lo responder enquanto a chamada de serviço está acontecendo, para que você executa em um thread de plano de fundo. Quando finalmente retornar a chamada de serviço, pode desejar atualizar a interface do usuário com os dados voltem do serviço, mas como qualquer WinForms experientes ou WPF desenvolvedor sabe, você pode atualizar elementos de interface do usuário apenas no thread da interface do usuário principal.

Certamente você pode usar a classe BackgroundWorker do System.ComponentModel, mas prefiro uma abordagem diferente com base em expressões lambda passando um objeto CommandExecutor, representado por esta interface:

public interface ICommandExecutor
    {
        // Execute an operation on a background thread that
        // does not update the user interface
        void Execute(Action action);

        // Execute an operation on a background thread and
        // update the user interface using the returned Action
        void Execute(Func<Action> function);
    }

O primeiro método é simplesmente uma instrução para executar uma atividade em um thread de segundo plano. O segundo método que recebe um Func < ação >é mais interessante. Vamos examinar como este método geralmente seria usado no código do aplicativo.

Primeiro, suponha que você está estruturar seu código WinForms ou WPF com o formulário Supervising Controller do padrão Model View Presenter. (Consulte msdn.microsoft.com/magazine/cc188690.aspx de para obter mais informações sobre o padrão MVP). Nesse modelo, a classe Presenter será responsável por chamando um método de serviço de execução demorada e usar os dados de retorno para atualizar o modo de exibição. Sua nova classe Presenter usarão simplesmente a interface de ICommandExecutor mostrada anteriormente para lidar com todos os o threading e thread empacotamento trabalho, conforme mostrado no do Figura 5.

Figura 5 A classe do apresentador

public class Presenter
{
private readonly IView _view;
private readonly IService _service;
private readonly ICommandExecutor _executor;
public Presenter(IView view, IService service, ICommandExecutor
executor)
{
_view = view;
_service = service;
_executor = executor;
}
public void RefreshData()
{
_executor.Execute(() =>
{
var data = _service.FetchDataFromExtremelySlowServiceCall();
return () => _view.UpdateDisplay(data);
});
}
}

A classe Presenter chama ICommandExecutor.Execute passando em um bloco que retorna outro bloco. O bloco original invoca a chamada de serviço de execução demorada para obter alguns dados e retorna um bloco de continuação que pode ser usado para atualizar a interface do usuário (IView neste cenário). Nesse caso específico, é importante usar a abordagem de continuação em vez de atualizar apenas o IView ao mesmo tempo porque a atualização deve ser empacotado ao thread da interface do usuário.

Figura 6 mostra a implementação concreta da interface ICommandExecutor.

Figura 6 Concrete implementação de ICommandExecutor

public class AsynchronousExecutor : ICommandExecutor
{
private readonly SynchronizationContext _synchronizationContext;
private readonly List<BackgroundWorker> _workers =
new List<BackgroundWorker>();
public AsynchronousExecutor(SynchronizationContext
synchronizationContext)
{
_synchronizationContext = synchronizationContext;
}
public void Execute(Action action)
{
// Exception handling is omitted, but this design
// does allow for centralized exception handling
ThreadPool.QueueUserWorkItem(o => action());
}
public void Execute(Func<Action> function)
{
ThreadPool.QueueUserWorkItem(o =>
{
Action continuation = function();
_synchronizationContext.Send(x => continuation(), null);
});
}
}

O método Execute (Func < ação >) chama Func < ação >em um segmento de plano de fundo e, em seguida, usa a continuação (a ação retornada pelo Func < ação >) e usa um SynchronizationContext objeto para executar a continuação no thread da interface do usuário principal.

Gosto de passando blocos para a interface ICommandExecutor devido a pouco como código ceremonial demora para invocar o processamento de plano de fundo. Em uma versão anterior dessa abordagem, antes que tínhamos delegados anônimos ou expressões lambda no translation from VPE for Csharp, tive uma implementação semelhante usado pouco classes de padrão de comando semelhante ao seguinte:

public interface ICommand
    {
        ICommand Execute();
    }

    public interface JeremysOldCommandExecutor
    {
        void Execute(ICommand command);
    }

A desvantagem da primeira abordagem é que eu tive que escrever classes de comando adicionais apenas para a operação de plano de fundo e o código atualizando o modo de exibição do modelo. As funções de declaração e construtor de classe extra são um pouco mais código cerimônia que podemos eliminar com a abordagem funcional, mas é mais importante para mim que a abordagem funcional permite-me colocar todo esse código intimamente relacionado em um único lugar no apresentador em vez de precisar espalhadas sobre essas classes de comando pouco.

Estilo de passagem de continuação

Aproveitando o conceito de continuação, você pode usar o estilo continuação passar de programação para chamar um método passando uma continuação em vez de aguardar o valor de retorno do método. O método aceitando a continuação é responsável por decidir se e quando a chamada a continuação.

No meu projeto Web MVC atual, existem dezenas de ações do controlador que salvar atualizações de entrada do usuário enviada do navegador do cliente por meio de uma chamada de AJAX para um objeto de entidade de domínio. A maioria dessas ações do controlador de simplesmente chama nossa classe Repository para salvar a entidade alterada, mas outras ações usam outros serviços para executar o trabalho de persistência. (Consulte meu artigo na edição de abril de MSDN Magazine no msdn.microsoft.com/magazine/dd569757.aspx de para saber mais sobre a classe de repositório.)

O fluxo de trabalho básico dessas ações do controlador é consistente:

  1. Validar a entidade de domínio e registre os erros de validação.
  2. Se houver erros de validação, retornar uma resposta para o cliente indicando que a operação falhou e inclua os erros de validação para exibição no cliente.
  3. Se existem erros de validação, manter a entidade de domínio e retornar uma resposta para o cliente indicando que a operação teve êxito.

O que nós gostaria de fazer é centralizar o fluxo de trabalho básico mas ainda permitir que cada ação individual controlador variar como a persistência real é feita. Hoje minha equipe está fazendo isso por herdam um CrudController < T >superclasse com muita métodos de modelo que cada subclasse pode substituir para adicionar ou alterar o comportamento básico. Essa estratégia trabalhou bem na primeira, mas ele rapidamente é dividir como as variações aumentaram. Agora minha equipe será mover para usando código de estilo continuação passar por ter ações nossas controlador delegar a algo como a seguinte interface:

 

public interface IPersistor
    {
        CrudReport Persist<T>(T target, Action<T> saveAction);
        CrudReport Persist<T>(T target);
    }

Uma ação de controlador típica diria IPersistor para executar o fluxo de trabalho CRUD básico e fornecer uma continuação IPersistor usa para realmente salvar o objeto de destino. Figura 7 mostra uma ação de controlador de exemplo que invoca IPersistor mas utiliza um serviço diferente que nosso repositório básico para persistência real.

Figura 7 uma ação de controlador de exemplo

public class SolutionController
{
private readonly IPersistor _persistor;
private readonly IWorkflowService _service;
public SolutionController(IPersistor persistor, IWorkflowService
service)
{
_persistor = persistor;
_service = service;
}
// UpdateSolutionViewModel is a data bag with the user
// input from the browser
public CrudReport Create(UpdateSolutionViewModel update)
{
var solution = new Solution();
// Push the data from the incoming
// user request into the new Solution
// object
update.ToSolution(solution);
// Persist the new Solution object, if it's valid
return _persistor.Persist(solution, x => _service.Create(x));
}
}

Acho que o importante observar aqui é que IPersistor propriamente dito é decidir se e quando a continuação fornecida pelo SolutionController será chamada. Figura 8 mostra uma implementação de exemplo de IPersistor.

Figura 8 uma implementação de IPersistor

public class Persistor : IPersistor
{
private readonly IValidator _validator;
private readonly IRepository _repository;
public Persistor(IValidator validator, IRepository repository)
{
_validator = validator;
_repository = repository;
}
public CrudReport Persist<T>(T target, Action<T> saveAction)
{
// Validate the "target" object and get a report of all
// validation errors
Notification notification = _validator.Validate(target);
// If the validation fails, do NOT save the object.
// Instead, return a CrudReport with the validation errors
// and the "success" flag set to false
if (!notification.IsValid())
{
return new CrudReport()
{
Notification = notification,
success = false
};
}
// Proceed to saving the target using the Continuation supplied
// by the client of IPersistor
saveAction(target);
// return a CrudReport denoting success
return new CrudReport()
{
success = true
};
}
public CrudReport Persist<T>(T target)
{
return Persist(target, x => _repository.Save(x));
}
}

Escreva menos código

Francamente, originalmente escolhi este tópico porque estava interessado em aprender mais sobre programação funcional e como ele pode ser aplicado mesmo dentro translation from VPE for Csharp ou o Visual Basic. No decorrer de escrever este artigo, que aprendi muito sobre técnicas de programação funcionais como úteis podem estar em tarefas normais diárias. A mais importante conclusão cheguei, e que tentei transmitir aqui, que é comparado com outras técnicas, abordagens de programação funcionais com freqüência pode levar a escrever menos código e geralmente mais código declarativo para algumas tarefas — e isso quase sempre é uma coisa boa.

Jeremy Miller MVP da Microsoft em translation from VPE for Csharp, também é autor da ferramenta de código aberto StructureMap (structuremap.sourceforge.net) para injeção de dependência com o .NET e a próxima ferramenta (storyteller.tigris.org), StoryTeller destinada teste de sobrecarga FIT no. NET. Visite seu blog, The Shade Tree Developer em codebetter.com/blogs/jeremy.miller, parte do site CodeBetter.