No artigo, cuja tradução é proposta abaixo, Robert Martin parece começar com pensamentos muito semelhantes aos que podem ser vistos nas discussões de Yegor Bugaenko sobre ORM, mas outros tiram conclusões. Pessoalmente, a abordagem de Yegor me impressiona, mas acho que Martin revela o tópico com mais detalhes. Parece-me que todos os que já pensaram em que lugar o ORM deveria ocupar e, geralmente, por que os objetos são necessários para os quais todos os campos estão abertos devem ter sua opinião. O artigo está escrito no gênero "Dialogue", em que um programador mais experiente discute um problema com alguém que tem menos experiência.
O que é uma aula?
Uma classe é uma especificação de muitos objetos semelhantes.
O que é um objeto?
Um objeto é um conjunto de funções que executam ações com dados encapsulados.
Ou é melhor dizer que um objeto é um conjunto de funções que executam ações com dados cuja existência está implícita
No sentido de "implícito"?
Depois que o objeto tem funções, pode-se supor que também haja dados, mas não há acesso direto aos dados e eles não são visíveis de fora.
Os dados não estão no objeto?
Talvez eles estejam, mas a regra que diz que eles devem estar lá não existe. Do ponto de vista do usuário, um objeto nada mais é do que um conjunto de funções. Os dados com os quais essas funções funcionam devem existir, mas a posição desses dados é desconhecida para o usuário.
Bem, digamos.
Bem, o que é uma estrutura de dados?
Uma estrutura de dados é uma coleção de itens relacionados.
Ou, em outras palavras, uma estrutura de dados é um conjunto de elementos com os quais as funções funcionam, cuja existência está implícita.
Ok, ok. Eu entendi. As funções que trabalham com estruturas de dados não são definidas dentro dessas estruturas, mas a partir da própria existência de uma estrutura de dados, podemos concluir que deve haver algo que funcione com elas.
Certo. E o que dizer dessas duas definições?
Em certo sentido, eles são opostos um ao outro.
Sério. Eles se complementam. Como uma mão e uma luva.
- Um objeto é um conjunto de funções que trabalham com elementos de dados cuja existência está implícita.
- Uma estrutura de dados é um conjunto de elementos de dados com os quais as funções funcionam, cuja existência está implícita.
Uau! Portanto, objetos e estruturas de dados não são a mesma coisa!
Certo. Estruturas de dados são DTOs.
E as tabelas nos bancos de dados também não são objetos, certo?
Verdade novamente. Bancos de dados contêm estruturas de dados, não objetos.
Espere um momento. O ORM não mapeia tabelas do banco de dados para objetos?
Claro que não. Você não pode mapear tabelas de banco de dados para objetos. As tabelas no banco de dados são estruturas de dados, não objetos.
Então, o que o ORM faz?
Eles transferem dados de uma estrutura para outra.
Então eles não têm nada a ver com objetos?
Nada mesmo. Estritamente falando, não existe algo como ORM no sentido de tecnologia que mapeia dados relacionais para objetos, porque as tabelas não podem ser mapeadas de um banco de dados para objetos.
Mas eles me disseram que os ORMs coletam objetos de negócios.
Não, o ORM recupera dados de um banco de dados com o qual os objetos de negócios trabalham
Mas essas estruturas de dados não se enquadram nos objetos de negócios?
Talvez eles consigam, ou talvez não. ORM não sabe nada sobre isso.
Mas a diferença é puramente semântica.
Não não Há consequências de longo alcance.
Por exemplo?
Por exemplo, o design do esquema do banco de dados e o design dos objetos de negócios. Objetos de negócios definem o comportamento dos negócios. O esquema do banco de dados define a estrutura de dados corporativos. Essas estruturas são limitadas por forças muito diferentes. Uma estrutura de dados de negócios não é necessariamente a melhor estrutura para o comportamento dos negócios.
Eeee. Isso é incompreensível.
Pense nisso dessa maneira. O esquema de dados não foi projetado para um único aplicativo, deve ser usado em toda a empresa. Portanto, a estrutura de dados é um compromisso entre vários aplicativos diferentes.
Isso é compreensível.
Bom Agora pense em cada aplicativo. O modelo de objeto de cada aplicativo descreve como o comportamento do aplicativo é estruturado. Cada aplicativo terá seu próprio modelo de objeto para corresponder melhor ao comportamento do aplicativo.
Ah, entendo. Como o esquema de dados é um compromisso entre aplicativos diferentes, o esquema não se enquadra no modelo de objeto de cada aplicativo individual.
Certo! Objetos e estruturas são limitados a coisas diferentes. Eles raramente se encaixam. As pessoas chamam isso de incompatibilidade de impedância objeto-relacional.
Algo que eu lembro. Mas parece que a incompatibilidade de impedância foi corrigida usando o ORM.
E agora você sabe que não é assim. A incompatibilidade de impedância entre objetos e estruturas de dados é complementar, não isomórfica.
O que?
Eles são opostos, não algo semelhante.
Opostos?
Sim, em um sentido muito interessante. Veja bem, objetos e estruturas de dados implicam estruturas de controle diametralmente opostas.
O que?
Pense em um conjunto de classes que implementam algum tipo de interface comum. Por exemplo, imagine classes que representam figuras bidimensionais, nas quais existem funções para calcular a área e o perímetro de uma figura.
Quanto as formas empurram código com objetos em todos os exemplos?
Vejamos dois tipos diferentes de formas: Quadrados e Círculos. É claro que as funções para calcular a área e o perímetro dessas classes usam estruturas de dados diferentes. Entende-se também que essas operações são invocadas usando polimorfismo dinâmico.
Devagar, por favor, nada está claro.
Existem duas funções diferentes para calcular a área, uma para o quadrado e a outra para o círculo. Quando uma função é chamada para calcular a área de um objeto específico, é esse objeto que decide qual função específica chamar. Isso é chamado de polimorfismo dinâmico.
Ok. Claro. Um objeto sabe como seus métodos são implementados. Naturalmente.
Agora vamos transformar esses objetos em estruturas de dados. Utilizamos sindicatos discriminados.
Discriminado o que?
Sindicatos Discriminados. Bem, C ++, ponteiros, a palavra-chave union, uma bandeira para determinar o tipo de estrutura, Uniões Discriminadas. No nosso caso, essas são apenas duas estruturas de dados diferentes. Um para o quadrado e outro para o círculo. O círculo tem um ponto central e um raio. E um código de tipo a partir do qual se pode entender que é um círculo.
O campo com o código será enum?
Bem sim. E o quadrado terá o ponto superior esquerdo e o comprimento do lado. E também enum para indicar o tipo.
Ok. Haverá duas estruturas com um código de tipo.
Certo. Agora vamos ver a função para a área. Provavelmente haverá uma troca, certo?
Bem. Claro, para duas aulas. O ramo da praça e do círculo. E para o perímetro, você também precisa de uma opção semelhante.
E novamente, certo. Agora pense sobre esses dois cenários. Em um cenário com objetos, duas implementações de funções para uma área são independentes uma da outra e pertencem (em certo sentido) diretamente ao tipo. A função para a área do quadrado pertence ao quadrado e a função para determinar a área do círculo pertence ao círculo.
Ok, eu entendo o que você está levando. Em um cenário com estruturas de dados, ambas as implementações de uma função para uma área estão na mesma função, elas não "pertencem" (o que essa palavra significa) ao tipo.
Mais é melhor. No caso de objetos, se você precisar adicionar o tipo Triângulo, qual código deve ser alterado?
Não mude nada. Basta fazer uma nova classe Triangle. Embora não, você provavelmente precisará corrigir o código que cria os objetos.
Certo. Portanto, ao adicionar um novo tipo, as alterações são desprezíveis. Agora, suponha que você precise adicionar uma nova função - por exemplo, uma função para determinar o centro.
Então você deve adicioná-lo aos três tipos, Círculo, Quadrado e Triângulo.
Bom Acontece que adicionar novas funções é difícil, porque você precisa fazer alterações em cada classe.
Mas com estruturas de dados, tudo é diferente. Para adicionar um triângulo, é necessário alterar cada função para adicionar ramificações para lidar com o triângulo em cada opção.
Certo. É difícil adicionar tipos, você precisa editar cada função.
Mas, para adicionar uma função ao centro, nada precisa ser alterado.
Sim. Adicionar recursos é fácil.
Uau. Acontece que essas duas abordagens são diretamente opostas.
Definitivamente sim. Resumir
- É difícil adicionar novas funções às aulas, você precisa fazer alterações em cada classe
- Adicionar novas funções às estruturas de dados é simples, basta adicionar uma função, nada mais precisa ser alterado
- Adicionar novos tipos às classes é simples, você só precisa adicionar uma nova classe
- É difícil adicionar novos tipos para estruturas; você precisa corrigir cada função
Sim Opostos. Opostos em um sentido curioso. Ou seja, se é sabido antecipadamente que novas funções precisam ser adicionadas, é conveniente usar estruturas de dados. Mas se você sabe com antecedência que precisa adicionar novos tipos, precisará usar classes.
Boa observação! Mas hoje precisamos pensar em mais uma coisa. Há outro ponto em que estruturas e classes de dados são opostas uma à outra. Dependências.
Vícios?
Sim, a direção das dependências no código fonte.
Ok, eu vou perguntar. Qual a diferença?
Vejamos o caso das estruturas. Cada função contém uma opção que seleciona a implementação desejada com base no código de tipo na união.
É sim. E daí?
Vejamos a chamada de função para a área. O código de chamada depende da função da área e a função da área depende de cada implementação específica.
E o que você quer dizer quando diz "depende"?
Imagine que cada implementação de uma função para uma área seja alocada para uma função separada. Ou seja, haverá as funções circleArea, squareArea e triangleArea.
Bem, acontece que nos ramos do comutador simplesmente haverá chamadas para essas funções.
Imagine que essas funções estão em arquivos diferentes.
Em seguida, no arquivo com o comutador será importado ou usado ou incluído para arquivos com funções.
Direito Esta é uma dependência no nível do código fonte. Uma fonte depende de outra fonte. Como essa dependência é direcionada?
O código-fonte com opção depende do código-fonte no qual as implementações estão localizadas.
E o código que chama a função da área?
O código de chamada depende do código com switch, que depende de todas as implementações.
Certo. Em todas as fontes, a seta é direcionada na direção da chamada, do código de chamada à implementação. Então, se você quiser fazer uma pequena alteração nessas implementações ...
Ok, ok, eu vejo o que você está chegando. Uma alteração em qualquer uma das implementações implicará a recompilação de todos os arquivos com o switch, e isso levará ao fato de que tudo o que chama esse switch será recompilado, por exemplo, no nosso caso, a função por área.
Sim Pelo menos será assim nos idiomas que usam datas de modificação de arquivos para entender o que precisa ser reconstruído.
E esses geralmente são todos os sistemas com digitação estática, certo?
Sim, e alguns outros sistemas sem ele
Isso precisa ser muito reconstruído.
E muito para refazê-lo.
Ok, mas no caso de aulas é o contrário?
Sim, porque o código que chama a função para a área depende da interface e a implementação também depende dessa interface.
Eu vejo. O código da classe Square importará ou usará ou incluirá um arquivo com a interface Shape.
Certo. A seta nos arquivos de implementação aponta na direção oposta à chamada. É direcionado do código de implementação para o código de chamada. Pelo menos esse será o caso para idiomas de tipo estaticamente. Para idiomas digitados dinamicamente, o código que chama a função para a área não depende de nada, porque a vinculação ocorre no tempo de execução.
Sim está bem. Ou seja, se você fizer alterações em uma das implementações ...
Só é necessário recriar e reinstalar o código com essas alterações.
Isso ocorre porque as dependências são direcionadas opostas à direção das chamadas.
Sim, chamamos isso de inversão de dependência.
Ok, vamos resumir tudo. Classes e estruturas de dados se opõem em três sentidos.
- As funções estão explicitamente nas classes e você só pode adivinhar a existência de dados. As estruturas de dados estão explicitamente presentes nas estruturas de dados e você pode apenas adivinhar quais funções estão disponíveis.
- No caso de classes, adicionar tipos é simples, mas adicionar funções é difícil. No caso de estruturas, adicionar funções é fácil, mas adicionar tipos é difícil.
- As estruturas de dados levam à recompilação e redistribuição do código de chamada. As classes isolam o código de chamada e não precisam recompilar e implantá-lo novamente.
Sim está certo. E isso deve ser lembrado por todo designer e arquiteto de software.