E se os globais fizerem sentido?

10

Eu tenho um valor que muitos objetos precisam. Por exemplo, um aplicativo financeiro com diferentes investimentos como objetos, e a maioria deles precisa da taxa de juros atual.

Eu esperava encapsular meu "ambiente financeiro" como um objeto, com a taxa de juros como uma propriedade. Porém, objetos irmãos que precisam desse valor não conseguem alcançá-lo.

Então, como compartilho valores entre muitos objetos sem sobrecarregar meu design? Obviamente, estou pensando nisso errado.

Greg
fonte
2
A taxa de juros é fixa para a duração de seus cálculos ou você está fazendo algo parecido com uma simulação em que pode variar entre os intervalos de tempo?
James
É mais como uma simulação - pode mudar durante a execução.
Greg
Nesse caso, cada investimento realmente precisa salvar a taxa de juros ou pode recebê-la por meio de um parâmetro em uma updatefunção chamada a cada passo do tempo? Você pode postar em pseudocódigo como sua simulação opera?
James
3
Você é o caminho certo, a Singletoné um mundo com açúcar sintático de OO e é uma solução terrível que une firmemente seu código das piores formas possíveis. Leia este artigo várias vezes até que você o entenda!
A taxa de juros é como uma série temporal, que é uma função que recebe DateTimecomo entrada e retorna um número como saída.
Rdong

Respostas:

14

Eu tenho um valor que muitos objetos precisam.

Este é um cheiro de design. É incomum que muitos objetos precisem saber sobre algo. Dito isto, a taxa de juros atual é um bom exemplo de circunstâncias excepcionais. Uma coisa que se preocupar é que raramente há a taxa de juros. Instrumentos financeiros diferentes usam taxas diferentes. No mínimo, localidades diferentes usam taxas 'padrão' diferentes. Além disso, para ajudar nos testes e nos relatórios, geralmente você deseja aprovar uma taxa, já que não deseja usar a taxa atual . Você deseja usar a taxa 'e se' ou 'na data do relatório'.

Então, como compartilho valores entre muitos objetos sem sobrecarregar meu design?

Ao compartilhá-los, não tê-los todos se refere a uma única instância. Passar a mesma coisa ainda é um acoplamento, até certo ponto, mas não muito, já que algo como a taxa de juros atual é necessária como entrada para uma variedade de cálculos.

Telastyn
fonte
17
O problema em circunstâncias excepcionais é que, no software do mundo real, elas não são tão excepcionais.
mattnz
Eu acho que isso é um sério mal-entendido. Nossos algoritmos existem em diferentes contextos simultaneamente. O primeiro é o contexto global. Então "este" contexto para linguagens orientadas a objetos. Contexto da sessão em um serviço da web, contexto de transação em um ambiente de banco de dados, janela principal para uma GUI, ... e há informações que pertencem a esses contextos. Eles devem "ficar por aqui" e estar disponíveis, o mesmo conjunto (objetos) para qualquer pessoa no mesmo contexto. Esta certo. O problema é resolver isso para cada objeto, não criando um serviço de contexto ou usando uma estrutura, como o Spring em Java.
Lorand Kedves
3
Não há nada de excepcional na taxa de juros atual. Existem muitos exemplos de itens do mundo real com um valor, - Limite de velocidade em estrada aberta, nível aceitável de álcool no sangue, taxa de imposto GST (ou IVA), entre outros. Essa é a diferença entre Ciência e Engenharia - os engenheiros resolvem os problemas do mundo real de hoje, os cientistas sonham com o dia em que o mundo real se encaixará em boas caixas de perfeição e resolverá esses problemas.
mattnz
11
Eu escolhi isso como a resposta, porque é simples e não depende de uma década de experiência em OOP para grocar. MUITO obrigado a todos os entrevistados. Eu tive um dia inteiro de leitura graças às muitas referências, mas continuo um pouco perplexo. Para uma pergunta tão simples, fiquei surpreso com a variedade e a emoção por trás das respostas. Continuo convencido de que, às vezes, existe uma fonte central de dados globais, mas variáveis, que é melhor atendida por um Singleton. Eu não acho que se deva passar ponteiros para cima e para baixo em uma hierarquia de objetos apenas para evitar um Singleton. Mais uma vez obrigado a todos.
Greg
@mattnz, o problema é que cada um de seus exemplos é variável nos casos em que você distribui seu programa para várias bases de usuários, que podem abranger empresas, estados ou países. Todos eles também podem ser variáveis ​​ao longo do tempo.
Dan Lyons
10

