Um bom quarto de século atrás, quando eu estava aprendendo C ++, fui ensinado que as interfaces deveriam perdoar e, na medida do possível, não se importar com a ordem em que os métodos foram chamados, já que o consumidor pode não ter acesso à fonte ou documentação em vez de esta.
No entanto, sempre que mentorizei programadores juniores e desenvolvedores seniores, eles reagiram com espanto, o que me fez pensar se isso era realmente uma coisa ou se estava fora de moda.
Tão claro quanto a lama?
Considere uma interface com estes métodos (para criar arquivos de dados):
OpenFile
SetHeaderString
WriteDataLine
SetTrailerString
CloseFile
Agora, é claro que você pode simplesmente passar por eles em ordem, mas diga que não se importa com o nome do arquivo (pense a.out
) ou com o cabeçalho e a sequência do trailer incluídos, basta ligar AddDataLine
.
Um exemplo menos extremo pode ser a omissão de cabeçalhos e trailers.
Ainda outro pode estar definindo as seqüências de cabeçalho e trailer antes que o arquivo seja aberto.
Esse é um princípio de design de interface que é reconhecido ou apenas o caminho POLA antes de receber um nome?
OBS: não fique atolado nas minúcias dessa interface, é apenas um exemplo para o bem desta pergunta.
fonte
Respostas:
Uma maneira de se manter fiel ao princípio de menor espanto é considerar outros princípios, como ISP e SRP , ou mesmo DRY .
No exemplo específico que você deu, a sugestão parece ser a de que existe uma certa dependência de ordem para manipular o arquivo; mas sua API controla o acesso ao arquivo e o formato dos dados, que cheira um pouco a uma violação do SRP.
Editar / Atualizar: também sugere que a própria API está solicitando que o usuário viole o DRY, porque precisará repetir as mesmas etapas sempre que usar a API .
Considere uma API alternativa em que as operações de E / S sejam separadas das operações de dados. e onde a própria API 'possui' a ordem:
ContentBuilder
FileWriter
Com a separação acima,
ContentBuilder
não é necessário "fazer" nada além de armazenar as linhas / cabeçalho / trailer (talvez também umContentBuilder.Serialize()
método que conheça a ordem). Seguindo outros princípios do SOLID, não importa mais se você define o cabeçalho ou o trailer antes ou depois da adição de linhas, porque nadaContentBuilder
é gravado no arquivo até que seja passado para eleFileWriter.Write
.Ele também tem o benefício adicional de ser um pouco mais flexível; por exemplo, pode ser útil gravar o conteúdo em um criador de logs de diagnóstico ou talvez passá-lo pela rede em vez de gravá-lo diretamente em um arquivo.
Ao projetar uma API, você também deve considerar o relatório de erros, seja um estado, um valor de retorno, uma exceção, um retorno de chamada ou outra coisa. O usuário da API provavelmente espera detectar programaticamente quaisquer violações de seus contratos ou até outros erros que não pode controlar, como erros de E / S de arquivos.
fonte
SetHeader
ou sejaAddLine
importante. Para eliminar essa dependência de ordem não é ISP nem SRP, é simplesmente POLA.FileWriter
poderia exigir o valor da últimaContentBuilder
etapa doWrite
método para garantir que todo o conteúdo de entrada seja concluído, tornandoInvalidContentException
desnecessário.ContentBuilder
e permitirFileWriter.Write
encapsular esse pouco de conhecimento. A exceção seria necessária caso algo estivesse errado com o conteúdo (por exemplo, como um cabeçalho ausente). Um retorno também pode funcionar, mas não sou fã de transformar exceções em códigos de retorno.Não se trata apenas de POLA, mas também de impedir o estado inválido como uma possível fonte de erros.
Vamos ver como podemos fornecer algumas restrições ao seu exemplo sem fornecer uma implementação concreta:
Primeiro passo: não permita que nada seja chamado antes de um arquivo ser aberto.
Agora deve ser óbvio que
CreateDataFileInterface.OpenFile
deve ser chamado para recuperar umaDataFileInterface
instância em que os dados reais podem ser gravados.Segunda etapa: verifique se os cabeçalhos e os trailers estão sempre definidos.
Agora você deve fornecer todos os parâmetros necessários antecipadamente para obter
DataFileInterface
: nome do arquivo, cabeçalho e trailer. Se a sequência de trailer não estiver disponível até que todas as linhas sejam gravadas, você também poderá mover esse parâmetro paraClose()
(possivelmente renomeando o método paraWriteTrailerAndClose()
) para que o arquivo pelo menos não possa ser concluído sem uma sequência de trailer.Para responder ao comentário:
Verdade. Não queria me concentrar mais no exemplo do que o necessário para expressar minha opinião, mas é uma boa pergunta. Nesse caso, acho que chamaria isso
Finalize(trailer)
e argumentaria que não faz muito. Escrever o trailer e fechar são meros detalhes de implementação. Mas se você não concorda ou tem uma situação semelhante em que é diferente, aqui está uma solução possível:Na verdade, eu não faria isso neste exemplo, mas mostra como realizar a técnica consequentemente.
A propósito, presumi que os métodos realmente devam ser chamados nessa ordem, por exemplo, para escrever sequencialmente muitas linhas. Se isso não for necessário, eu sempre preferiria um construtor, como sugerido por Ben Cottrel .
fonte
WriteTrailerAndClose()
) está próxima de uma violação do SRP. (Isso é algo com o qual lutei várias vezes, mas sua sugestão parece ser um exemplo possível.) Como você reagiria?OpenFile
sobrecarga que não exija uma.