Passar o objeto duas vezes para o mesmo método ou consolidar com interface combinada?

15

Eu tenho um método que cria um arquivo de dados depois de conversar com uma placa digital:

CreateDataFile(IFileAccess boardFileAccess, IMeasurer boardMeasurer)

Aqui boardFileAccesse boardMeasurerestão a mesma instância de um Boardobjeto que implementa ambos IFileAccesse IMeasurer. IMeasureré usado neste caso para um único método que definirá um pino na placa ativo para fazer uma medição simples. Os dados dessa medição são armazenados localmente no quadro usando IFileAccess. Boardestá localizado em um projeto separado.

Cheguei à conclusão de que CreateDataFileuma coisa é fazer uma medição rápida e, em seguida, armazenar os dados. Fazer as duas coisas no mesmo método é mais intuitivo para outra pessoa que usa esse código do que para medir e gravar em um arquivo. como chamadas de método separadas.

Para mim, parece estranho passar o mesmo objeto para um método duas vezes. Eu considerei criar uma interface local IDataFileCreatorque se estenda IFileAccesse, em IMeasurerseguida, tenha uma implementação contendo uma Boardinstância que chamará apenas os Boardmétodos necessários . Considerando que o mesmo objeto do quadro seria sempre usado para medição e gravação de arquivos, é uma prática ruim passar o mesmo objeto para um método duas vezes? Em caso afirmativo, o uso de uma interface local e a implementação são uma solução apropriada?

pavuxun
fonte
2
É difícil ou impossível distinguir a intenção do seu código a partir dos nomes que você está usando. Uma interface chamada IDataFileCreator sendo passada para um método chamado CreateDataFile é incompreensível. Eles estão competindo pela responsabilidade de persistir dados? De que classe o CreateDataFile é um método? A medição não tem nada a ver com dados persistentes, muito é claro. Sua pergunta não é sobre o maior problema que você está tendo com seu código.
Martin Maat
É sempre possível que seu objeto de acesso a arquivos e seu medidor possam ser dois objetos diferentes? Eu diria que sim. Se você mudar agora, precisará alterá-lo novamente na versão 2, que suporta a realização de medições na rede.
user253751
2
Aqui está outra pergunta: por que os objetos de acesso e medição de arquivos de dados são os mesmos em primeiro lugar?
user253751

Respostas:

40

Não, isso está perfeitamente bem. Isso significa apenas que a API foi projetada em excesso no que diz respeito ao seu aplicativo atual .

Mas isso não prova que nunca haverá um caso de uso em que a fonte de dados e o medidor sejam diferentes. O objetivo de uma API é oferecer ao programador de aplicativos possibilidades, nem todas elas serão usadas. Você não deve restringir artificialmente o que os usuários da API podem fazer, a menos que isso complique a API, para que a compreensão da rede diminua.

Kilian Foth
fonte
7

Concorde com a resposta de @ KilianFoth de que isso está perfeitamente correto .

Ainda assim, se desejar, você pode criar um método que utilize um único objeto que implemente as duas interfaces:

public object CreateDataFile<T_BoardInterface>(
             T_BoardInterface boardInterface
    )
    where T_BoardInterface : IFileAccess, IMeasurer
{
    return CreateDataFile(
                boardInterface
            ,   boardInterface
        );
}

Não há razão geral para que os argumentos precisem ser objetos diferentes e, se um método exigir que os argumentos sejam diferentes, esse seria um requisito especial que seu contrato deve deixar claro.

Nat
fonte
4

Cheguei à conclusão de que CreateDataFileuma coisa é fazer uma medição rápida e, em seguida, armazenar os dados. Fazer as duas coisas no mesmo método é mais intuitivo para outra pessoa que usa esse código do que para medir e gravar em um arquivo. como chamadas de método separadas.

Eu acho que esse é seu problema, na verdade. O método não está fazendo uma coisa. Ele está executando duas operações distintas que envolvem E / S para dispositivos diferentes , sendo que ambos são descarregados para outros objetos:

  • Buscar uma medida
  • Salve esse resultado em um arquivo em algum lugar

Essas são duas operações de E / S diferentes. Notavelmente, o primeiro não altera o sistema de arquivos de forma alguma.

De fato, devemos observar que há um passo intermediário implícito:

  • Buscar uma medida
  • Serialize a medida em um formato conhecido
  • Salve a medida serializada em um arquivo

Sua API deve fornecer cada um deles separadamente de alguma forma. Como você sabe que um chamador não deseja fazer uma medição sem armazená-la em qualquer lugar? Como você sabe que eles não desejam obter uma medida de outra fonte? Como você sabe que eles não querem armazená-lo em outro lugar que não seja o dispositivo? Há boas razões para desacoplar as operações. Numa nua mínimo, cada peça deve ser disponível para qualquer chamador. Não devo ser forçado a gravar a medida em um arquivo se meu caso de uso não exigir.

Como exemplo, você pode separar as operações assim.

IMeasurer tem uma maneira de buscar a medida:

public interface IMeasurer
{
    IMeasurement Measure(int someInput);
}

Seu tipo de medição pode ser apenas algo simples, como um stringou decimal. Não estou insistindo que você precise de uma interface ou classe para isso, mas torna o exemplo aqui mais geral.

IFileAccess possui algum método para salvar arquivos:

interface IFileAccess
{
    void SaveFile(string fileContents);
}

Então você precisa de uma maneira de serializar uma medida. Crie isso na classe ou interface que representa uma medida ou tenha um método utilitário:

interface IMeasurement
{
    // As part of the type
    string Serialize();
}