Nesse caso em particular, eu usaria o padrão Singleton . O FinancialEnvironment seria o objeto que todas as outras bibliotecas de classes conhecem, mas seria instanciado pelo Singleton. Idealmente, você enviaria esse objeto instanciado para as várias bibliotecas de classes.

Por exemplo:

  • Camada de serviço (biblioteca de classes) - Instancia o objeto FinancialEnvironment por meio de um singleton
  • Camada lógica de negócios (biblioteca de classes) - Aceita o objeto FinancialEnvironment da camada de serviço
  • Camada de acesso a dados (biblioteca de classes) - Aceita o objeto FinancialEnvironment da camada de serviço (ou, dependendo da sua arquitetura, a camada lógica de negócios). Ou talvez o Singleton chame a Data Access Layer para obter informações, como taxa de juros, de um repositório (banco de dados / serviço da web / serviço WCF).
  • Biblioteca de classes de entidades (ou DTOs, se você quiser chamar assim) - Onde o objeto FinancialEnvironment vive. Todas as outras bibliotecas de classes têm uma referência à biblioteca de classes Entities.

As outras classes são unidas apenas através da biblioteca de classes Entities, elas aceitam um objeto FinancialEnvironment instanciado. Eles não se importam como foi criado, apenas a camada de serviço, tudo o que eles querem é a informação. O singleton também pode ser inteligente o suficiente para armazenar vários objetos FinancialEnvironment, dependendo das regras para o local, como @Telastyn apontou.

Em uma nota lateral, eu não sou um grande fã do Padrão Singleton, considero um cheiro de código, pois pode ser mal utilizado com muita facilidade. Mas, em alguns casos, você precisa disso.

Atualizar:

Se você absolutamente, positivamente deve ter uma variável global, a implementação do Padrão Singleton, conforme descrito acima, funcionaria. No entanto, não sou um grande fã disso e, com base nos comentários do meu post original, várias outras pessoas também não são. Como algo tão volátil como uma Taxa de juros, um Singleton pode não ser a melhor solução. Singletons funcionam melhor quando a informação não muda. Por exemplo, usei um Singleton em um dos meus aplicativos para instanciar contadores de desempenho. Porque se eles mudarem, você deverá ter uma lógica para lidar com os dados que estão sendo atualizados.

Se eu fosse um apostador, apostaria que a taxa de juros foi armazenada em algum lugar do banco de dados ou recuperada por meio de um serviço da web. Nesse caso, um Repositório (camada de acesso a dados) seria recomendado para recuperar essas informações. Para evitar viagens desnecessárias ao banco de dados (não sei com que frequência as taxas de juros são alteradas ou outras informações na classe FinancialInformation), o cache pode ser usado. No mundo C #, a biblioteca Caching Application Block da Microsoft funciona muito bem.

A única coisa que mudaria a partir do exemplo acima, seriam as várias classes na camada de serviço que precisam que a FinancialInformation recuperasse da Camada de Acesso a Dados, em vez de o Singleton instanciar o objeto.

