Interface no nível de classe ou função?

5

Eu tenho caído em um padrão ultimamente onde tenho definido rotinas que dependem de uma interface definida por uma função que é especificada como um parâmetro para a rotina. (A linguagem é C #, mas isso pode ser aplicado a qualquer idioma com funções de primeira classe.)

Por exemplo, eu tenho uma API que expõe as operações CRUD em uma loja de apoio. No caso de uma operação GET em um recurso específico, posso generalizar a rotina para:

  • obtenha o recurso que estamos procurando
    • retorna uma resposta não encontrada se o recurso não existir
    • retorna o recurso se encontrado

O que acabei fazendo é definir uma rotina que aceita uma função delegada para encontrar o recurso. É esse delegado que define o contrato de interface.

Isso funciona bem na minha situação porque as informações necessárias para localizar o recurso podem variar. No meu caso, ele procura dados em um banco de dados por chaves, mas o tipo e o número de chaves podem variar. Eu posso capturar estes em um fechamento na rotina de chamada e satisfazer a interface de função de delegado. Por exemplo:

// Locate a simple record that only has one key
public SimpleRecord GetSimpleRecord(int recordID) {
   return getResource(repository => repository.SimpleRecords.Find(recordID));
}

// Locate a complex record that has many keys
public ComplexRecord GetComplexRecord(int recordID, int userID, string token) {
   return getResource(repository => repository.ComplexRecords.Find(recordID, userID, token));
}

Este trabalho, mas parece ser uma mistura de OOP e programação de estilo funcional. Se eu precisar de mais de um delegado, ele começa a ficar um pouco confuso. Algumas rotinas que eu preciso em todos os lugares acabei definindo como métodos abstratos que todas as subclasses precisam implementar. Então eu tenho um híbrido.

Esse tipo de técnica tem um nome ou padrão que está faltando? Os delegados devem ser implementados em uma interface de classe definida que seja passada para o chamador?

UPDATE com um exemplo mais concreto:

Estou tentando aderir ao principal DRY. Eu estou falando sobre controladores em um aplicativo C # Web API. Todo e qualquer pedido tem alguma semelhança que eu implementei em uma classe de controlador base:

  • Manipule todas as exceções, retornando o código de status HTTP correto, (404 para recursos que não foram encontrados, 201 para recursos criados, etc.)
  • Mapear entidades do banco de dados para e de objetos de transporte de dados com os quais o cliente lida

Eu quero expressar o que fazer nesta classe base e delegar como para a classe concreate. O como acaba sendo implementado por funções delegadas. Talvez eu precise obter uma pessoa do banco de dados, pelo primeiro nome e sobrenome, ou uma ordem de compra, por um id inteiro. Em ambos os casos, se o recurso não for encontrado, um 404 deve ser retornado. Um retorna uma pessoa, uma ordem de compra. Como procurá-los e como mapear os dados para um objeto cliente é diferente.

A função básica pode ser assim:

T getResource<T> (Func<IRepository, T> find) {
    T data = find(getRepository());
    if (data == null) {
        throw new DataNotFoundException();
    }
    return data;
}

Agora, no controlador de pessoa e no controlador de ordem de compra, não preciso repetir a lógica do que fazer quando o recurso não é encontrado - basta implementar o delegado de localização. (Este é um exemplo simples sem mapeamento, adição, remoção ou outros detalhes que diferem entre recursos e recursos).

public Person Get(string first, string last) {
   return getResource<Person>(repository => repository.People.Find(first, last));
}

public PurchaseOrder Get(int id) {
   return getResource<PurchaseOrder>(repository => repository.POs.Find(id));
}

Observe como os encerramentos acima lidam com números variados e tipos de parâmetros para encontrar coisas, mas satisfazem a interface definida pela função find do delegate. Isso é possível com interfaces de classe padrão?

(E essa questão não é sobre o repositório. Isso é resolvido com injeção de dependência e é implementado com o Entity Framework.)

    
por Jeremyx 19.11.2013 / 21:13
fonte

3 respostas

1

Eu penso imediatamente em dois padrões que podem ser úteis para o seu caso.

Padrão de método de modelo

Tudo o que é igual para todas as implementações concretas está em uma única classe abstrata. Tudo o que é mais concreto é delegado às subclasses. Se eu fosse você, primeiro tentaria este aqui.

Padrão do construtor

Se você precisar de mais flexibilidade, por exemplo Se estiver usando o Template Method Pattern, você terá que criar uma ampla hierarquia de classes abstratas, você pode se beneficiar do padrão de construtor. Transformar código imperativo em um objeto mais descritivo é exatamente o que você obtém com esse padrão. Tudo o que resta para as aplicações cliente é criar e passar uma descrição concreta do que você quer para o CRUD a partir do armazenamento de apoio.

    
por 27.12.2013 / 22:28
fonte
0

Você pode ter mais indireto do que o necessário. Por exemplo:

public SimpleRecord GetSimpleRecord(int recordID) {
   return getResource(repository => repository.SimpleRecords.Find(recordID));
}

poderia ser simplesmente:

public SimpleRecord GetSimpleRecord(int recordID) {
   return repository.SimpleRecords.Find(recordID);
}

Supondo que o Repository cumpre um contrato de interface como o IRepository, você pode simplesmente injetar um repositório que desenhe a fonte de dados correta.

public class DataAccess
{
    IRepository repository;

    public DataAccess(IRepository repository)
    {
        this.repository = repository;
    }

    public SimpleRecord Find(int recordID)
    {
        // etc.
    }
}
    
por 19.11.2013 / 21:42
fonte
0

Por que não usar apenas funções assertivas?

public T Exists<T>(T val) where T : class
{
    if (val == null)
        throw new DataNotFoundException();
    return val;
}

E então use:

public Person Get(string first, string last) {
   return Exists(repository.People.Find(first, last));
}

Mas isso provavelmente se aplica apenas ao seu exemplo simples. Mas não sabemos quão complexos são seus requisitos se você não descrever seus casos complexos. Eu acho que você está se aproximando desse problema do lado ruim. Embora o C # tenha poucas propriedades funcionais, não é uma linguagem funcional. Tentar resolver problemas de maneira funcional nunca vai parecer bem.

    
por 27.12.2013 / 23:03
fonte