C # não tem nenhuma classe de amigo - quais são as melhores opções

5

Eu volto a essa pergunta a cada dois anos, então agora decidi resolvê-lo de uma vez por todas, perguntando aqui.

Então, a sequência:

  1. Estou escrevendo um aplicativo simples que analisa o arquivo Json (configuração para o aplicativo de terceiros, também chamado de config ) e permite que o usuário faça alguma edição com ele. Escolhi uma plataforma, fiz algumas interfaces de usuário básicas, liguei alguns botões a NewFileCommand, OpenFileCommand, SaveFileCommand, etc.
  2. Agora preciso armazenar em algum lugar a configuração aberta (e desserializada em uma classe), portanto, criei uma nova classe StorageService. Ele tem alguns métodos como OpenFile (), ReloadFile (), SaveFile (), etc. Eles são chamados no ViewModel, StorageService abre o arquivo, lê a configuração e armazena como um campo . Quando necessário, ele salva em um arquivo - até aí tudo bem.
  3. Neste momento, quero adicionar algumas manipulações com a própria configuração. Eu realmente não quero colocá-lo no StorageService. Alguns métodos como GetConfigPropertyByNameAndDoSomeMagicWithIt () definitivamente devem ser colocados em outro serviço.
  4. Então, eu fiz outro serviço, OperationsService. Agora eu quero que ele tenha um acesso à configuração, armazenado em StorageService e adicione métodos GetConfigPropertyByNameAndDoSomeMagicWithIt (). Eu não quero expor o StorageService.config na API pública - porque eu já tenho todos os acessadores necessários. Para todos os outros, exceto do OperationsService. Que eu praticamente quero fazer o único, que pode acessar StorageService.config. Parece um bom caso de uso para o bom e antigo conceito C ++ da classe "friend", em que OperationsService pode ser declarado como amigo do StorageService e acessar seus componentes internos.
  5. Estou lembrando que já enfrentei um problema semelhante anteriormente, então pesquisei por "C # friendly class", aberta o primeiro link na esperança de encontrar uma solução moderna - e percebi que eu tenho um sentimento déjà vu.

Então, o que é um fluxo na minha abordagem atual? Como resolvê-lo na "arquitetura moderna apropriada de OOP"?
Tornar OperationsService uma classe interna de StorageService? Isso significaria que todo o código seria armazenado no mesmo arquivo (praticamente violação do SOLID). Tornar OperationsService uma classe interna de StorageService e torná-lo parcial e passar para outro arquivo? Parece meio assustador.

Em geral, nem quero que o OperationsService seja exposto (nem mesmo para registrá-lo no contêiner do IoC) para o restante do aplicativo - apenas para fazer o trabalho pesado. OperationsService não seria tão grande, então eu não quero fazer um terceiro (algum tipo de serviço de wrapper) também.

Em termos de objetos, eu posso reformular o problema como "o objeto A tem um campo privado, que deve ser acessível apenas para o objeto B. A responsabilidade do objeto é IO, a responsabilidade do objeto B é lógica, então eles não podem ser mesclados".

Para resumir, temos:

  • config, que é POCO e contém dados desserializados do Json.
  • StorageService, que contém OpenFile (), SaveFile (). Além disso, contém a instância atual da configuração.
  • OperationsService teórico, que contém alguma lógica em GetConfigPropertyByNameAndDoSomeMagicWithIt (). Para executar essa lógica, também é necessário ter acesso à configuração. A questão é como relacionar OperationsService e StorageService.
por Vitalii Vasylenko 08.08.2018 / 23:04
fonte

4 respostas

1

Uma maneira de divulgar OperationsService sobre o restante do sistema em relação a dados específicos é transmiti-los diretamente para uma base de necessidade: Você pode manter sua lógica de manipulação de configuração dentro de OperationsService e adicionar um público AcceptOperations() method em StorageService que usa um objeto OperationsService como um arg e invoca sua lógica de manipulação de configuração relevante, passando diretamente para ele quaisquer dados de configuração relevantes de propriedade privada.

Concretamente, você ainda pode ter GetConfigPropertyByNameAndDoSomeMagicWithIt() como membro de OperationsService , que chamaria AcceptOperations() na instância StorageService relevante e passaria this como um argumento. AcceptOperations() chamaria algum método ManipulateConfig() concreto na instância OperationsService dada a ele como um parâmetro, passando os dados de configuração internos para serem manipulados como argumentos para ManipulateConfig() .

Esta proposta leva em parte algumas semelhanças com o padrão Padrão de visitante do OOP, excluindo o Double Dispatch idioma, mas mantendo o conceito de definir restritivamente quem pode visitar o que e definitivamente de acordo com a idéia original por trás do padrão que é realizar tal separação de responsabilidades tal que "... [fornece] a capacidade de adicionar novas operações para estruturas de objetos existentes sem modificar as estruturas. " Toda a idéia de tal padrão é ajudar a seguir o Princípio aberto-fechado , que é um dos princípios da SOLID, cuja manutenção você mencionou como uma preocupação sua para começar.

    
por 09.08.2018 / 00:58
fonte
5

Você pode colocar as classes que deseja que sejam amigáveis entre si em uma montagem separada. Em seguida, declare apenas os que você deseja disponibilizar fora da assembléia como público . Os que você deseja ocultar das classes em outras montagens declara interno . Isso deve lhe dar praticamente o que você quer.

Do ponto de vista da POO, isso é melhor do que amigo, já que o amigo permite o acesso além dos membros públicos, o que é inerentemente contra-OOP.

    
por 09.08.2018 / 00:03
fonte
1

Os objetos devem manipular seu estado interno e devem estar se modificando conforme necessário. Eu acho que você extraiu seus objetos incorretamente. Storage Service é realmente um gerenciador de persistência e não deve conter valores de configuração como valores internos. O serviço de armazenamento deve gerenciar apenas a conversão de arquivo para objeto e vice-versa. então você pode ter um objeto de configuração que tenha todos os métodos que fazem coisas com valores de configuração ou expor, ou uma classe de gerenciador de configuração que aceite um objeto de configuração na criação e exponha os métodos de acesso necessários.

O motivo pelo qual você está tendo problemas é que Storage Service está atualmente violando o princípio da responsabilidade única. Torna-se mais evidente quando você está tentando quebrá-lo ainda mais com GetConfigPropertyByNameAndDoSomeMagicWithIt() . A solução adequada em tal caso seria refatorar objetos com responsabilidades melhor definidas. amigo é uma solução de band-aid para um design pobre.

    
por 09.08.2018 / 14:30
fonte
0

Acho que o que está faltando é IConfigurationService, uma versão concreta da qual depende do IConfigStorageService. Você poderia ter LocalConfigStorageService, que pega a configuração de um arquivo ... ou S3ConfigStorageService, que é recuperado de um bucket de nuvem. Ou até mesmo DatabaseConfigStorageService, que o coleta de um banco de dados. Nos três casos, o IConfigStorageService é responsável por recuperar uma configuração e armazenar a atualização na configuração do IConfigurationService.

O IConfigurationService usa a configuração retornada para responder às solicitações dos consumidores.

Isso é especificamente uma instância do Padrão de ponte

    
por 09.08.2018 / 18:15
fonte