// Utility method. Makes more sense if the measurement is not a custom type.
public static string SerializeMeasurement(IMeasurement m)
{
    return ...
}

Não está claro se você ainda tem essa operação de serialização separada.

Esse tipo de separação melhora sua API. Permite que o chamador decida o que precisa e quando, em vez de forçar suas idéias preconcebidas sobre o que a E / S deve executar. Os chamadores devem ter o controle para executar qualquer operação válida , seja você útil ou não.

Depois de ter implementações separadas para cada operação, seu CreateDataFilemétodo se torna apenas um atalho para

fileAccess.SaveFile(SerializeMeasurement(measurer.Measure()));

Notavelmente, seu método agrega muito pouco valor depois de fazer tudo isso. A linha de código acima não é difícil para os chamadores usarem diretamente, e seu método é puramente por conveniência. Deve ser e é algo opcional . E essa é a maneira correta de a API se comportar.


Depois que todas as partes relevantes são fatoradas e reconhecemos que o método é apenas uma conveniência, precisamos reformular sua pergunta:

Qual seria o caso de uso mais comum para os chamadores?

Se o objetivo principal é tornar o caso de uso típico de medir e gravar no mesmo quadro um pouco mais conveniente, faz todo o sentido apenas disponibilizá-lo Boarddiretamente na classe:

public class Board : IMeasurer, IFileAccess
{
    // Interface methods...

    /// <summary>
    /// Convenience method to measure and immediate record measurement in
    /// default location.
    /// </summary>
    public void ReadAndSaveMeasurement()
    {
        this.SaveFile(SerializeMeasurement(this.Measure()));
    }
}

Se isso não melhorar a conveniência, eu não me incomodaria com o método.


Sendo este um método de conveniência, levanta uma outra questão.

A IFileAccessinterface deve saber sobre o tipo de medição e como serializá-la? Nesse caso, você pode adicionar um método para IFileAccess:

interface IFileAccess
{
    void SaveFile(string fileContents);
    void SaveMeasurement(IMeasurement m);
}

Agora os chamadores apenas fazem o seguinte:

fileAccess.SaveFile(measurer.Measure());

que é tão curto e provavelmente mais claro que o seu método de conveniência, conforme concebido na pergunta.

jpmc26
fonte
2

O cliente consumidor não deve ter que lidar com um par de itens quando um único item é suficiente. No seu caso, eles quase não têm, até a invocação de CreateDataFile.

A solução potencial que você sugere é criar uma interface derivada combinada. No entanto, essa abordagem requer um único objeto que implemente as duas interfaces, o que é bastante restritivo, sem dúvida uma abstração com vazamento, na medida em que é basicamente customizada para uma implementação específica. Considere como seria complicado se alguém quisesse implementar as duas interfaces em objetos separados: eles precisariam usar proxy de todos os métodos em uma das interfaces para encaminhar para o outro objeto. (FWIW, outra opção é apenas mesclar as interfaces, em vez de exigir que um objeto precise implementar duas interfaces por meio de uma interface derivada.)

No entanto, outra abordagem menos restritiva para a implementação é a IFileAccessemparelhamento com uma IMeasurercomposição, de modo que uma delas esteja vinculada e faça referência à outra. (Isso aumenta um pouco a abstração de um deles, pois agora também representa o emparelhamento.) Então, CreateDataFilepoderia pegar apenas uma das referências, por exemplo IFileAccess, e ainda obter a outra, conforme necessário. Sua implementação atual como um objeto que implementa ambas as interfaces seria simplesmente return this;para a referência de composição, aqui o getter para IMeasurerin IFileAccess.

Se o emparelhamento for falso em algum momento do desenvolvimento, ou seja, às vezes um medidor diferente é usado com o mesmo acesso a arquivos, você pode fazer o mesmo emparelhamento, mas em um nível mais alto, o que significa que a interface adicional introduzida não seja uma interface derivada, mas uma interface que tenha dois getters, combinando um acesso a um arquivo e um medidor juntos por meio da composição, e não da derivação. O cliente consumidor, então, tem um item para se preocupar enquanto o emparelhamento é mantido e objetos individuais para lidar (para compor novos pares) quando necessário.


Em outra nota, eu poderia perguntar quem é o dono CreateDataFile, e a pergunta vai para quem é esse terceiro. Já temos algum cliente consumidor que chama CreateDataFile, o objeto / classe proprietário de CreateDataFilee o IFileAccesse IMeasurer. Às vezes, quando adotamos uma visão mais ampla do contexto, organizações alternativas, às vezes melhores, podem aparecer. Difícil de fazer aqui, uma vez que o contexto é incompleto, portanto, basta pensar.

Erik Eidt
fonte
0

Alguns levantaram que CreateDataFileestá fazendo muito. Eu poderia sugerir que, em vez disso, Boardestá fazendo muito, pois o acesso a um arquivo parece uma preocupação separada do restante do quadro.

No entanto, se assumirmos que isso não é um erro, o maior problema é que a interface deve ser definida pelo cliente, neste caso CreateDataFile.

O Princípio de Segregação de Interface afirma que o cliente não deve depender mais de uma interface do que o necessário. Tomando emprestada a frase dessa outra resposta , isso pode ser parafraseado como "uma interface é definida pelo que o cliente precisa".

Agora, é possível compor essa interface específica do cliente usando IFileAccesse IMeasurercomo outras respostas sugerem, mas, em última análise, esse cliente deve ter uma interface personalizada para isso.

Xtros
fonte
@ Downownoter - O que está errado ou pode ser melhorado?
Xtros 5/02/19