Perché un modello di dominio anemico è considerato negativo in C # / OOP, ma molto importante in F # / FP?


46

In un post sul blog su F # per divertimento e profitto, si dice:

In un design funzionale, è molto importante separare il comportamento dai dati. I tipi di dati sono semplici e "stupidi". E poi separatamente, hai un numero di funzioni che agiscono su quei tipi di dati.

Questo è esattamente l'opposto di un design orientato agli oggetti, in cui comportamento e dati devono essere combinati. Dopo tutto, questo è esattamente ciò che è una classe. In una progettazione veramente orientata agli oggetti, infatti, non dovresti avere altro che un comportamento: i dati sono privati ​​e sono accessibili solo tramite metodi.

Infatti, in OOD, non avere un comportamento sufficiente attorno a un tipo di dati è considerato una cosa negativa e ha persino un nome: il " modello di dominio anemico ".

Dato che in C # sembriamo continuare a prendere in prestito da F # e cercare di scrivere codice più funzionale; come mai non stiamo prendendo in prestito l'idea di separare dati / comportamento, e nemmeno lo consideriamo cattivo? È semplicemente che la definizione non coincide con OOP, o c'è una ragione concreta che è male in C # che per qualche motivo non si applica in F # (e in effetti, è invertito)?

(Nota: sono particolarmente interessato alle differenze in C # / F # che potrebbero cambiare l'opinione di ciò che è buono / cattivo, piuttosto che le persone che potrebbero non essere d'accordo con entrambe le opinioni nel post del blog).


1
Ciao Dan! La tua attitudine è di ispirazione. Oltre alla piattaforma .NET (e haskell), ti incoraggio a guardare scala. Debashish ghosh ha scritto un paio di blog sulla modellazione di domini con strumenti funzionali, è stato perspicace per me, speriamo anche per te, eccoti
AndreasScheinert

2
Oggi un collega mi ha inviato un interessante post sul blog: blog.inf.ed.ac.uk/sapm/2014/02/04/… Sembra che le persone stiano iniziando a sfidare l'idea che i modelli di dominio anemici sono decisamente cattivi; che penso potrebbe essere una buona cosa!
Danny Tuppeny,

1
Il post sul blog a cui fai riferimento si basa su un'idea errata: "Normalmente va bene che i dati vengano esposti senza essere incapsulati. I dati sono immutabili, quindi non possono essere" danneggiati "da una funzione di comportamento scorretto." Anche i tipi immutabili hanno invarianti che devono essere preservati e che richiedono di nascondere i dati e controllare come possono essere creati. Ad esempio, non è possibile esporre l'implementazione di un albero immutabile rosso-nero, perché in tal modo qualcuno potrebbe creare un albero costituito solo da nodi rossi.
Doval,

4
@Doval per essere onesti è come dire che non puoi esporre uno scrittore di file system perché qualcuno potrebbe riempire il tuo disco. Qualcuno che crea un albero di soli nodi rossi non fa assolutamente alcun danno all'albero rosso-nero da cui sono stati clonati, né alcun codice in tutto il sistema che utilizza quell'istanza ben formata. Se scrivi codice che crea attivamente nuove istanze di immondizia o fa cose pericolose, l'immutabilità non ti salverà, ma salverà altri da te . Nascondere l'implementazione non impedisce alle persone di scrivere codice senza senso che finisce per dividersi per zero.
Jimmy Hoffa,

2
@JimmyHoffa Sono d'accordo, ma non ha nulla a che fare con ciò che ho criticato. L'autore afferma che normalmente va bene esporre i dati perché è immutabile e sto dicendo che l'immutabilità non rimuove magicamente la necessità di nascondere i dettagli dell'implementazione.
Doval,

Risposte:


37

Il motivo principale per cui FP punta a questo obiettivo e C # OOP no è che in FP l'attenzione è rivolta alla trasparenza referenziale; vale a dire, i dati entrano in una funzione e i dati escono, ma i dati originali non vengono modificati.

In C # OOP esiste un concetto di delega di responsabilità in cui delegare la gestione di un oggetto ad esso, e quindi si desidera che cambi i propri interni.

In FP non vuoi mai cambiare i valori in un oggetto, quindi avere le tue funzioni incorporate nel tuo oggetto non ha senso.

Inoltre in FP hai un polimorfismo di tipo più elevato che consente alle tue funzioni di essere molto più generalizzate di quelle consentite da C # OOP. In questo modo puoi scrivere una funzione che funziona per qualsiasi a, e quindi averla incorporata in un blocco di dati non ha senso; che accoppierebbe strettamente il metodo in modo che funzioni solo con quel particolare tipo di a. Comportamenti del genere sono tutti ben condivisi in C # OOP perché non si ha la possibilità di astrarre funzioni così in generale comunque, ma in FP è un compromesso.

Il problema più grande che ho riscontrato nei modelli di dominio anemico in C # OOP è che finisci con un codice duplicato perché hai DTO x e 4 diverse funzioni che commettono attività f in DTO x perché 4 persone diverse non hanno visto l'altra implementazione . Quando metti il ​​metodo direttamente su DTO x, quelle 4 persone vedono l'implementazione di f e lo riutilizzano.

I modelli di dati anemici in C # OOP ostacolano il riutilizzo del codice, ma questo non è il caso in FP perché una singola funzione è generalizzata in così tanti tipi diversi che si ottiene un maggiore riutilizzo del codice poiché tale funzione è utilizzabile in tanti più scenari di una funzione che scriverebbe per un singolo DTO in C #.


Come sottolineato nei commenti , l'inferenza del tipo è uno dei vantaggi su cui FP fa affidamento per consentire un polimorfismo così significativo, e in particolare è possibile risalire al sistema di tipo Hindley Milner con l'inferenza di tipo Algoritmo W; tale inferenza di tipo nel sistema di tipo OOP C # è stata evitata perché il tempo di compilazione quando viene aggiunta l'inferenza basata sui vincoli diventa estremamente lungo a causa della ricerca esaustiva necessaria, dettagli qui: https://stackoverflow.com/questions/3968834/generics-why -cant-the-compilatore-infer-the-type-argomenti-in-questo-caso


Quali funzioni di F # semplificano la scrittura di questo tipo di codice riutilizzabile? Perché il codice in C # non può essere riutilizzabile? (Penso di aver visto la possibilità di avere metodi che prendano argomenti con proprietà specifiche, senza bisogno di un'interfaccia; quale immagino sarebbe una chiave?)
Danny Tuppeny,

7
@DannyTuppeny onestamente F # è un cattivo esempio per il confronto, è solo un C # leggermente vestito; è un linguaggio imperativo mutevole proprio come C #, ha alcune funzionalità FP che C # non ha ma non molto. Guarda haskell per vedere dove spicca davvero FP e cose come questa diventano molto più possibili a causa delle classi di tipi e degli ADT generici
Jimmy Hoffa,

@MattFenwick Mi riferisco esplicitamente a C # perché è quello che chiedeva il poster. Dove mi riferisco a OOP in tutta la mia risposta qui intendo C # OOP, modificherò per chiarire.
Jimmy Hoffa,

2
Una caratteristica comune tra i linguaggi funzionali che consente questo tipo di riutilizzo è la digitazione dinamica o inferita. Mentre il linguaggio stesso può utilizzare tipi di dati ben definiti, la funzione tipica non importa quali siano i dati, purché le operazioni (altre funzioni o aritmetiche) eseguite su di essi siano valide. Questo è disponibile anche nei paradigmi OO (Go, ad esempio, ha un'implementazione implicita dell'interfaccia che consente a un oggetto di essere un'anatra, perché può volare, nuotare e ciarlare, senza che l'oggetto sia stato dichiarato esplicitamente come un'anatra), ma è praticamente un requisito della programmazione funzionale.
KeithS,

4
@KeithS Per sovraccarico basato sul valore in Haskell penso che intendi il pattern matching. La capacità di Haskell di avere immediatamente più funzioni di livello superiore con lo stesso nome con diversi pattern desugars a 1 funzione di livello superiore + una corrispondenza di pattern.
jozefg,

6

Perché un modello di dominio anemico è considerato negativo in C # / OOP, ma molto importante in F # / FP?

La tua domanda ha un grosso problema che limiterà l'utilità delle risposte che ricevi: stai insinuando / supponendo che F # e FP siano simili. FP è un'enorme famiglia di lingue che include termini simbolici di riscrittura, dinamica e statica. Anche tra i linguaggi FP tipizzati staticamente ci sono molte tecnologie diverse per esprimere modelli di dominio come moduli di ordine superiore in OCaml e SML (che non esistono in F #). F # è uno di questi linguaggi funzionali ma è particolarmente notevole per essere snello e, in particolare, non fornisce né moduli di ordine superiore né tipi di tipo superiore.

In effetti, non potevo iniziare a dirti come sono espressi i modelli di dominio in FP. L'altra risposta qui parla in modo molto specifico di come viene fatto in Haskell e non è affatto applicabile a Lisp (la madre di tutte le lingue FP), alla famiglia di lingue ML o ad altre lingue funzionali.

come mai non stiamo prendendo in prestito l'idea di separare dati / comportamento, e nemmeno lo consideriamo cattivo?

I generici potrebbero essere considerati un modo per separare dati e comportamento. I generici provengono dalla famiglia ML di linguaggi di programmazione funzionale non fanno parte di OOP. C # ha generici, ovviamente. Quindi si potrebbe sostenere che C # sta lentamente prendendo in prestito l'idea di separare dati e comportamento.

È semplicemente che la definizione non si adatta con OOP,

Credo che OOP sia basato su una premessa fondamentalmente diversa e, di conseguenza, non ti fornisca gli strumenti necessari per separare dati e comportamento. Per tutti gli scopi pratici è necessario il prodotto e la somma dei tipi di dati e l'invio su di essi. In ML questo significa tipi di unione e record e corrispondenza del modello.

Guarda l'esempio che ho dato qui .

o c'è una ragione concreta che è male in C # che per qualche motivo non si applica in F # (e in effetti, è invertito)?

Fai attenzione a saltare da OOP a C #. C # non è affatto puritano sull'OOP come le altre lingue. .NET Framework è ora pieno di generici, metodi statici e persino lambda.

(Nota: sono particolarmente interessato alle differenze in C # / F # che potrebbero cambiare l'opinione di ciò che è buono / cattivo, piuttosto che le persone che potrebbero non essere d'accordo con entrambe le opinioni nel post del blog).

La mancanza di tipi di unione e pattern matching in C # rende quasi impossibile farlo. Quando tutto ciò che hai è un martello, tutto sembra un chiodo ...


2
... quando tutto ciò che hai è un martello, la gente di OOP creerà fabbriche di martelli; P +1 per individuare davvero il punto cruciale di ciò che manca a C # per consentire ai dati e al comportamento di essere completamente astratti l'uno dall'altro: tipi di unione e corrispondenza dei modelli.
Jimmy Hoffa,

-4

Penso che in un'applicazione aziendale spesso non si voglia nascondere i dati perché la corrispondenza dei modelli su valori immutabili è ottima per garantire che si stiano coprendo tutti i casi possibili. Ma se stai implementando algoritmi o strutture di dati complessi, è meglio nascondere i dettagli di implementazione trasformando gli ADT (tipi di dati algebrici) in ADT (tipi di dati astratti).


4
Potresti spiegare qualcosa in più su come questo si applica alla programmazione orientata agli oggetti rispetto alla programmazione funzionale?
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.