Eu estava pensando sobre um bom design de classe orientada a objetos. Em particular, tenho dificuldade em decidir entre essas opções:
- método estático vs instância
- método sem parâmetros ou valor de retorno vs método com parâmetros e valor de retorno
- sobreposição vs funcionalidade de método distinto
- método privado versus público
Exemplo 1:
Essa implementação usa métodos de instância, sem valor de retorno ou parâmetros, sem funcionalidade de sobreposição e todos os métodos public
XmlReader reader = new XmlReader(url);
reader.openUrl();
reader.readXml();
Document result = reader.getDocument();
Exemplo 2:
Essa implementação usa métodos estáticos, com valores e parâmetros de retorno, com funcionalidade sobreposta e métodos privados
Document result = XmlReader.readXml(url);
No exemplo um, todos os métodos são de instância pública, o que os torna fáceis de testar na unidade. Embora todos os métodos sejam distintos, o readXml () depende de openUrl (), pois openUrl () deve ser chamado primeiro. Todos os dados são declarados nos campos da instância, portanto, não há valores ou parâmetros de retorno em nenhum método, exceto no construtor e nos acessadores.
No exemplo dois, apenas um método é público, o restante é estático privado, o que dificulta o teste de unidade. Os métodos estão sobrepostos, pois o readXml () chama openUrl (). Não há campos, todos os dados são passados como parâmetros nos métodos e o resultado é retornado imediatamente.
Quais princípios devo seguir para fazer uma programação orientada a objetos adequada?
fonte
Respostas:
O exemplo 2 é muito ruim para testes ... e não quero dizer que você não possa testar os internos. Você também não pode substituir seu
XmlReader
objeto por um objeto simulado, pois não possui nenhum objeto.O exemplo 1 é desnecessariamente difícil de usar. A respeito
que não é mais difícil de usar do que o seu método estático.
Coisas como abrir o URL, ler XML, converter bytes em strings, analisar, fechar soquetes e o que for, não são interessantes. Criar um objeto e usá-lo é importante.
Então, IMHO, o projeto OO adequado é tornar públicas apenas as duas coisas (a menos que você realmente precise das etapas intermediárias por algum motivo). Estática é má.
fonte
Document result = new XmlReader(url).getDocument();
Por que? Para que eu possa atualizá-loDocument result = whateverReader.getDocument();
e terwhateverReader
me entregue por outra coisa.Aqui está a questão: não existe uma resposta certa e não existe uma definição absoluta de "design orientado a objetos adequado" (algumas pessoas oferecem uma, mas são ingênuas ... dão tempo).
Tudo se resume aos seus objetivos.
Você é um artista e o papel está em branco. Você pode desenhar um retrato lateral em preto e branco delicado e com lápis de cor ou uma pintura abstrata com enormes cortes de néons mistos. Ou qualquer coisa no meio.
Então, o que parece certo para o problema que você está resolvendo? Quais são as reclamações das pessoas que precisam usar suas aulas para trabalhar com xml? O que é difícil no trabalho deles? Que tipo de código eles estão tentando escrever que envolve as chamadas para sua biblioteca e como você pode ajudar esse fluxo a melhorar para eles?
Eles gostariam de mais sucessão? Eles gostariam que fosse muito inteligente em descobrir os valores padrão dos parâmetros, para que não precisassem especificar muito (ou nada) e adivinhar corretamente? Você pode automatizar as tarefas de configuração e limpeza que sua biblioteca exige, para que seja impossível que elas esqueçam essas etapas? O que mais você pode fazer por eles?
Inferno, o que você provavelmente precisa fazer é codificá-lo de 4 ou 5 maneiras diferentes, colocar seu chapéu de consumidor e escrever um código que use todos os 5, e ver qual é o melhor. Se você não pode fazer isso para toda a sua biblioteca, faça-o para um subconjunto. E você também precisa adicionar algumas alternativas à sua lista - que tal uma interface fluente, uma abordagem mais funcional, parâmetros nomeados ou algo baseado no DynamicObject, para que você possa criar "pseudo-métodos" significativos que os ajudem Fora?
Por que o jQuery é o rei agora? Como Resig e equipe seguiram esse processo, até encontrar um princípio sintático que reduziu incrivelmente a quantidade de código JS necessária para trabalhar com o dom e os eventos. Esse princípio sintático não estava claro para eles ou para qualquer outra pessoa quando começaram. Eles a encontraram.
Como programador, é assim que você chama mais. Você procura no escuro tentando coisas até encontrá-lo. Quando fizer isso, você saberá. E você dará a seus usuários um enorme salto de produtividade. E é disso que trata o design (na área de software).
fonte
A segunda opção é melhor, pois é mais simples para as pessoas usarem (mesmo que seja apenas você), muito mais simples.
Para testes de unidade, basta testar a interface e não os internos, se você realmente deseja mover os internos de privado para protegido.
fonte
1. Estático versus instância
Eu acho que existem diretrizes muito claras sobre o que é bom design de OO e o que não é. O problema é que a blogosfera torna difícil separar o bom do ruim e o feio. Você pode encontrar algum tipo de referência que apóie até a pior prática possível.
E a pior prática que consigo pensar é no Estado Global, incluindo as estáticas que você mencionou e a Singleton favorita de todos. Alguns trechos do artigo clássico de Misko Hevery sobre o assunto .
O que isso se resume é que você não deve fornecer referências estáticas para qualquer coisa que possua algum tipo de estado armazenado. O único lugar em que uso a estática é para constantes enumeradas e tenho dúvidas sobre isso.
2. Métodos com parâmetros de entrada e valores de retorno vs. métodos sem nenhum
O que você precisa entender é que os métodos que não têm parâmetros de entrada e de saída são praticamente garantidos para operar em algum tipo de estado armazenado internamente (caso contrário, o que eles estão fazendo?). Existem idiomas inteiros criados com a idéia de evitar o estado armazenado.
Sempre que você tiver armazenado o estado, você tem a possibilidade de efeitos colaterais, portanto, sempre use-o com atenção. Isso implica que você deve preferir funções com entradas e / ou saídas definidas.
E, de fato, funções que definiram entradas e saídas são muito mais fáceis de testar - você não precisa executar uma função aqui e ir lá para ver o que aconteceu, e não precisa definir uma propriedade em algum lugar mais antes de executar a função em teste.
Você também pode usar com segurança esse tipo de função como estática. No entanto, eu não o faria, porque, se mais tarde eu quisesse usar uma implementação ligeiramente diferente dessa função em algum lugar, em vez de fornecer uma instância diferente com a nova implementação, ficaria preso sem nenhuma maneira de substituir a funcionalidade.
3. Sobreposição vs. Distinto
Eu não entendo a pergunta. Qual seria a vantagem em 2 métodos sobrepostos?
4. Privado x Público
Não exponha nada que não precise expor. No entanto, também não sou muito fã de privado. Não sou desenvolvedor de C #, mas desenvolvedor de ActionScript. Passei muito tempo no código Flex Framework da Adobe, que foi escrito por volta de 2007. E eles fizeram algumas escolhas muito ruins sobre o que tornar privado, o que torna um pesadelo tentar estender suas Classes.
Portanto, a menos que você pense que é um arquiteto melhor do que os desenvolvedores da Adobe em 2007 (da sua pergunta, eu diria que você tem mais alguns anos antes de ter a chance de fazer essa afirmação), provavelmente deseja apenas usar o padrão protegido .
Existem alguns problemas com seus exemplos de código, o que significa que eles não são bem arquitetados, portanto, não é possível escolher A ou B.
Por um lado, você provavelmente deve separar sua criação de objeto do seu uso . Então você normalmente não tem o seu
new XMLReader()
direito próximo ao local onde é usado.Além disso, como o @djna diz, você deve encapsular os métodos usados no XML Reader, para que sua API (exemplo de instância) possa ser simplificada para:
Não sei como o C # funciona, mas, como já trabalhei com várias tecnologias da Web, desconfio que você nem sempre poderá retornar um documento XML imediatamente (exceto, talvez, como promessa ou tipo futuro). objeto), mas não posso dar conselhos sobre como lidar com uma carga assíncrona em c #.
Observe que, com essa abordagem, você pode criar várias implementações que podem usar um parâmetro que diz onde / o que ler e retornar um objeto XML e trocá-los com base nas necessidades do seu projeto. Por exemplo, você pode estar lendo diretamente de um banco de dados, de uma loja local ou, como no exemplo original, de um URL. Você não pode fazer isso se usar um método estático.
fonte
Foco na perspectiva do cliente.
Os métodos estáticos tendem a limitar a evolução futura, nosso cliente está trabalhando em termos de uma interface fornecida por possivelmente muitas implementações diferentes.
O principal problema com o seu idioma de leitura / abertura é que o cliente precisa saber a ordem na qual chamar métodos, quando ele quer apenas fazer um trabalho simples. Óbvio aqui, mas em uma classe maior está longe de ser óbvio.
O método principal para testar é read (). Os métodos internos podem ser tornados visíveis para testar programas, tornando-os nem públicos nem privados e colocando testes no mesmo pacote - os testes ainda podem ser mantidos separados do código liberado.
fonte
Na prática, você encontrará métodos estáticos geralmente confinados a classes de utilidade e não devem sobrecarregar seus objetos de domínio, gerentes, controladores ou DAOs. Os métodos estáticos são mais úteis quando todas as referências necessárias podem ser passadas razoavelmente como parâmetros e oferecem algumas funcionalidades que podem ser reutilizadas em muitas classes. Se você se encontrar usando um método estático como uma solução alternativa para ter uma referência a um objeto de instância, pergunte-se por que você simplesmente não tem essa referência.
Se você não precisar de parâmetros no método, não os adicione. O mesmo acontece com o valor de retorno. Manter isso em mente simplificará seu código e garantirá que você não esteja codificando para uma infinidade de cenários que nunca acabam acontecendo.
É uma boa idéia tentar evitar a sobreposição de funcionalidades. Às vezes, pode ser difícil, mas quando é necessária uma alteração na lógica, é muito mais fácil alterar um método que é reutilizado do que alterar vários métodos com funcionalidade semelhante
Em geral, getters, setters e construtores devem ser públicos. Tudo o resto você desejará tentar manter em sigilo, a menos que haja um caso em que outra classe precise executá-la. Manter os métodos padronizados como privados ajudará a manter o encapsulamento . O mesmo vale para os campos, acostume-se ao privado como padrão
fonte
Não responderei à sua pergunta, mas acho que um problema foi criado pelos termos que você usou. Por exemplo
Eu acho que você precisa de um XML, por isso vou criar um objeto XML que pode ser criado a partir de um tipo de texto (não sei C # ... em Java, ele é chamado String). Por exemplo
Você pode testá-lo e está claro. Então, se você precisar de uma fábrica, poderá adicionar um método de fábrica (e pode ser estático como no seu segundo exemplo). Por exemplo
fonte
Você pode seguir algumas regras simples. Ajuda se você entender os motivos das regras.
método estático vs instância
Para métodos, você não precisa tomar essa decisão conscientemente. Se parecer que seu método não está usando nenhum membro do campo (seu analisador favorito deve informar isso), você deve adicionar a palavra-chave estática.
método sem parâmetros ou valor de retorno vs método com parâmetros e valor de retorno
A segunda opção é melhor por causa do escopo. Você deve sempre manter seu escopo apertado. Chegar às coisas de que você precisa é ruim, você deve ter sugestões, trabalhar localmente e retornar um resultado. Sua primeira opção é ruim pelo mesmo motivo que variáveis globais geralmente são ruins: elas são significativas para apenas uma parte do seu código, mas são visíveis (e, portanto, ruídos) em qualquer outro lugar e podem ser alteradas de qualquer lugar. Isso dificulta a obtenção de uma imagem completa da sua lógica.
sobreposição vs funcionalidade de método distinto
Eu não acho que isso seja um problema. Métodos que chamam outros métodos são bons se isso dividir tarefas em pequenos pedaços distintamente funcionais.
método privado versus público
Torne tudo privado, a menos que você precise que ele seja público. O usuário da sua classe pode ficar sem o barulho, ele quer ver apenas o que importa para ele.
fonte