Eu tenho um método que cria um arquivo de dados depois de conversar com uma placa digital:
CreateDataFile(IFileAccess boardFileAccess, IMeasurer boardMeasurer)
Aqui boardFileAccess
e boardMeasurer
estão a mesma instância de um Board
objeto que implementa ambos IFileAccess
e 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
. Board
está localizado em um projeto separado.
Cheguei à conclusão de que CreateDataFile
uma 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 IDataFileCreator
que se estenda IFileAccess
e, em IMeasurer
seguida, tenha uma implementação contendo uma Board
instância que chamará apenas os Board
mé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?
fonte
Respostas:
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.
fonte
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:
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.
fonte
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:
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:
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:Seu tipo de medição pode ser apenas algo simples, como um
string
oudecimal
. 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: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:
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
CreateDataFile
método se torna apenas um atalho paraNotavelmente, 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
Board
diretamente na classe: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
IFileAccess
interface deve saber sobre o tipo de medição e como serializá-la? Nesse caso, você pode adicionar um método paraIFileAccess
:Agora os chamadores apenas fazem o seguinte:
que é tão curto e provavelmente mais claro que o seu método de conveniência, conforme concebido na pergunta.
fonte
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
IFileAccess
emparelhamento com umaIMeasurer
composiçã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,CreateDataFile
poderia pegar apenas uma das referências, por exemploIFileAccess
, e ainda obter a outra, conforme necessário. Sua implementação atual como um objeto que implementa ambas as interfaces seria simplesmentereturn this;
para a referência de composição, aqui o getter paraIMeasurer
inIFileAccess
.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 chamaCreateDataFile
, o objeto / classe proprietário deCreateDataFile
e oIFileAccess
eIMeasurer
. À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.fonte
Alguns levantaram que
CreateDataFile
está fazendo muito. Eu poderia sugerir que, em vez disso,Board
está 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
IFileAccess
eIMeasurer
como outras respostas sugerem, mas, em última análise, esse cliente deve ter uma interface personalizada para isso.fonte