Eu acho que essa pergunta deve se aplicar à maioria dos programas que carregam configurações de um arquivo. Minha pergunta é do ponto de vista de programação, e é realmente como lidar com o carregamento de configurações de um arquivo em termos de diferentes classes e acessibilidade. Por exemplo:
- Se um programa tivesse um
settings.ini
arquivo simples , seu conteúdo deveria ser carregado noload()
método de uma classe, ou talvez no construtor? - Os valores devem ser armazenados em
public static
variáveis ou deve haverstatic
métodos para obter e definir propriedades? - O que deve acontecer no caso de o arquivo não existir ou não ser legível? Como você deixaria o resto do programa saber que não pode obter essas propriedades?
- etc.
Espero estar perguntando isso no lugar certo aqui. Eu queria tornar a pergunta o mais independente possível da linguagem, mas estou focada principalmente em linguagens que possuem coisas como herança - especialmente Java e C # .NET.
Respostas:
Essa é realmente uma pergunta realmente importante e geralmente é feita de maneira errada, pois não recebe importância suficiente, mesmo sendo parte essencial de praticamente todos os aplicativos. Aqui estão minhas diretrizes:
Sua classe de configuração, que contém todas as configurações, deve ser apenas um tipo de dados antigo simples, struct / class:
Ele não precisa ter métodos e não deve envolver herança (a menos que seja a única opção que você tem no seu idioma para implementar um campo variante - veja o próximo parágrafo). Ele pode e deve usar a composição para agrupar as configurações em classes de configuração específicas menores (por exemplo, subConfig acima). Se você fizer isso dessa maneira, será ideal repassar os testes de unidade e o aplicativo em geral, pois terá dependências mínimas.
Você provavelmente precisará usar tipos de variantes, caso as configurações para diferentes configurações sejam heterogêneas na estrutura. É aceito que você precisará inserir uma conversão dinâmica em algum momento quando ler o valor para convertê-lo na (sub) classe de configuração correta, e sem dúvida isso dependerá de outra configuração.
Você não deve ter preguiça de digitar todas as configurações como campos, apenas fazendo o seguinte:
Isso é tentador, pois significa que você pode escrever uma classe de serialização generalizada que não precisa saber com quais campos está lidando, mas está errada e vou explicar o porquê em um momento.
A serialização da configuração é feita em uma classe completamente separada. Qualquer que seja a API ou biblioteca usada para fazer isso, o corpo da sua função de serialização deve conter entradas que basicamente equivalem a ser um mapa do caminho / chave no arquivo para o campo no objeto. Alguns idiomas fornecem boa introspecção e podem fazer isso imediatamente, outros você precisará escrever explicitamente o mapeamento, mas o principal é que você deve escrever o mapeamento apenas uma vez. Por exemplo, considere este extrato que eu adaptei da documentação do analisador de opções do programa boost do c ++:
Observe que a última linha basicamente diz "otimização" mapeia para Config :: opt e também que há uma declaração do tipo que você espera. Você deseja que a leitura da configuração falhe se o tipo não for o que você espera, se o parâmetro no arquivo não for realmente um float ou um int, ou se não existir. Ou seja, a falha deve ocorrer quando você lê o arquivo porque o problema está no formato / validação do arquivo e você deve lançar um código de exceção / retorno e relatar o problema exato. Você não deve atrasar isso para mais tarde no programa. É por isso que você não deve ser tentado a pegar todos os Confs estilo dicionário, como mencionado acima, que não falharão quando o arquivo for lido - pois a conversão é atrasada até que o valor seja necessário.
Você deve tornar a classe Config somente leitura de alguma maneira - definindo o conteúdo da classe uma vez ao criá-la e inicializá-la do arquivo. Se você precisar ter configurações dinâmicas em seu aplicativo que mudem, assim como as constantes que não, você deve ter uma classe separada para lidar com as dinâmicas, em vez de tentar permitir que os bits da sua classe de configuração não sejam somente leitura .
Idealmente, você lê o arquivo em um local do seu programa, ou seja, você só tem uma instância de um "
ConfigReader
". No entanto, se você estiver enfrentando dificuldades para passar a instância do Config para onde você precisa, é melhor ter um segundo ConfigReader do que introduzir uma configuração global (o que eu acho que é o que o OP quer dizer com "static "), O que me leva ao meu próximo ponto:Evite a música de sirene sedutora do singleton: "Vou evitar que você passe nessa aula, todos os seus construtores serão adoráveis e limpos. Continue, será tão fácil". A verdade é que com uma arquitetura testável bem projetada, você dificilmente precisará passar a classe Config, ou partes dela, através de muitas classes de seu aplicativo. O que você encontrará, em sua classe de nível superior, sua função main () ou o que quer que seja, desvendará o conf em valores individuais, que você fornecerá às classes de componentes como argumentos que serão reunidos novamente (dependência manual injeção). Um conf único / global / estático tornará o teste de unidade muito mais difícil de implementar e entender seu aplicativo - por exemplo, confundirá novos desenvolvedores para sua equipe que não saberão que precisam definir o estado global para testar coisas.
Se seu idioma suportar propriedades, você deverá usá-las para esse fim. O motivo é que significa que será muito fácil adicionar definições de configuração "derivadas" que dependem de uma ou mais configurações. por exemplo
Se o seu idioma não suportar nativamente o idioma da propriedade, pode haver uma solução alternativa para obter o mesmo efeito, ou você simplesmente cria uma classe de wrapper que fornece as configurações de bônus. Se você não pode conferir o benefício das propriedades, é uma perda de tempo escrever manualmente e usar getters / setters simplesmente com o objetivo de agradar a algum deus OO. Você ficará melhor com um campo antigo simples.
Pode ser necessário um sistema para mesclar e obter várias configurações de locais diferentes em ordem de precedência. Essa ordem de precedência deve ser bem definida e compreendida por todos os desenvolvedores / usuários, por exemplo, considere o registro do Windows HKEY_CURRENT_USER / HKEY_LOCAL_MACHINE. Você deve fazer esse estilo funcional para manter suas configurações somente leitura, ou seja:
ao invés de:
Finalmente, devo acrescentar que, é claro, se a estrutura / linguagem escolhida fornecer seus próprios mecanismos de configuração conhecidos e incorporados, você deverá considerar os benefícios de usá-lo em vez de usar o seu próprio.
Então. Muitos aspectos a serem considerados - acerte e isso afetará profundamente a arquitetura do aplicativo, reduzindo bugs, facilitando o teste de coisas e forçando você a usar um bom design em outro lugar.
fonte
.ini
para que seja legível por humanos, mas você está sugerindo que eu serialize uma classe com as variáveis?Em geral (na minha opinião), é melhor deixar o aplicativo lidar com como a configuração é armazenada e passar a configuração para seus módulos. Isso permite flexibilidade na maneira como as configurações são salvas, para que você possa direcionar arquivos ou serviços da web, bancos de dados ou ...
Além disso, coloca o ônus do "o que acontece quando as coisas falham" no aplicativo, quem sabe melhor o que essa falha significa.
E isso facilita muito o teste de unidade quando você pode apenas passar em um objeto de configuração em vez de precisar tocar no sistema de arquivos ou lidar com problemas de simultaneidade introduzidos pelo acesso estático.
fonte
Se você estiver usando o .NET para programar as classes, terá opções diferentes, como Resources, web.config ou mesmo um arquivo personalizado.
Se você usar Resources ou web.config, os dados serão realmente armazenados no arquivo XML, mas o carregamento será mais rápido.
Recuperar os dados desses arquivos e armazená-los em outro local será como um uso duplo da memória, pois estes são carregados na memória por padrão.
Para qualquer outro arquivo ou linguagem de programação, a resposta acima de Benedict funcionará.
fonte