Por exemplo, uma classe geralmente possui membros e métodos, por exemplo:
public class Cat{
private String name;
private int weight;
private Image image;
public void printInfo(){
System.out.println("Name:"+this.name+",weight:"+this.weight);
}
public void draw(){
//some draw code which uses this.image
}
}
Porém, depois de ler sobre o princípio de responsabilidade única e o princípio aberto aberto, prefiro separar uma classe no DTO e a classe auxiliar apenas com métodos estáticos, por exemplo:
public class CatData{
public String name;
public int weight;
public Image image;
}
public class CatMethods{
public static void printInfo(Cat cat){
System.out.println("Name:"+cat.name+",weight:"+cat.weight);
}
public static void draw(Cat cat){
//some draw code which uses cat.image
}
}
Eu acho que se encaixa no princípio de responsabilidade única, porque agora a responsabilidade do CatData é manter apenas os dados, não se importa com os métodos (também para o CatMethods). E também se encaixa no princípio aberto fechado, porque adicionar novos métodos não precisa alterar a classe CatData.
Minha pergunta é: é um bom ou um antipadrão?
object-oriented
static-methods
dto
ocomfd
fonte
fonte
Respostas:
Você mostrou dois extremos ("todos os métodos privados e todos (talvez não relacionados) em um objeto" versus "tudo público e nenhum método dentro do objeto"). IMHO boa modelagem OO é nenhum deles , o ponto ideal está em algum lugar no meio.
Um teste decisivo de quais métodos ou lógicas pertencem a uma classe e o que pertence a fora é examinar as dependências que os métodos introduzirão. Métodos que não introduzem dependências adicionais são bons, desde que se ajustem bem à abstração do objeto especificado . Métodos que exigem dependências externas adicionais (como uma biblioteca de desenhos ou uma biblioteca de E / S) raramente são um bom ajuste. Mesmo que você desapareça as dependências usando a injeção de dependência, ainda pensaria duas vezes se realmente for necessário colocar esses métodos na classe de domínio.
Portanto, nem você deve tornar todos os membros públicos, nem precisa implementar métodos para todas as operações em um objeto dentro da classe. Aqui está uma sugestão alternativa:
Agora, o
Cat
objeto fornece lógica suficiente para permitir que o código circundante seja facilmente implementadoprintInfo
edraw
, sem expor todos os atributos em público. O lugar certo para esses dois métodos provavelmente não é uma classe divinaCatMethods
(desdeprintInfo
edraw
provavelmente são preocupações diferentes, então eu acho que é muito improvável que eles pertençam à mesma classe).Eu posso imaginar um
CatDrawingController
que implementadraw
(e talvez use injeção de dependência para obter um objeto Canvas). Também posso imaginar outra classe que implementa alguma saída e uso do consolegetInfo
(portanto, issoprintInfo
pode se tornar obsoleto nesse contexto). Mas, para tomar decisões sensatas sobre isso, é preciso conhecer o contexto e como aCat
classe será realmente usada.Essa é realmente a maneira como interpretei os críticos do Modelo de Domínio Anêmico de Fowler - para uma lógica geralmente reutilizável (sem dependências externas), as próprias classes de domínio são um bom lugar, portanto devem ser usadas para isso. Mas isso não significa implementar nenhuma lógica lá, muito pelo contrário.
Observe também que o exemplo acima ainda deixa espaço para tomar uma decisão sobre (im) mutabilidade. Se a
Cat
classe não expor nenhum setter eImage
for imutável, esse design permitirá tornarCat
imutável (o que a abordagem do DTO não fará). Mas se você acha que a imutabilidade não é necessária ou não é útil para o seu caso, você também pode ir nessa direção.fonte
getInfo()
pode ser enganoso para alguém que faz essa pergunta. Ele sutilmente mistura responsabilidades de apresentação comoprintInfo()
faz, derrotando a finalidade deDrawingController
classeDrawingController
. Eu concordo com issogetInfo()
eprintInfo()
são nomes horríveis. ConsideretoString()
eprintTo(Stream stream)
Resposta tardia, mas não consigo resistir.
Na maioria dos casos, a maioria das regras, aplicadas sem pensar, na maioria das vezes dá terrivelmente errado (incluindo esta).
Deixe-me contar uma história sobre o nascimento de um objeto em meio ao caos de algum código processual, rápido e sujo, que aconteceu, não por design, mas por desespero.
Meu estagiário e eu somos uma programação em pares para criar rapidamente algum código descartável para raspar uma página da web. Não temos absolutamente nenhuma razão para esperar que esse código continue por muito tempo, então estamos apenas produzindo algo que funcione. Agarramos a página inteira como um barbante e cortamos as coisas de que precisamos da maneira mais incrível que você possa imaginar. Não julgue. Funciona.
Agora, ao fazer isso, criei alguns métodos estáticos para cortar. Meu estagiário criou uma classe de DTO muito parecida com a sua
CatData
.Quando olhei pela primeira vez para o DTO, ele me incomodou. Os anos de dano que Java causou ao meu cérebro me fizeram recuar nos campos públicos. Mas estávamos trabalhando em c #. O C # não precisa de getters e setters prematuros para preservar seu direito de tornar os dados imutáveis ou encapsulados posteriormente. Sem alterar a interface, você pode adicioná-los sempre que quiser. Talvez apenas para que você possa definir um ponto de interrupção. Tudo sem contar a seus clientes nada sobre isso. Sim C #. Boo Java.
Então eu segurei minha língua. Eu assisti enquanto ele usava meus métodos estáticos para inicializar essa coisa antes de usá-la. Tivemos cerca de 14 deles. Era feio, mas não tínhamos motivos para nos importar.
Então precisávamos disso em outros lugares. Nos descobrimos querendo copiar e colar o código. 14 linhas de inicialização sendo lançadas. Estava começando a ficar doloroso. Ele hesitou e me pediu idéias.
Relutantemente, perguntei: "você consideraria um objeto?"
Ele olhou para o DTO e torceu o rosto, confuso. "É um objeto".
"Quero dizer um objeto real"
"Hã?"
"Deixe-me mostrar uma coisa. Você decide se é útil"
Eu escolhi um novo nome e rapidamente criei algo que se parecia um pouco com isso:
Isso não fez nada que os métodos estáticos já não estavam fazendo. Mas agora eu havia sugado os 14 métodos estáticos para uma classe em que eles poderiam ficar sozinhos com os dados em que trabalhavam.
Não forcei meu estagiário a usá-lo. Eu apenas o ofereci e deixei que ele decidisse se queria seguir os métodos estáticos. Fui para casa pensando que ele provavelmente seguiria o que já tinha trabalhado. No dia seguinte, descobri que ele o estava usando em vários lugares. Organizou o restante do código, que ainda era feio e processual, mas agora esse pouco de complexidade estava escondido atrás de um objeto. Foi um pouco melhor.
Agora, toda vez que você acessa, está fazendo um bom trabalho. Um DTO é um valor em cache rápido e agradável. Eu me preocupei com isso, mas percebi que poderia adicionar o cache, se precisarmos, sem tocar em nenhum código de uso. Então eu não vou incomodar até que nos importemos.
Estou dizendo que você deve sempre manter objetos OO sobre DTOs? Não. Os DTOs brilham quando você precisa cruzar um limite que o impede de mover métodos. Os DTOs têm o seu lugar.
Mas o mesmo acontece com objetos OO. Aprenda a usar as duas ferramentas. Saiba o que cada um custa. Aprenda a deixar o problema, a situação e o estagiário decidirem. Dogma não é seu amigo aqui.
Como minha resposta já é ridiculamente longa, deixe-me desapontá-lo de alguns conceitos errados com uma revisão do seu código.
Onde está o seu construtor? Isso não está me mostrando o suficiente para saber se é útil.
Você pode fazer muitas coisas tolas em nome do Princípio da Responsabilidade Única. Eu poderia argumentar que Cat Strings e Cat ints devem ser separadas. Todos os métodos de desenho e imagens devem ter sua própria classe. Como o seu programa em execução é de responsabilidade única, você deve ter apenas uma classe. : P
Para mim, a melhor maneira de seguir o Princípio da Responsabilidade Única é encontrar uma boa abstração que permita colocar complexidade em uma caixa para que você possa escondê-la. Se você pode dar um bom nome para evitar que as pessoas se surpreendam com o que descobrem quando olham para dentro, você o seguiu bastante bem. Esperar que dite mais decisões do que está pedindo problemas. Honestamente, as duas listagens de código fazem isso, então não vejo por que o SRP é importante aqui.
Bem não. O princípio de fechamento aberto não é sobre a adição de novos métodos. Trata-se de poder alterar a implementação de métodos antigos e ter que editar nada. Nada que use você e não seus métodos antigos. Em vez disso, você escreve um novo código em outro lugar. Alguma forma de polimorfismo fará isso muito bem. Não veja isso aqui.
Bem, inferno, como eu deveria saber? Olha, fazer de qualquer maneira tem benefícios e custos. Quando você separa o código dos dados, pode alterar sem precisar recompilar o outro. Talvez isso seja extremamente importante para você. Talvez isso apenas torne seu código desnecessariamente complicado.
Se isso faz você se sentir melhor, não está longe de algo que Martin Fowler chama de objeto de parâmetro . Você não precisa apenas levar primitivas para o seu objeto.
O que eu gostaria que você fizesse é desenvolver um senso de como fazer sua separação, ou não, nos dois estilos de codificação. Porque acredite ou não, você não está sendo forçado a escolher um estilo. Você apenas tem que viver com sua escolha.
fonte
Você se deparou com um tópico de debate que vem criando argumentos entre desenvolvedores há mais de uma década. Em 2003, Martin Fowler cunhou a frase "Modelo de Domínio Anêmico" (ADM) para descrever essa separação de dados e funcionalidade. Ele - e outros que concordam com ele - argumentam que "Modelos de Domínio Rico" (mistura de dados e funcionalidade) é "OO adequado" e que a abordagem da ADM é um anti-padrão não-OO.
Sempre houve quem rejeitasse esse argumento e esse lado se tornou cada vez mais ousado nos últimos anos com a adoção por mais desenvolvedores de técnicas de desenvolvimento funcional. Essa abordagem incentiva ativamente a separação de preocupações de dados e funções. Os dados devem ser imutáveis o máximo possível, para que o encapsulamento do estado mutável se torne uma preocupação. Não há benefício em anexar funções diretamente a esses dados em tais situações. Se é então "não OO" ou não, não interessa absolutamente a essas pessoas.
Independentemente do lado da cerca em que você se sentar (eu me sento muito firme no lado "Martin Fowler está falando muito velho"), seu uso de métodos estáticos
printInfo
edraw
quase universalmente é desaprovado. É difícil zombar dos métodos estáticos ao escrever testes de unidade. Portanto, se tiverem efeitos colaterais (como imprimir ou desenhar em alguma tela ou outro dispositivo), não devem ser estáticos ou devem ter o local de saída passado como parâmetro.Então você pode ter uma interface:
E uma implementação que é injetada no restante do sistema em tempo de execução (com outras implementações sendo usadas para teste):
Ou adicione parâmetros extras para remover os efeitos colaterais desses métodos:
fonte
DTOs - Objetos de Transporte de Dados
são úteis para isso. Se você deseja desviar dados entre programas ou sistemas, os DTOs são desejáveis, pois fornecem um subconjunto gerenciável de um objeto que se preocupa apenas com a estrutura e a formatação dos dados. A vantagem é que você não precisa sincronizar atualizações para métodos complexos em vários sistemas (desde que os dados subjacentes não sejam alterados).
O objetivo principal do OO é vincular dados e o código que atua nesses dados. Separar um objeto lógico em classes separadas geralmente é uma má idéia.
fonte
Muitos padrões e princípios de design entram em conflito e, finalmente, apenas seu julgamento como um bom programador o ajudará a decidir quais padrões devem ser aplicados a um problema específico.
Na minha opinião, o princípio Faça a coisa mais simples que poderia funcionar deve vencer por padrão, a menos que você tenha um motivo forte para tornar o design mais complicado. Portanto, no seu exemplo específico, eu optaria pelo primeiro design, que é uma abordagem orientada a objetos mais tradicional, com dados e funções juntos na mesma classe.
Aqui estão algumas perguntas que você pode se perguntar sobre o aplicativo:
Responder sim a qualquer uma dessas opções pode levar a um design mais complexo com DTO separado e classes funcionais.
fonte
É um bom padrão?
Você provavelmente obterá respostas diferentes aqui, porque as pessoas seguem diferentes escolas de pensamento. Portanto, antes mesmo de começar: Esta resposta está de acordo com a programação orientada a objetos, conforme descrito por Robert C. Martin no livro "Código Limpo".
POO "verdadeiro"
Tanto quanto sei, há 2 interpretações de OOP. Para a maioria das pessoas, tudo se resume a "um objeto é uma classe".
No entanto, achei a outra escola de pensamento mais útil: "Um objeto é algo que faz alguma coisa". Se você estiver trabalhando com classes, cada classe seria uma das duas coisas: um " detentor de dados " ou um objeto . Os titulares de dados mantêm dados (duh), os objetos fazem coisas. Isso não significa que um detentor de dados não possa ter métodos! Pense em
list
: Alist
realmente não faz nada, mas possui métodos para reforçar a maneira como mantém os dados .Titulares de dados
Você
Cat
é um excelente exemplo de um detentor de dados. Certamente, fora do seu programa, um gato é algo que faz alguma coisa , mas dentro do seu programa, aCat
é uma Stringname
, um intweight
e uma Imagemimage
.O fato de os titulares de dados não fazerem nada também não significa que eles não contenham lógica de negócios! Se a sua
Cat
classe tem um construtor que requername
,weight
eimage
, você encapsulado com sucesso a regra de negócio que cada gato tem aqueles. Se você pode alterarweight
depois que umaCat
classe foi criada, essa é outra regra de negócios encapsulada. Essas são coisas muito básicas, mas isso significa apenas que é muito importante não entendê-las errado - e, dessa maneira, você garante que é impossível entender errado.Objetos
Objetos fazem alguma coisa. Se você deseja limpar objetos *, podemos mudar para "Objetos fazem uma coisa".
Imprimir as informações do gato é o trabalho de um objeto. Por exemplo, você pode chamar esse objeto "
CatInfoLogger
", com um método públicoLog(Cat)
. É tudo o que precisamos saber externamente: esse objeto registra as informações de um gato e, para isso, precisa de umCat
.Por dentro, esse objeto teria referências a tudo o que precisa para cumprir sua única responsabilidade de registrar gatos. Nesse caso, isso é apenas uma referência a
System
forSystem.out.println
, mas na maioria dos casos, serão referências particulares a outros objetos. Se a lógica para formatar a saída antes da impressão se tornar muito complexa, éCatInfoLogger
possível obter uma referência a um novo objetoCatInfoFormatter
. Se, posteriormente, todo log também precisar ser gravado em um arquivo, você poderá adicionar umCatToFileLogger
objeto que faça isso.Suportes de dados x objetos - resumo
Agora, por que você faria tudo isso? Porque agora alterar uma classe muda apenas uma coisa ( a maneira como um gato é registrado no exemplo). Se você está mudando um objeto, está mudando apenas a maneira como uma determinada ação é executada; se você alterar um detentor de dados, altere apenas quais dados são mantidos e / ou de que maneira eles são mantidos.
Alterar dados pode significar que a maneira como algo é feito também precisa mudar, mas essa mudança não acontecerá até que você faça isso mudando o objeto responsável.
Exagero?
Essa solução pode parecer um pouco exagerada. Deixe-me ser franco: é . Se todo o seu programa for tão grande quanto o exemplo, não faça nenhuma das opções acima - basta escolher uma das formas propostas com um sorteio. Não há perigo de confundir outro código se não é nenhum outro código.
No entanto, qualquer software "sério" (= mais que um script ou 2) provavelmente é grande o suficiente para lucrar com uma estrutura como essa.
Responda
Transformar a "maioria das classes" (sem qualquer qualificação adicional) em DTOs / detentores de dados não é um anti-padrão, porque na verdade não é nenhum padrão - é mais ou menos aleatório.
Manter dados e objetos separados, no entanto, é visto como uma boa maneira de manter seu código limpo *. Às vezes, você precisará apenas de um DTO "anêmico" e pronto. Outras vezes, você precisa de métodos para impor a maneira como os dados são mantidos. Apenas não coloque a funcionalidade do seu programa com os dados.
* Isso é "limpo" com um C maiúsculo como o título do livro, porque - lembre-se do primeiro parágrafo - trata-se de uma escola de pensamento, enquanto há outras.
fonte