Ao projetar uma classe, a consistência no comportamento deve ser preferida à prática comum de programação? Para dar um exemplo específico:
Uma convenção comum é a seguinte: se uma classe possui um objeto (por exemplo, ele o criou), é responsável por limpá-lo quando terminar. Um exemplo específico seria no .NET que, se sua classe possuir um IDisposable
objeto, ele será descartado no final de sua vida útil. E se você não o possui, não o toque.
Agora, se olharmos para a StreamWriter
classe no .NET, podemos encontrar na documentação que ela fecha o fluxo subjacente quando está sendo fechada / descartada. Isso é necessário nos casos em que o StreamWriter
instanciado passa um nome de arquivo, pois o escritor cria o fluxo de arquivos subjacente e, portanto, precisa fechá-lo. No entanto, também é possível passar em um fluxo externo que o gravador também fecha.
Isso me incomodou muitas vezes (sim, eu sei que você pode criar um invólucro sem fechamento, mas esse não é o ponto), mas aparentemente a Microsoft tomou a decisão de que é mais consistente sempre fechar o fluxo, não importa de onde ele veio.
Quando encontro esse padrão em uma de minhas classes, geralmente crio um ownsFooBar
sinalizador que é definido como falso nos casos em que FooBar
é injetado pelo construtor e verdadeiro caso contrário. Dessa forma, a responsabilidade de limpá-lo é passada ao chamador quando ele passa a instância explicitamente.
Agora, estou me perguntando se talvez a consistência deva ser favorável às melhores práticas (ou talvez minha melhor prática não seja tão boa)? Algum argumento a favor / contra?
Editar para esclarecimentos
Com "consistência", quero dizer: o comportamento consistente da classe sempre tomando posse (e fechando o fluxo) versus "prática recomendada" para tomar posse de um objeto apenas se você o criou ou transferiu explicitamente a propriedade.
Como um exemplo em que é irritante:
Suponha que você tenha duas classes (de alguma biblioteca de terceiros) que aceitam um fluxo para fazer algo com ele, como criar e processar alguns dados:
public class DataProcessor
{
public Result ProcessData(Stream input)
{
using (var reader = new StreamReader(input))
{
...
}
}
}
public class DataSource
{
public void GetData(Stream output)
{
using (var writer = new StreamWriter(output))
{
....
}
}
}
Agora eu quero usá-lo assim:
Result ProcessSomething(DataSource source)
{
var processor = new DataProcessor();
...
var ms = new MemoryStream();
source.GetData(ms);
return processor.ProcessData(ms);
}
Isso falhará com uma exceção Cannot access a closed stream
no processador de dados. É um pouco construído, mas deve ilustrar o ponto. Existem várias maneiras de corrigi-lo, mas, no entanto, sinto que trabalho em torno de algo que não deveria.
Respostas:
Também uso essa técnica, chamo de 'transferência' e acredito que mereça o status de um padrão. Quando um objeto A aceita um objeto descartável B como um parâmetro de tempo de construção, ele também aceita um booleano chamado 'handoff' (que o padrão é false) e, se for verdadeiro, o descarte de A cascata para o descarte de B.
Eu não sou a favor da proliferação das escolhas infelizes de outras pessoas, por isso nunca aceitaria as más práticas da Microsoft como uma convenção estabelecida, nem consideraria o seguimento cego das escolhas infelizes das outras pessoas como "comportamento consistente" de qualquer forma, forma ou forma .
fonte
handOff
padrão, se essa propriedade for configurada no construtor, mas existir outro método para modificá-la, esse outro método também deverá aceitar umhandOff
sinalizador. SehandOff
foi especificado para o item atualmente em espera, ele deve ser descartado, o novo item deve ser aceito e ohandOff
sinalizador interno atualizado para refletir o status do novo item. O método não precisa verificar as referências antigas e novas quanto à igualdade, pois se a referência original foi "transferida" todas as referências mantidas pelo chamador original devem ser abandonadas.Dispose
solicitações forem ignoradas para os pincéis do sistema, um método "color.CreateBrush` poderá, à sua vontade, criar um novo pincel (que exigiria descarte) ou retornar um pincel do sistema (que ignoraria o descarte). A maneira mais simples de evitar reutilização de um objeto se ele se preocupa com disposição, mas permitir a reutilização se isso não acontecer, é para eliminá-lo.Eu acho que você está certo, para ser honesto. Eu acho que a Microsoft mexeu com a classe StreamWriter, especificamente, pelos motivos que você descreve.
No entanto, já vi muitos códigos em que as pessoas nem tentam descartar seu próprio fluxo porque a estrutura faz isso por elas, portanto, corrigi-lo agora fará mais mal do que bem.
fonte
Eu iria com consistência. Como você mencionou para a maioria das pessoas, faz sentido que, se seu objeto criar um objeto filho, ele será o proprietário e o destruirá. Se for fornecida uma referência externa, em algum momento, "externa" também está segurando o mesmo objeto e não deve ser de propriedade. Se você deseja transferir a propriedade de fora para o seu objeto, use os métodos Anexar / Desanexar ou um construtor que aceite um booleano que deixe claro que a propriedade é transferida.
Depois de digitar tudo isso para obter uma resposta completa, acho que a maioria dos padrões de design genéricos não necessariamente se enquadram na mesma categoria que as classes StreamWriter / StreamReader. Eu sempre pensei que a razão pela qual a MS escolheu o StreamWriter / Reader automaticamente assumir a propriedade é porque, neste caso específico, ter propriedade compartilhada não faz muito sentido. Você não pode ter dois objetos diferentes simultaneamente lendo / gravando no mesmo fluxo. Tenho certeza de que você poderia escrever algum tipo de divisão de tempo determinística, mas isso seria um inferno de um antipadrão. Então é provavelmente por isso que eles disseram, já que não faz sentido compartilhar um fluxo, vamos sempre assumi-lo. Mas eu não consideraria esse comportamento como uma convenção genérica em todas as classes.
Você pode considerar o Streams um padrão consistente, onde o objeto que está sendo transmitido não pode ser compartilhado do ponto de vista do design e, portanto, sem nenhum sinalizador adicional, o leitor / gravador apenas assume sua propriedade.
fonte
Seja cuidadoso. O que você considera "consistência" pode ser apenas uma coleção aleatória de hábitos.
"Coordenando interfaces de usuário para consistência", Boletim SIGCHI 20, (1989), 63-65. Desculpe, sem link, é um artigo antigo. "... um workshop de dois dias com 15 especialistas não conseguiu produzir uma definição de consistência". Sim, trata-se de "interface", mas acredito que se você pensar em "consistência" por um tempo, descobrirá que também não pode defini-lo.
fonte