bwalk2895
fonte
8
Ugh. Tornar o global um singleton não o torna menos fedorento. Se alguma coisa você se restringiu muito mais.
Telastyn
3
@DavidCowden, não importa se eles não são complexos de implementar; eles fubar seu design pior do que os globais. Eles são globais e impõem restrições (desnecessárias) de que você só tem uma.
Telastyn
4
Eu ia postar um comentário sarcástico dizendo "torná-lo um singleton e passará das práticas recomendadas para as melhores práticas", mas depois vi que já era uma resposta aceita e votada. Muito agradável!
Kevin
4
Singletoné uma empresa global com açúcar sintático OO e uma muleta para os preguiçosos e de mente fraca. Singleton/globalé a pior maneira absoluta de associar firmemente seu código a algo que será um câncer mais tarde, quando você perceber que idéia colossalmente ruim era e por que todo mundo diz que é!
4
@Telastyn: É uma realidade lamentável que os projetos mais perfeitos, uma vez que eles deixem o mundo perfeitamente ordenado do design de software teórico e se juntem ao mundo real, sejam enganados.
mattnz
4

Arquivos de configuração?

Se você possui valores usados ​​"globalmente", coloque-os em um arquivo de configuração. Então cada sistema e subsistema pode fazer referência a isso e puxar as chaves necessárias, torná-las somente leitura.

Noite escura
fonte
Para que o usuário atualize um arquivo de configuração toda vez que a taxa de juros mudar?
Caleb
2
Por que não? depende da "variável", é claro que as coisas que mudam frequentemente devem ser colocadas dentro de um armazenamento de dados CENTRALIZADO.
Darknight
1

Estou falando da experiência de quem tem cerca de um mês de manutenção em um projeto de bom tamanho (~ 50k LOC) que acabamos de lançar.

Posso lhe dizer que você provavelmente não quer realmente um objeto global. A introdução desse tipo de lixo fornece muito mais oportunidades de abuso do que ajuda.

Minha sugestão inicial é que, se você tem várias classes diferentes que precisam de uma taxa de juros atual, provavelmente deseja que elas implementem um IInterestRateConsumerou algo assim. Dentro dessa interface, você terá uma SetCurrentInterestRate(double rate)(ou o que fizer sentido), ou talvez apenas uma propriedade.

Passar uma taxa de juros não é realmente um acoplamento - se sua classe precisa de uma taxa de juros, isso faz parte da API. É apenas um acoplamento se uma de suas classes começar a se preocupar exatamente com como a outra classe usa essa taxa de juros.

Wayne Werner
fonte
Passar uma taxa de juros ao redor é acoplamento, simplesmente não é um acoplamento ruim .
vaughandroid
1

Martin Fowler tem um artigo que fala brevemente sobre como refatorar uma global estática para algo mais flexível. Basicamente, você o transforma em um singleton e modifica o singleton para que ele suporte a substituição da classe da instância por uma subclasse (e, se necessário, mova a lógica que cria a instância para uma classe separada que pode ser subclassificada, o que você faria se criar a instância de superclasse, substituí-la mais tarde for um problema).

Obviamente, você precisa pesar os problemas com singletons (até singletons substituíveis) versus a dor de passar o mesmo objeto em qualquer lugar.

No que diz respeito ao objeto "ambiente financeiro" - é conveniente programar na primeira passagem, mas ao concluir, você adicionou algumas dependências extras. As classes que agora precisam apenas de uma taxa de juros só funcionam quando passam por um objeto do ambiente financeiro, o que dificulta sua reutilização quando não houver um objeto do ambiente financeiro. Então eu desencorajaria a aprovação amplamente.

psr
fonte
0

Por que não colocar os dados da taxa de juros em um cache central?

Você pode usar uma das várias bibliotecas de cache, o que melhor se adequar aos seus requisitos, algo como o memcached resolve todos os seus problemas de simultaneidade e gerenciamento de código e permitirá que seu aplicativo seja escalado para vários processos.

Ou vá até o porco e armazene-os em um banco de dados, o que permitirá que você dimensione para vários servidores.

James Anderson
fonte
0

Em tais situações, introduzi (reutilizei) o termo "contexto" com êxito, às vezes com várias camadas.

Isso significa um singleton, portanto, um armazenamento de objetos "global", do qual esses tipos de objetos podem ser solicitados. Os códigos que os exigem, incluem o cabeçalho da loja e usam as funções globais para obter suas instâncias de objeto (como agora, o provedor de taxa de juros).

