I metodi dovrebbero far parte delle entità persistenti?

2

Stiamo codificando un gioco di piccole dimensioni e abbiamo una classe Player . Questa classe Player ha alcune proprietà che dovresti conservare in un database come Id , Level , Health .

Vorremmo che Player sia in grado di uccidere un altro Player . Per questo abbiamo bisogno di un metodo che riduca il Health di Player .

Per fare questo facciamo:

  • Crea un metodo sull'entità Player chiamata DealDamage(Player to) ?
  • Crea una classe separata che non persistiamo nel database chiamato WorldObject con un metodo DealDamage(WorldObject to) che è ereditato dalla classe Player ? L'elenco di metodi potrebbe diventare molto grande in un punto. In quale livello di applicazione inseriamo questa classe?
  • Crea una classe separata che non persistiamo nel database chiamato DamageController con un metodo DealDamage(Player from, Player to) e dividi la logica tra diversi controller? Questo probabilmente diventerà confuso?
  • ...

Il pensiero di non avere metodi su entità persistenti mi sembra interessante dal momento che sembra che tu stia dividendo la logica aziendale dalla logica del livello dati. Ma può e dovrebbe essere fatto?

    
posta bdebaere 04.08.2018 - 10:07
fonte

2 risposte

1

Mentre ci sono alcune scuole di pensiero sull'orientamento agli oggetti che insistono nel combinare le entità dati con la logica per allineare le classi con le entità fisiche , questa non è assolutamente una linea guida che dovresti seguire a meno che tu non creda veramente che sia la soluzione migliore al tuo particolare problema (Questo approccio può naturalmente prestarsi ad alcuni problemi, ma spesso causa più problemi di quanti ne risolva se seguiti ciecamente). Esistono altre scuole di pensiero che enfatizzano la netta separazione delle preoccupazioni e danno la priorità ai Principi SOLID ".

Le entità fisiche come classi sono una prospettiva valida sulla progettazione della classe, ma sono anche estremamente limitanti e forniscono solo una prospettiva ristretta che non risolve problemi su come le classi possono interagire tra loro, come testarle o cosa succede quando la logica comportamentale potrebbe coinvolgere più entità fisiche. Né considera necessariamente il modo in cui il software cambia e si evolve nel tempo, man mano che i suoi requisiti cambiano o vengono rilevati problemi nell'implementazione originale.

È importante tenere a mente gli obiettivi dell'orientamento agli oggetti piuttosto che considerarli come un insieme di regole da seguire. In generale, gli obiettivi consistono nel raggruppare i comportamenti (metodi) logicamente correlati in classi, mantenendo una netta separazione tra classi / comportamenti diversi non correlati. La misura in cui decidi di farlo dipende da te, ma puoi spesso prendere molte decisioni in base al fatto che qualcosa "si senta" utile nel modo in cui strutturi il tuo programma.

Se qualcosa non sembra utile, o sembra controproducente, o porta a un codice che sembra "disordinato" e inutilmente complesso, allora questo dovrebbe essere un motivo sufficiente per non farlo. Prendete invece in considerazione le implicazioni pratiche, ad esempio se rende il vostro codice più facile da testare unitamente o se la logica alla base della vostra struttura di codice sarebbe immediatamente ovvia per gli altri sviluppatori che stanno cercando di ragionare sul vostro codice. Prendi in considerazione principi come KISS , DRY e YAGNI .

Sembra estremamente improbabile che qualsiasi logica aziendale / applicativa debba mai preoccuparsi delle specifiche delle operazioni CRUD relative a un archivio dati, quindi sembrerebbe piuttosto strano avere il metodo DealDamage nella stessa classe di un Save o Read metodo; il semplice buonsenso impone che queste cose probabilmente non siano correlate.

