Por que um modelo de domínio anêmico é considerado ruim em C # / OOP, mas muito importante em F # / FP?


46

Em um post no F # por diversão e lucro, ele diz:

Em um design funcional, é muito importante separar o comportamento dos dados. Os tipos de dados são simples e "burros". E então separadamente, você tem várias funções que atuam nesses tipos de dados.

Esse é exatamente o oposto de um design orientado a objetos, onde comportamento e dados devem ser combinados. Afinal, é exatamente isso que uma classe é. De fato, em um design realmente orientado a objetos, você deve ter apenas comportamento - os dados são privados e só podem ser acessados ​​por métodos.

De fato, em OOD, não ter comportamento suficiente em torno de um tipo de dados é considerado uma Coisa Ruim e até tem um nome: o " modelo de domínio anêmico ".

Dado que, em C #, parecemos continuar pedindo emprestado ao F # e tentando escrever um código mais funcional; por que não emprestamos a idéia de separar dados / comportamento e até consideramos ruim? É simplesmente que a definição não combina com OOP, ou há uma razão concreta para que seja ruim em C # que por alguma razão não se aplique em F # (e, de fato, é revertida)?

(Observação: estou especificamente interessado nas diferenças em C # / F # que podem mudar a opinião do que é bom / ruim, em vez de indivíduos que podem discordar de qualquer opinião na publicação do blog).


1
Oi Dan! Sua atitude é inspiradora. Além da plataforma .NET (e haskell), recomendo que você olhe para o scala. Debashish ghosh escreveu um par de blogs sobre modelagem de domínio com ferramentas funcionais, foi esclarecedora para mim, espero que para você também, aqui vai: debasishg.blogspot.com/2012/01/...
AndreasScheinert

2
Hoje, um colega me enviou uma interessante postagem no blog: blog.inf.ed.ac.uk/sapm/2014/02/04/… Parece que as pessoas estão começando a desafiar a idéia de que os modelos de domínio anêmico são totalmente ruins; o que eu acho que pode ser uma coisa boa!
Danny Tuppeny

1
A postagem do blog que você menciona se baseia em uma idéia equivocada: "Normalmente, os dados são expostos sem serem encapsulados. Os dados são imutáveis, portanto, não podem ser" danificados "por uma função que se comporta mal". Mesmo os tipos imutáveis ​​têm invariantes que precisam ser preservados, e isso requer ocultar dados e controlar como eles podem ser criados. Por exemplo, você não pode expor a implementação de uma árvore imutável vermelho-preto, porque alguém pode criar uma árvore que consiste apenas em nós vermelhos.
Doval

4
@Doval para ser justo, é como dizer que você não pode expor um gravador de sistema de arquivos porque alguém pode encher seu disco. Alguém criando uma árvore com apenas nós vermelhos não faz absolutamente nenhum dano à árvore vermelho-preta da qual foram clonados, nem qualquer código no sistema que esteja usando essa instância bem formada. Se você escrever um código que crie ativamente novas instâncias de lixo ou faça coisas perigosas, a imutabilidade não salvará você, mas salvará outros de você . Ocultar a implementação não impedirá as pessoas de escreverem um código sem sentido que acaba dividindo por zero.
Jimmy Hoffa

2
@JimmyHoffa Eu concordo, mas isso não tem nada a ver com o que eu critiquei. O autor afirma que normalmente é bom que os dados sejam expostos porque são imutáveis, e estou dizendo que a imutabilidade não remove magicamente a necessidade de ocultar os detalhes da implementação.
Doval

Respostas:


37

A principal razão pela qual o FP visa isso e o C # OOP não é que, no FP, o foco está na transparência referencial; isto é, os dados entram em uma função e os dados saem, mas os dados originais não são alterados.

No C # OOP, existe um conceito de delegação de responsabilidade no qual você delega o gerenciamento de um objeto e, portanto, deseja que ele mude seus próprios internos.

No FP, você nunca deseja alterar os valores em um objeto, portanto, ter suas funções incorporadas ao seu objeto não faz sentido.

Além disso, no FP, você tem um polimorfismo de tipo mais alto, permitindo que suas funções sejam muito mais generalizadas do que o C # OOP permite. Dessa maneira, você pode escrever uma função que funcione para qualquer um a, e, portanto, incorporá-la em um bloco de dados não faz sentido; isso uniria fortemente o método para que ele funcione apenas com esse tipo específico de a. Um comportamento como esse é muito comum no C # OOP porque você não tem a capacidade de abstrair funções de maneira geral, mas no FP é uma desvantagem.

O maior problema que eu já vi nos modelos de domínio anêmico no C # OOP é que você acaba com código duplicado porque possui o DTO x e 4 funções diferentes que confirmam a atividade f para o DTO x, porque quatro pessoas diferentes não viram a outra implementação . Quando você coloca o método diretamente no DTO x, todas essas 4 pessoas veem a implementação de f e a reutilizam.

Modelos de dados anêmicos no C # OOP dificultam a reutilização de código, mas esse não é o caso no FP porque uma única função é generalizada em tantos tipos diferentes que você obtém uma maior reutilização de código, pois essa função é utilizável em muitos outros cenários do que uma função que você escreveria para um único DTO em C #.


Conforme apontado nos comentários , a inferência de tipo é um dos benefícios que a FP utiliza para permitir esse polimorfismo significativo e, especificamente, você pode rastrear isso no sistema de tipo Hindley Milner com a inferência de tipo W do algoritmo; essa inferência de tipo no sistema de tipo C # OOP foi evitada porque o tempo de compilação quando a inferência baseada em restrição é adicionada se torna extremamente longo devido à pesquisa exaustiva necessária, detalhes aqui: https://stackoverflow.com/questions/3968834/generics-why -cant-o-compilador-inferir-o-tipo-argumentos-neste caso


Quais recursos do F # facilitam a gravação desse tipo de código reutilizável? Por que o código em C # não pode ser tão reutilizável? (Eu acho que vi a capacidade de ter métodos ter argumentos com propriedades específicas, sem a necessidade de uma interface, o que eu acho que seria uma tecla?)
Danny Tuppeny

7
@DannyTuppeny honestamente F # é um péssimo exemplo para comparação, é apenas um C # levemente vestido; é uma linguagem imperativa mutável, assim como o C #, possui algumas facilidades de FP que o C # não possui, mas não muito. Olhar para Haskell para ver onde FP realmente se destaca e coisas como esta tornam-se muito mais possível devido ao tipo de aulas, bem como ADTs genéricos
Jimmy Hoffa

@ MattFenwick Estou me referindo explicitamente ao C # porque é isso que o cartaz estava perguntando. Onde me refiro ao OOP em toda a minha resposta aqui, quero dizer C # OOP, editarei para esclarecer.
Jimmy Hoffa

2
Um recurso comum entre linguagens funcionais que permitem esse tipo de reutilização é a digitação dinâmica ou inferida. Embora o próprio idioma possa usar tipos de dados bem definidos, a função típica não se importa com o que são dados, desde que as operações (outras funções ou aritmética) sendo executadas neles sejam válidas. Também está disponível nos paradigmas OO (Go, por exemplo, tem implementação implícita de interface que permite que um objeto seja um Duck, porque ele pode voar, nadar e charlatão, sem que o objeto tenha sido explicitamente declarado como um Duck), mas é praticamente um requisito de programação funcional.
Keiths

4
@ KeithS Por sobrecarga baseada em valor em Haskell, acho que você quer dizer correspondência de padrões. A capacidade de Haskell de ter várias funções de nível superior com o mesmo nome, com diferentes desugars de padrões imediatamente para 1 função de nível superior + uma correspondência de padrões.
21413 Jozefg

6

Por que um modelo de domínio anêmico é considerado ruim em C # / OOP, mas muito importante em F # / FP?

Sua pergunta tem um grande problema que limitará a utilidade das respostas que você obtém: você está implicando / assumindo que F # e FP são semelhantes. FP é uma família enorme de idiomas, incluindo reescrita simbólica de termos, dinâmica e estática. Mesmo entre as linguagens FP estaticamente tipadas, existem muitas tecnologias diferentes para expressar modelos de domínio, como módulos de ordem superior no OCaml e SML (que não existem no F #). O F # é uma dessas linguagens funcionais, mas é particularmente notável por ser enxuta e, em particular, não fornece módulos de ordem superior ou tipos de tipo superior.

De fato, não pude começar a dizer como os modelos de domínio são expressos no FP. A outra resposta aqui fala muito especificamente sobre como isso é feito em Haskell e não é aplicável ao Lisp (a mãe de todas as línguas FP), à família de línguas ML ou a qualquer outra linguagem funcional.

por que não emprestamos a idéia de separar dados / comportamento e até consideramos ruim?

Os genéricos podem ser considerados uma maneira de separar dados e comportamento. Os genéricos são da família ML das linguagens de programação funcional que não fazem parte do POO. C # tem genéricos, é claro. Então, alguém poderia argumentar que o C # está emprestando lentamente a idéia de separar dados e comportamento.

Simplesmente a definição não se encaixa no OOP,

Acredito que a OOP se baseia em uma premissa fundamentalmente diferente e, consequentemente, não fornece as ferramentas necessárias para separar dados e comportamento. Para todos os fins práticos, você precisa de tipos de dados de produtos e soma e enviá-los por eles. No ML, isso significa tipos de união e registro e correspondência de padrões.

Confira o exemplo que dei aqui .

ou existe uma razão concreta para que seja ruim no C # que, por algum motivo, não se aplique no F # (e, de fato, seja revertido)?

Cuidado ao pular de OOP para C #. O C # não é nem de longe tão puritano em relação ao OOP quanto em outros idiomas. O .NET Framework agora está cheio de genéricos, métodos estáticos e até lambdas.

(Observação: estou especificamente interessado nas diferenças em C # / F # que podem mudar a opinião do que é bom / ruim, em vez de indivíduos que podem discordar de qualquer opinião na publicação do blog).

A falta de tipos de união e correspondência de padrões no C # torna quase impossível fazer isso. Quando tudo que você tem é um martelo, tudo parece um prego ...


2
... quando tudo que você tem é um martelo, o pessoal da OOP criará fábricas de martelos; P +1 por realmente definir o ponto crucial do que está faltando em C # para permitir que os dados e o comportamento sejam totalmente abstraídos um do outro: tipos de união e correspondência de padrões.
Jimmy Hoffa

-4

Eu acho que em um aplicativo de negócios muitas vezes você não deseja ocultar dados porque a correspondência de padrões em valores imutáveis ​​é excelente para garantir que você esteja cobrindo todos os casos possíveis. Porém, se você estiver implementando algoritmos ou estruturas de dados complexos, é melhor ocultar os detalhes da implementação, transformando ADTs (tipos de dados algébricos) em ADTs (tipos de dados abstratos).


4
Você poderia explicar um pouco mais sobre como isso se aplica à programação orientada a objetos versus programação funcional?
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.