A loja pode ser:

  • estritamente digitado: você inclui os cabeçalhos de todos os tipos atendidos e pode criar acessadores digitados, como InterestRate getCurrentInterestRate ();
  • ou genérico: Objeto getObject (enum obType); e somente estenda a enum obType com os novos tipos (obtypeCurrentInterestRate).

Quanto maior o sistema, mais utilizável é a última solução, com um risco bem pequeno de usar a enumeração errada. Por outro lado, com idiomas que permitem declarações de tipo encaminhamento, acho que você pode usar acessadores digitados sem incluir todos os cabeçalhos da loja.

Mais uma observação: você pode ter várias instâncias do mesmo tipo de objeto para usos diferentes, como valor de idioma às vezes diferente para a GUI e para registros de impressão, globais e no nível da sessão, etc. , mas a função da instância solicitada (CurrentInterestRate).

Na implementação da loja, você precisa gerenciar os níveis de contexto e as coleções de instâncias de contexto. Um exemplo simples é o serviço web, onde você tem o contexto global (uma instância para todas as solicitações para esse objeto - problemático ao ter um farm de servidores) e um contexto para cada sessão da web. Você também pode ter contextos para cada usuário, que pode ter várias sessões paralelas, etc. Com vários servidores, você deve usar um tipo de cache distribuído para essas coisas.

Quando a solicitação é recebida, você decide qual nível de contexto é o objeto solicitado e obtém esse contexto para a chamada. Se o objeto estiver lá, você o envia de volta; caso contrário, crie e armazene-o nesse nível de contexto e retorne-o. Obviamente, sincronize a seção de criação (e publique-a no cache distribuído). A criação pode ser configurável como plug-in, melhor com linguagens que permitem criar instâncias de objetos pelo nome da classe (Java, Objective C, ...), mas você pode fazer isso em C, bem como em bibliotecas conectáveis ​​com funções de fábrica.

Nota lateral: o chamador NÃO deve saber muito sobre seus próprios contextos e o nível de contexto do objeto solicitado. Razões: 1: é fácil cometer erros (ou "truques inteligentes") jogando com esses parâmetros; 2: o nível de contexto do solicitado pode mudar mais tarde. Eu conecto principalmente informações de contexto ao encadeamento, para que o armazenamento de objetos tenha as informações sem parâmetros extras da solicitação.

Por outro lado, a solicitação pode conter dicas para a instância: como obter taxa de juros para uma data específica. Deve ser o mesmo acesso "global", mas várias instâncias, dependendo da data (e levando valores diferentes de data para a mesma instância entre alterações de taxa), portanto, é recomendável adicionar um objeto "dica" à solicitação, usado pelo fábrica de instância e não a loja; e um keyForHint para a fábrica, usado pela loja. Você pode adicionar essas funções mais tarde, apenas mencionei.

Para o seu caso, isso é um tipo de exagero (apenas um objeto é servido no nível global), mas para um código extra bem pequeno e simples no momento, você obtém um mecanismo para requisitos adicionais, talvez mais complexos.

Outra boa notícia: se você está em Java, obtém esse serviço do Spring sem pensar muito, só queria explicar em detalhes.

Lorand Kedves
fonte
0

O motivo para NÃO usar um global (ou singleton) é que, embora inicialmente você espere ter apenas um valor, muitas vezes é surpreendentemente útil poder usar o mesmo código várias vezes no mesmo programa, por exemplo:

  • para calcular o que aconteceria se a taxa de juros fosse diferente
  • ter alguns componentes dependem da taxa de juros dos EUA e alguns componentes dependem da taxa de juros do Reino Unido

Eu faria da taxa de juros um membro da classe "instrumento financeiro" e aceitaria que você a passasse para qualquer classe de membro (por cálculo ou dando a eles um ponteiro / gancho na construção).

Jack V.
fonte
0

As coisas devem ser passadas em mensagens, não lidas de uma coisa global.

Tulains Córdova
fonte