Percebi que quase toda vez que vejo programadores usando classes estáticas em linguagens orientadas a objetos como C #, eles estão fazendo errado. Obviamente, os principais problemas são o estado global e a dificuldade de trocar implementações em tempo de execução ou com zombarias / stubs durante os testes.
Por curiosidade, observei alguns dos meus projetos selecionando aqueles que foram bem testados e quando fiz um esforço para realmente pensar em arquitetura e design. Os dois usos das classes estáticas que encontrei são:
A classe de utilitário - algo que eu preferiria evitar se estivesse escrevendo esse código hoje,
O cache do aplicativo: usar a classe estática para isso é claramente errado. E se eu quiser substituí-lo por
MemoryCache
ou Redis?
Olhando para o .NET Framework, também não vejo nenhum exemplo de uso válido de classes estáticas. Por exemplo:
File
classe estática torna realmente doloroso mudar para alternativas. Por exemplo, e se alguém precisar alternar para Armazenamento Isolado ou armazenar arquivos diretamente na memória ou precisar de um provedor que suporte transações NTFS ou, pelo menos, consiga lidar com caminhos com mais de 259 caracteres ? Ter uma interface comum e várias implementações parece tão fácil quanto usarFile
diretamente, com o benefício de não precisar reescrever a maior parte do código base quando os requisitos mudam.Console
A classe estática torna os testes excessivamente complicados. Como garantir dentro de um teste de unidade que um método produz dados corretos para o console? Devo modificar o método para enviar a saída para um gravávelStream
que é injetado em tempo de execução? Parece que uma classe de console não estática seria tão simples quanto uma classe estática, apenas sem todas as desvantagens de uma classe estática, mais uma vez.Math
classe estática também não é um bom candidato; e se eu precisar mudar para a aritmética de precisão arbitrária? Ou para alguma implementação mais nova e mais rápida? Ou a um esboço que dirá, durante um teste de unidade, que um determinado método de umaMath
classe foi realmente chamado durante um teste?
No Programmer.SE, eu li:
Não use "Static" em C #? Algumas respostas são totalmente contra as classes estáticas. Outros afirmam que “os métodos estáticos são bons de usar e têm um lugar de direito na programação.”, Mas não os envolvam com argumentos.
Quando usar um Singleton e quando usar uma classe estática . As classes de utilidade são mencionadas, dado que eu mencionei acima, as classes de utilidade são problemáticas.
Por que e quando devo tornar a classe 'estática'? Qual é o objetivo da palavra-chave 'estática' nas classes? O único uso válido mencionado é um contêiner para métodos de extensão. Ótimo.
Além dos métodos de extensão, quais são os usos válidos das classes estáticas, ou seja, casos em que a Injeção de Dependência ou os singletons são impossíveis ou resultarão em um design de qualidade inferior e maior extensibilidade e manutenção?
fonte
instanceof
) porque, dadas duas instâncias,IFoo
não há garantia de que elas sejam apoiadas pela mesma classe.YAGNI
, e aceita que, se precisar de um tipo diferente de string, fará com que seu IDE mude todas as instâncias decom.lang.lib.String
paracom.someones.elses.String
em toda a base de código.Respostas:
YAGNI
A sério. Se alguém quiser usar implementações diferentes de
File
ouMath
ouConsole
então, poderá passar pela dor de abstrair isso. Você já viu / usou Java?!? As bibliotecas padrão Java são um bom exemplo do que acontece quando você abstrai as coisas por uma questão de abstração. Eu realmente preciso seguir três etapas para criar meu objeto Calendário ?Tudo isso dito, não vou me sentar aqui e defender fortemente as classes estáticas. O estado estático é totalmente ruim (mesmo que eu ainda o use ocasionalmente para agrupar / armazenar em cache coisas). As funções estáticas podem ser ruins devido a problemas de simultaneidade e testabilidade.
Mas funções estáticas puras não são tão terríveis. Não são coisas elementares que não fazem sentido para fora abstrato vez que não há nenhuma alternativa sadia para a operação. Não são implementações de uma estratégia que pode ser estático, porque eles já estão captada através do delegado / functor. E, às vezes, essas funções não têm uma classe de instância a ser associada; portanto, classes estáticas são boas para ajudar a organizá-las.
Além disso, os métodos de extensão são um uso prático , mesmo que não sejam classes filosoficamente estáticas.
fonte
Console
realmente não deve fazer parte do estado do sistema, mas também não deve fazer parte de um objeto que é repassado constantemente. Em vez disso, parece que faria mais sentido dizer que cada thread tem uma propriedade "console atual" que pode ser facilmente salva e restaurada se for necessário ter uma rotina que grava paraConsole
usar outra coisa.Em uma palavra simplicidade.
Quando você se dissocia demais, você recebe o olá mundo do inferno.
Mas ei, pelo menos não há configuração XML, o que é um toque agradável.
A classe estática permite otimizar o caso rápido e comum. A maioria dos pontos mencionados pode ser resolvida com muita facilidade, introduzindo sua própria abstração sobre eles ou usando coisas como Console.Out.
fonte
O caso dos métodos estáticos:
é muito mais simples, mais claro e mais legível que isso:
Agora adicione injeção de dependência para ocultar as instanciações explícitas e veja o one-liner crescer em dezenas ou centenas de linhas - tudo para dar suporte ao cenário improvável de você inventar sua própria
Max
implementação, que é significativamente mais rápida que a do framework e você precisa para poder alternar de forma transparente entre asMax
implementações alternativas .O design da API é uma troca entre simplicidade e flexibilidade. Você sempre pode introduzir camadas adicionais de indireção ( ad infinitum ), mas é necessário ponderar a conveniência do caso comum contra o benefício de camadas adicionais.
Por exemplo, você sempre pode gravar seu próprio wrapper de instância em torno da API do sistema de arquivos se precisar alternar de maneira transparente entre os provedores de armazenamento. Mas sem os métodos estáticos mais simples, todos seriam forçados a criar uma instância sempre que precisassem fazer algo simples como salvar um arquivo. Seria realmente uma troca ruim de design otimizar para um caso extremo extremamente raro e tornar tudo mais complicado para o caso comum. O mesmo ocorre com
Console
- você pode criar facilmente seu próprio wrapper se precisar abstrair ou zombar do console, mas muitas vezes você usa o console para soluções rápidas ou para fins de depuração, para que você queira apenas a maneira mais simples possível de produzir algum texto.Além disso, embora "uma interface comum com várias implementações" seja legal, não há necessariamente uma abstração "correta" que unifique todos os provedores de armazenamento que você deseja. Você pode alternar entre arquivos e armazenamento isolado, mas outra pessoa pode alternar entre arquivos, banco de dados e cookies para armazenamento. A interface comum pareceria diferente e é impossível prever todas as abstrações possíveis que os usuários possam desejar. É muito mais flexível fornecer métodos estáticos como "primitivos" e permitir que os usuários construam suas próprias abstrações e invólucros.
Seu
Math
exemplo é ainda mais dúbio. Se você deseja alterar para uma precisão de precisão arbitrária, presumivelmente precisará de novos tipos numéricos. Esta seria uma mudança fundamental de todo você programa e ter que mudarMath.Max()
paraArbitraryPrecisionMath.Max()
é certamente menos do que você se preocupa.Dito isto, concordo que as classes estáticas com estado são, na maioria dos casos, más.
fonte
Em geral, os únicos lugares onde eu usaria classes estáticas são aqueles em que a classe hospeda funções puras que não cruzam os limites do sistema. Sei que o último é redundante, pois qualquer coisa que atravessa um limite provavelmente não é pura intenção. Eu chego a essa conclusão tanto pela desconfiança do estado global quanto pela facilidade de testar a perspectiva. Mesmo onde há uma expectativa razoável de que haverá uma única implementação para uma classe que ultrapasse os limites do sistema (rede, sistema de arquivos, dispositivo de E / S), eu escolho usar instâncias em vez de classes estáticas, porque a capacidade de fornecer uma simulação A implementação / fake nos testes de unidade é fundamental no desenvolvimento de testes de unidade rápidos, sem acoplamento a dispositivos reais. Nos casos em que o método é uma função pura, sem acoplamento a um sistema / dispositivo externo, acho que uma classe estática pode ser apropriada, mas apenas se não prejudicar a testabilidade. Acho que os casos de uso são relativamente raros fora das classes de estrutura existentes. Além disso, pode haver casos em que você não tem escolha - por exemplo, métodos de extensão em C #.
fonte