Ad esempio, il tuo metodo DealDamage probabilmente non si comporterà diversamente se i dati sono memorizzati in un database SQL rispetto a quelli memorizzati in un file Flat o in un archivio NoSQL, quindi da un punto logico / semantico di vedere ha senso tenerli separati. Inoltre, se il tuo metodo DealDamage si comporta diversamente a seconda di tali elementi, potrebbe essere un indicatore di un possibile problema nella progettazione o un problema con il modo in cui i requisiti sono specificati.

Non c'è assolutamente nulla di sbagliato nella programmazione orientata agli oggetti con la creazione di semplici modelli di "entità" il cui scopo è semplicemente quello di contenere i dati che sono stati recuperati da un database. Né c'è qualcosa di sbagliato nella creazione di classi comportamentali stateless che non hanno dati propri e operano interamente su quei modelli di entità, infatti un tale design è abbastanza tipico in un Layered Architecture , e anche necessario se stai costruendo un sistema basato su Architettura dei microservizi .

Naturalmente, dipende dalle tue esigenze in merito alla necessità o al beneficio di uno di questi stili architettonici. In generale, qualsiasi tipo di app molto piccola o banale potrebbe non ottenere molto beneficio dalla netta separazione delle preoccupazioni che si otterrebbero da un design più complesso, e si può ottenere un funzionamento del codice abbastanza velocemente con l'approccio Just Do It che tende a ignorare eventuali preoccupazioni più ampie sulla progettazione / architettura o possibili cambiamenti futuri.

I progetti più complessi hanno lo scopo di risolvere una moltitudine di problemi che le persone generalmente associano a sistemi più grandi, le cui esigenze sono più complesse e in continua evoluzione. Soprattutto quando la complessità di questi sistemi li renderebbe difficili da testare o cambiare in modo affidabile come un monolite. La separazione tra strati / moduli / sottosistemi consente di sviluppare separatamente questi diversi componenti, più facilmente riutilizzati da altri sistemi, più flessibili / scalabili per soddisfare i requisiti futuri e più facili da ragionare su altri sviluppatori.

    
risposta data 04.08.2018 - 12:01
fonte
0

Consente di coniare alcuni termini.

Se metti i tuoi metodi sullo stesso oggetto dei tuoi dati, esegui la classica Programmazione orientata agli oggetti (OOP) . Se non hai metodi sui tuoi oggetti dati e li metti invece su classi di servizio, come il tuo DamageController, al quale passi gli oggetti dati, questo viene spesso chiamato Modello di dominio anemico (ADM ) .

Personalmente trovo ADM un approccio attraente per cose come servizi web, processi di lavoro o qualsiasi cosa che sia riassunta come flusso di lavoro aziendale.

Se ho un oggetto Order potrei avere una dozzina di cose diverse che potreste fare in diversi momenti del suo ciclo di vita, ma in ogni dato programma probabilmente sto applicando lo stesso singolo processo a un flusso infinito di ordini.

D'altra parte, se sto scrivendo un'applicazione in cui abbiamo un numero di cose persistenti come un gioco per computer con una dozzina di nemici sullo schermo. Trovo OOP più attraente.

Voglio fare molte cose imprevedibili agli oggetti durante il loro ciclo di vita e nel codice che fa quelle cose che potrei non avere accesso al servizio di cui avrei bisogno. È più facile mettere i metodi sull'oggetto. muovi il personaggio, spara la sua arma, gioca l'animazione ecc ecc

Tuttavia, voglio ancora evitare catene di ereditarietà profonda e questo può essere ottenuto iniettando i servizi nell'oggetto e usando la composizione, ad esempio.

public class Character
{
    private IPathFinder pathFinder;
    public void MoveTo(x,y)
    {
       var route = this.pathFinder.FindRouteTo(x,y);
       this.target = route.NextWaypoint();
    }
}

Useremo ancora un po 'di ereditarietà, come Player: Character, Enemy: Character, ma ho un'opzione per evitare di dover tornare indietro troppo.

    
risposta data 04.08.2018 - 17:11
fonte

Leggi altre domande sui tag