Estou trabalhando em um projeto que lida com dispositivos físicos e estou confuso sobre como nomear corretamente algumas classes neste projeto.
Considerando que os dispositivos reais (sensores e receptores) são uma coisa, e sua representação no software é outra, estou pensando em nomear algumas classes com o padrão de nome de sufixo "Info".
Por exemplo, enquanto a Sensor
seria uma classe para representar o sensor real (quando está realmente conectado a algum dispositivo em funcionamento), SensorInfo
seria usado para representar apenas as características desse sensor. Por exemplo, ao salvar o arquivo, eu serializaria SensorInfo
a no cabeçalho do arquivo, em vez de serializar a Sensor
, o que nem faria sentido.
Mas agora estou confuso, porque existe um campo intermediário no ciclo de vida dos objetos em que não consigo decidir se devo usar um ou outro, ou como obter um do outro, ou mesmo se as duas variantes devem realmente ser reduzidas a apenas uma classe.
Além disso, a Employee
classe de exemplo muito comum obviamente é apenas uma representação da pessoa real, mas ninguém sugeriria o nome da classe EmployeeInfo
, tanto quanto eu sei.
A linguagem com a qual estou trabalhando é .NET e esse padrão de nomenclatura parece ser comum em toda a estrutura, por exemplo, com estas classes:
Directory
eDirectoryInfo
aulas;File
eFileInfo
aulas;ConnectionInfo
classe (semConnection
classe correspondente );DeviceInfo
classe (semDevice
classe correspondente );
Portanto, minha pergunta é: existe uma lógica comum sobre o uso desse padrão de nomenclatura? Existem casos em que faz sentido ter pares de nomes ( Thing
e ThingInfo
) e outros casos em que deve existir apenas a ThingInfo
classe ou a Thing
classe sem sua contraparte?
fonte
Info
sufixo distingue umastatic
classe que contém métodos utilitários de sua contraparte com estado. Não é uma "melhor prática" como tal; é apenas uma maneira que a equipe do .NET criou para resolver um problema específico. Eles poderiam ter apenas como facilmente chegar aFileUtility
eFile
, masFile.DoSomething()
eFileInfo.FileName
parecem ler melhor.Foo
, pode ter uma classe de utilitário não instanciadaFoos
. Quando se trata de nomear, o importante é a consistência na API e, idealmente, nas APIs da plataforma.Employee
exemplos são encontrados por dezenas, online ou em livros clássicos, enquanto eu ainda não o viEmployeeInfo
(talvez porque um funcionário seja um ser vivo, não uma construção técnica como uma conexão ou um arquivo). Mas, concordou, se a classeEmployeeInfo
fosse proposta em um projeto, acredito que poderia ser útil.Respostas:
Eu acho que "info" é um nome impróprio. Os objetos têm estado e ações: "info" é apenas outro nome para "state", que já está inserido no OOP.
O que você realmente está tentando modelar aqui? Você precisa de um objeto que represente o hardware do software para que outro código possa usá-lo.
É fácil dizer, mas como você descobriu, há mais do que isso. "Representar hardware" é surpreendentemente amplo. Um objeto que faz isso tem várias preocupações:
Certos dispositivos, como sensores, terão menos preocupações do que um dispositivo multifuncional para impressora / scanner / fax. Um sensor provavelmente produz apenas um fluxo de bits, enquanto um dispositivo complexo pode ter protocolos e interações complexos.
De qualquer forma, voltando à sua pergunta específica, existem várias maneiras de fazer isso, dependendo de seus requisitos específicos e da complexidade da interação do hardware.
Aqui está um exemplo de como eu projetaria a hierarquia de classes para um sensor de temperatura:
ITemperatureSource: interface que representa qualquer coisa que possa produzir dados de temperatura: um sensor, pode até ser um invólucro de arquivo ou dados codificados (pense em testes simulados).
Acme4680Sensor: sensor do modelo 4680 da ACME (ótimo para detectar quando o Roadrunner está próximo). Isso pode implementar várias interfaces: talvez esse sensor detecte temperatura e umidade. Este objeto contém um estado no nível do programa, como "o sensor está conectado?" e "qual foi a última leitura?"
Acme4680SensorComm: usado apenas para comunicação com o dispositivo físico. Não mantém muito estado. É usado para enviar e receber mensagens. Possui um método C # para cada uma das mensagens que o hardware entende.
HardwareManager: usado para obter dispositivos. Esta é essencialmente uma fábrica que armazena em cache instâncias: deve haver apenas uma instância de um objeto de dispositivo para cada dispositivo de hardware. É preciso ser inteligente o suficiente para saber que, se o encadeamento A solicita o sensor de temperatura ACME e o encadeamento B solicita o sensor de umidade ACME, esses objetos são realmente o mesmo objeto e devem ser retornados aos dois encadeamentos.
No nível superior, você terá interfaces para cada tipo de hardware. Eles descrevem as ações que seu código C # executaria nos dispositivos, usando tipos de dados C # (não por exemplo, matrizes de bytes que o driver de dispositivo bruto pode usar).
No mesmo nível, você tem uma classe de enumeração com uma instância para cada tipo de hardware. O sensor de temperatura pode ser de um tipo, o sensor de umidade, outro.
Um nível abaixo disso são as classes reais que implementam essas interfaces: elas representam um dispositivo semelhante ao Acme4680Sensor I descrito acima. Qualquer classe específica pode implementar várias interfaces se o dispositivo puder executar várias funções.
Cada classe de dispositivo tem sua própria classe Comm (comunicação) privada que lida com a tarefa de baixo nível de conversar com o hardware.
Fora do módulo de hardware, a única camada visível é a interface / enum mais o HardwareManager. A classe HardwareManager é a abstração de fábrica que lida com a instanciação de classes de dispositivos, instâncias de cache (você realmente não deseja duas classes de dispositivos conversando com o mesmo dispositivo de hardware), etc. Uma classe que precisa de um tipo específico de sensor solicita que o HardwareManager obtenha o dispositivo para a enum em particular, que ele descobre se já está instanciado, se não como criá-lo e inicializá-lo etc.
O objetivo aqui é dissociar a lógica de negócios da lógica de hardware de baixo nível. Quando você estiver escrevendo um código que imprime os dados do sensor na tela, esse código não deve se importar com o tipo de sensor que você possui, e apenas se esse desacoplamento estiver no local, centralizado nessas interfaces de hardware.
Nota: existem associações entre o HardwareManager e cada classe de dispositivo que eu não desenhei porque o diagrama teria se transformado em sopa de flechas.
fonte
Pode ser um pouco difícil encontrar uma única convenção unificadora aqui, porque essas classes estão espalhadas por vários espaços de nomes (
ConnectionInfo
parece estar dentroCrystalDecisions
eDeviceInfo
dentroSystem.Reporting.WebForms
).Olhando para esses exemplos, porém, parece haver dois usos distintos do sufixo:
Distinguindo uma classe que fornece métodos estáticos com uma classe que fornece métodos de instância. Este é o caso das
System.IO
classes, conforme sublinhado por suas descrições:Diretório :
DirectoryInfo :
Info
parece uma escolha um pouco estranha aqui, mas torna a diferença relativamente clara: umaDirectory
classe poderia razoavelmente representar um diretório específico ou fornecer métodos auxiliares gerais relacionados ao diretório sem manter nenhum estado, enquantoDirectoryInfo
só poderia realmente ser o primeiro.Enfatizando que a classe contém apenas informações e não fornece comportamento razoavelmente esperado do nome sem sufixo .
Eu acho que a última parte dessa frase pode ser a peça do quebra-cabeça que distingue, digamos,
ConnectionInfo
deEmployeeInfo
. Se eu tivesse uma classe chamadaConnection
, esperaria razoavelmente que ela realmente me fornecesse a funcionalidade que uma conexão possui - eu estaria procurando métodos comovoid Open()
etc. No entanto, ninguém em sã consciência esperaria que umaEmployee
classe pudesse realmente faça o que um realEmployee
faz ou procure métodos comovoid DoPaperwork()
oubool TryDiscreetlyBrowseFacebook()
.fonte
Em geral, um
Info
objeto encapsula informações sobre o estado de um objeto em algum momento no tempo . Se eu pedir ao sistema que examine um arquivo e me forneça umFileInfo
objeto associado ao seu tamanho, esperaria que esse objeto informasse o tamanho do arquivo no momento em que a solicitação foi fornecida (ou, para ser mais preciso, o tamanho de o arquivo em algum momento entre quando a chamada foi feita e quando ela retornou). Se o tamanho do arquivo mudar entre a hora em que a solicitação retorna e a hora em que oFileInfo
objeto é examinado, eu não esperaria que essa alteração se refletisse noFileInfo
objeto.Observe que esse comportamento seria muito diferente daquele de um
File
objeto. Se uma solicitação para abrir um arquivo de disco no modo não exclusivo produz umFile
objeto que possui umaSize
propriedade, eu esperaria que o valor retornado desse modo mudasse quando o tamanho do arquivo de disco mudar, pois oFile
objeto não representa apenas o estado de um arquivo - representa o próprio arquivo .Em muitos casos, os objetos anexados a um recurso devem ser limpos quando seus serviços não forem mais necessários. Como os
*Info
objetos não se anexam aos recursos, eles não requerem limpeza. Como conseqüência, nos casos em que umInfo
objeto satisfaz os requisitos de um cliente, pode ser melhor usar um código do que usar um objeto que represente o recurso subjacente, mas cuja conexão com esse recurso precisaria ser limpa.fonte
File
classe não pode ser instanciada e osFileInfo
objetos são atualizados com o sistema de arquivos subjacente.FileInfo
tinha apenas informações capturadas estaticamente. Não é? Além disso, nunca tive oportunidade de abrir arquivos no modo não exclusivo, mas deveria ser possível, e esperaria que exista um método que relate o tamanho atual de um arquivo, mesmo que o objeto usado para abrir não seja chamadaFile
(normalmente eu só usoReadAllBytes
,WriteAllBytes
,ReadAllText
,WriteAllText
, etc.).Eu não gosto dessa distinção. Todos os objetos são "representações [s] no software". É o que a palavra "objeto" significa.
Agora, pode fazer sentido separar informações sobre um periférico do código real que faz interface com o periférico. Assim, por exemplo, um Sensor possui um SensorInfo, que contém a maioria das variáveis de instância, juntamente com alguns métodos que não requerem hardware, enquanto a classe Sensor é responsável por interagir de fato com o sensor físico. Você não tem um, a
Sensor
menos que seu computador tenha um sensor, mas você poderia ter um plausívelSensorInfo
.O problema é que esse tipo de design pode ser generalizado para (quase) qualquer classe. Então você tem que ter cuidado. Você obviamente não teria
SensorInfoInfo
aula, por exemplo. E se você tem umaSensor
variável, pode se ver violando a lei de Demeter interagindo com seuSensorInfo
membro. Nada disso é fatal, é claro, mas o design da API não é apenas para autores de bibliotecas. Se você manter sua própria API limpa e simples, seu código será mais sustentável.Recursos de sistema de arquivos como diretórios, na minha opinião, estão muito próximos dessa borda. Há algumas situações em que você deseja descrever um diretório que não é acessível localmente, é verdade, mas o desenvolvedor médio provavelmente não está em uma dessas situações. Complicar a estrutura de classes dessa maneira é, em minha opinião, inútil. Contraste a abordagem do Python em
pathlib
: Há uma classe única que "provavelmente é o que você precisa" e várias classes auxiliares que a maioria dos desenvolvedores pode ignorar com segurança. Se você realmente precisar deles, no entanto, eles fornecem basicamente a mesma interface, apenas com métodos concretos removidos.fonte
SensorInfo
seria uma espécie de DTO e / ou também como um "instantâneo" ou mesmo uma "especificação", representando apenas a parte de dados / estado doSensor
objeto real que "pode nem estar lá".PureWindowsPath
funciona um pouco como um objeto Info, mas possui métodos para fazer coisas que não exigem um sistema Windows (por exemplo, pegar um subdiretório, separar uma extensão de arquivo etc.). Isso é mais útil do que apenas fornecer uma estrutura glorificada.Eu diria que o contexto / domínio é importante, pois temos código de lógica de negócios de alto nível e modelos de baixo nível, componentes de arquitetura e assim por diante ...
'Info', 'Dados', 'Gerente', 'Objeto', 'Classe', 'Modelo', 'Controlador' etc. podem ser sufixos malcheirosos, especialmente em um nível inferior, pois cada objeto tem alguma informação ou dados, portanto essa informação não é necessária.
Os nomes de classe do domínio comercial devem ser como todos os interessados falam sobre o assunto, não importa se ele soa estranho ou não é um idioma 100% correto.
Bons sufixos para estruturas de dados são, por exemplo, 'Lista', 'Mapa' e, para os padrões de sugestão 'Decorador', 'Adaptador', se você achar necessário.
No cenário do seu sensor, eu não esperaria
SensorInfo
salvar qual é o seu sensor, masSensorSpec
.Info
imho é mais uma informação derivada, comoFileInfo
é algo como o tamanho, você não salva ou o caminho do arquivo que é construído a partir do caminho e do nome do arquivo, etc.Outro ponto:
Isso sempre me lembra de pensar em um nome apenas por alguns segundos e, se não encontrei, uso nomes estranhos e marquei 'TODO'. Sempre posso alterá-lo mais tarde, pois meu IDE fornece suporte à refatoração. Não é um slogan da empresa que deve ser bom, mas apenas alguns códigos que podemos mudar sempre que quisermos. Tenha isso em mente.
fonte
O ThingInfo pode servir como um excelente Proxy somente leitura para o Thing.
consulte http://www.dofactory.com/net/proxy-design-pattern
Proxy: "Forneça um substituto ou espaço reservado para outro objeto para controlar o acesso a ele".
Normalmente, o ThingInfo terá propriedades públicas sem setters. Essas classes e os métodos da classe são seguros de usar e não confirmarão nenhuma alteração nos dados de backup, no objeto ou em qualquer outro objeto. Nenhuma mudança de estado ou outros efeitos colaterais ocorrerão. Eles podem ser usados para relatórios e serviços da Web ou em qualquer lugar em que você precise de informações sobre o objeto, mas queira limitar o acesso ao próprio objeto.
Use o ThingInfo sempre que possível e limite o uso do Thing real aos horários em que você realmente precisa alterar o objeto Thing. Isso torna a leitura e a depuração consideravelmente mais rápidas quando você se acostuma a usar esse padrão.
fonte
Receiver
que recebe fluxos de dados de muitosSensor
s. A idéia é: o receptor deve abstrair os sensores reais. Mas o problema é: preciso das informações do sensor para poder escrevê-las em algum cabeçalho do arquivo. A solução: cadaIReceiver
um terá uma lista deSensorInfo
. Se eu enviar comandos ao receptor que impliquem alteração do estado do sensor, essas alterações serão refletidas (via getter) no respectivoSensorInfo
.List<SensorInfo>
propriedade readonly.Até agora, ninguém nesta questão parece ter percebido o real motivo dessa convenção de nomes.
A
DirectoryInfo
não é o diretório. É um DTO com dados sobre o diretório. Pode haver muitas instâncias desse tipo descrevendo o mesmo diretório. Não é uma entidade. É um objeto de valor descartável. ADirectoryInfo
não representa o diretório real. Você também pode pensar nisso como um identificador ou controlador para um diretório.Compare isso com uma classe chamada
Employee
. Esse pode ser um objeto de entidade ORM e é o único objeto que descreve esse funcionário. Se fosse um objeto de valor sem identidade, deveria ser chamadoEmployeeInfo
. UmEmployee
realmente representa o funcionário real. Uma classe DTO semelhante a valor chamadaEmployeeInfo
claramente não representaria o funcionário, mas a descreveria ou armazenaria dados sobre ela.Na verdade, há um exemplo na BCL em que ambas as classes existem: A
ServiceController
é uma classe que descreve um serviço do Windows. Pode haver qualquer número desses controladores para cada serviço. UmaServiceBase
(ou uma classe derivada) é o serviço real e não faz sentido conceitualmente ter várias instâncias dele por serviço distinto.fonte
Proxy
padrão mencionado por @Shmoken, não é?EmployeeInfo
de um serviço da web. Isso não é um proxy. Exemplo adicional: UmAddressInfo
nem sequer tem algo que pode proxy porque um endereço não é uma entidade. É um valor independente.