Qual è la vera responsabilità di una classe?


42

Continuo a chiedermi se è legittimo usare verbi basati su sostantivi in ​​OOP.
Mi sono imbattuto in questo brillante articolo , anche se non sono ancora d'accordo con il punto.

Per spiegare un po 'di più il problema, l'articolo afferma che non dovrebbe esserci, per esempio, una FileWriterclasse, ma poiché la scrittura è un'azione dovrebbe essere un metodo della classe File. Ti renderai conto che spesso dipende dalla lingua poiché un programmatore Ruby sarebbe probabilmente contrario all'uso di una FileWriterclasse (Ruby usa il metodo File.openper accedere a un file), mentre un programmatore Java non lo farebbe.

Il mio punto di vista personale (e sì, molto umile) è che ciò infrangerebbe il principio della responsabilità singola. Quando ho programmato in PHP (perché PHP è ovviamente il miglior linguaggio per OOP, giusto?), Spesso uso questo tipo di framework:

<?php

// This is just an example that I just made on the fly, may contain errors

class User extends Record {

    protected $name;

    public function __construct($name) {
        $this->name = $name;
    }

}

class UserDataHandler extends DataHandler /* knows the pdo object */ {

    public function find($id) {
         $query = $this->db->prepare('SELECT ' . $this->getFields . ' FROM users WHERE id = :id');
         $query->bindParam(':id', $id, PDO::PARAM_INT);
         $query->setFetchMode( PDO::FETCH_CLASS, 'user');
         $query->execute();
         return $query->fetch( PDO::FETCH_CLASS );
    }


}

?>

Comprendo che il suffisso DataHandler non aggiunge nulla di rilevante ; ma il punto è che il principio della singola responsabilità ci impone che un oggetto utilizzato come modello contenente dati (che potrebbe essere chiamato Record) non dovrebbe avere la responsabilità di eseguire query SQL e l'accesso a DataBase. Ciò in qualche modo invalida il modello ActionRecord utilizzato ad esempio da Ruby on Rails.

Mi sono imbattuto in questo codice C # (yay, quarto linguaggio degli oggetti utilizzato in questo post) proprio l'altro giorno:

byte[] bytes = Encoding.Default.GetBytes(myString);
myString = Encoding.UTF8.GetString(bytes);

E devo dire che per me non ha molto senso che una Encodingo una Charsetclasse codifichi effettivamente le stringhe. Dovrebbe semplicemente essere una rappresentazione di cosa sia realmente una codifica.

Quindi, tenderei a pensare che:

  • Non è una Fileresponsabilità di classe aprire, leggere o salvare file.
  • Non è una Xmlresponsabilità di classe serializzare se stesso.
  • Non è una Userresponsabilità di classe interrogare un database.
  • eccetera.

Tuttavia, se estrapoliamo queste idee, perché dovremmo Objectavere una toStringlezione? Non è responsabilità di un'auto o di un cane convertirsi in una stringa, adesso?

Capisco che da un punto di vista pragmatico, liberarsi del toStringmetodo per la bellezza di seguire una rigorosa forma SOLIDA, che rende il codice più mantenibile rendendolo inutile, non è un'opzione accettabile.

Capisco anche che potrebbe non esserci una risposta esatta (che sarebbe più un saggio che una risposta seria) a questo, o che potrebbe essere basata sull'opinione. Tuttavia, vorrei ancora sapere se il mio approccio segue effettivamente il principio della responsabilità singola.

Qual è la responsabilità di una classe?


Objects toString è principalmente praticità e toString viene generalmente utilizzato per visualizzare rapidamente lo stato interno durante il debug
ratchet freak

3
@ratchetfreak Sono d'accordo, ma è stato un esempio (quasi uno scherzo) per dimostrare che la singola responsabilità è molto spesso rotta nel modo in cui ho descritto.
Pierre Arlaud,

8
Mentre il principio della responsabilità singola ha i suoi ardenti sostenitori, a mio avviso è una linea guida nella migliore delle ipotesi e molto allentato per la stragrande maggioranza delle scelte progettuali. Il problema con SRP è che ti ritrovi con centinaia di classi con nomi strani, rende davvero difficile trovare ciò di cui hai bisogno (o scoprire se ciò di cui hai già bisogno esiste) e cogliere il quadro generale. Preferisco di gran lunga avere meno classi ben nominate che mi diano immediatamente cosa aspettarmi da loro anche se hanno parecchie responsabilità. Se in futuro le cose cambieranno, allora dividerò la classe.
Dunk

Risposte:


24

Date alcune divergenze tra le lingue, questo può essere un argomento difficile. Quindi, sto formulando i seguenti commenti in un modo che cerca di essere il più completo possibile nel regno di OO.

Innanzitutto, il cosiddetto "principio di responsabilità singola" è un riflesso - esplicitamente dichiarato - della coesione del concetto . Leggendo la letteratura dell'epoca (intorno al '70), le persone stavano (e sono ancora) in difficoltà per definire cosa sia un modulo e come costruirli in modo da preservare le belle proprietà. Quindi, direbbero "ecco un mucchio di strutture e procedure, ne farò un modulo", ma senza criteri sul perché questo insieme di cose arbitrarie sia impacchettato insieme, l'organizzazione potrebbe finire per avere poco senso - poca "coesione". Quindi, sono emerse discussioni sui criteri.

Quindi, la prima cosa da notare qui è che, finora, il dibattito riguarda l'organizzazione e gli effetti correlati sulla manutenzione e sulla comprensibilità (per poca materia a un computer se un modulo "ha un senso").

Quindi, qualcun altro (il sig. Martin) è entrato e ha applicato lo stesso pensiero all'unità di una classe come criterio da utilizzare quando si pensa a cosa dovrebbe o non dovrebbe appartenere ad essa, promuovendo questi criteri a un principio, quello in discussione Qui. Il punto che ha sottolineato è che "Una classe dovrebbe avere solo una ragione per cambiare" .

Bene, sappiamo per esperienza che molti oggetti (e molte classi) che sembrano fare "molte cose" hanno un'ottima ragione per farlo. Il caso indesiderato sarebbero le classi che sono gonfie di funzionalità al punto da essere impenetrabili per la manutenzione, ecc. E capire quest'ultima è vedere dove il sig. Martin stava mirando a quando ha approfondito l'argomento.

Certo, dopo aver letto ciò che il sig. Martin ha scritto, dovrebbe essere chiaro che questi sono criteri di direzione e progettazione per evitare scenari problematici, non in alcun modo per perseguire alcun tipo di conformità, per non parlare di una forte conformità, specialmente quando la "responsabilità" è mal definita (e domande come "fa questo viola il principio? "sono esempi perfetti della confusione diffusa). Quindi, trovo sfortunato che si chiama principio, fuorviando le persone nel tentativo di portarlo alle ultime conseguenze, dove non farebbe nulla di buono. Lo stesso Martin ha discusso dei progetti che "fanno più di una cosa" che probabilmente dovrebbero essere mantenuti in questo modo, dal momento che la separazione produrrebbe risultati peggiori. Inoltre, ci sono molte sfide conosciute riguardo alla modularità (e questo argomento ne è un caso), non siamo in grado di avere buone risposte anche per alcune semplici domande al riguardo.

Tuttavia, se estrapoliamo queste idee, perché Object dovrebbe avere una classe toString? Non è responsabilità di un'auto o di un cane convertirsi in una stringa, adesso?

Ora, lasciami fare una pausa per dire qualcosa qui toString: c'è una cosa fondamentale comunemente trascurata quando si fa quella transizione di pensiero dai moduli alle classi e si riflette su quali metodi dovrebbero appartenere a una classe. E la cosa è l'invio dinamico (aka, late binding, "polimorfismo").

In un mondo senza "metodi prioritari", scegliere tra "obj.toString ()" o "toString (obj)" è solo una questione di preferenza di sintassi. Tuttavia, in un mondo in cui i programmatori possono modificare il comportamento di un programma aggiungendo una sottoclasse con un'implementazione distinta di un metodo esistente / ignorato, questa scelta non ha più gusto: rendere una procedura un metodo può anche renderlo un candidato per l'override, e lo stesso potrebbe non essere vero per le "procedure libere" (le lingue che supportano i multi-metodi hanno una via d'uscita da questa dicotomia). Di conseguenza, non è più una discussione solo sull'organizzazione, ma anche sulla semantica. Infine, a quale classe è associato il metodo, diventa anche una decisione di impatto (e in molti casi, finora, abbiamo poco più di linee guida per aiutarci a decidere dove appartengono le cose,

Infine, siamo di fronte a linguaggi che portano terribili decisioni di progettazione, ad esempio, costringendo uno a creare una classe per ogni piccola cosa. Quindi, quella che una volta era la ragione canonica e i criteri principali per avere oggetti (e nella classe-terra, quindi, classi) affatto, cioè avere questi "oggetti" che sono tipo di "comportamenti che si comportano anche come dati" , ma protegge la loro rappresentazione concreta (se presente) dalla manipolazione diretta a tutti i costi (e questo è il suggerimento principale per quella che dovrebbe essere l'interfaccia di un oggetto, dal punto di vista dei suoi clienti), viene offuscata e confusa.


31

[Nota: parlerò di oggetti qui. Gli oggetti sono la programmazione orientata agli oggetti, dopo tutto, non le classi.]

Qual è la responsabilità di un oggetto dipende principalmente dal tuo modello di dominio. Di solito ci sono molti modi per modellare lo stesso dominio e sceglierai un modo o l'altro in base al modo in cui verrà utilizzato il sistema.

Come tutti sappiamo, la metodologia "verbo / sostantivo" che viene spesso insegnata nei corsi introduttivi è ridicola, perché dipende così tanto da come si formula una frase. Puoi esprimere quasi tutto come un sostantivo o un verbo, con una voce attiva o passiva. Avere il tuo modello di dominio dipendente da ciò che è sbagliato, dovrebbe essere una scelta progettuale consapevole se qualcosa è un oggetto o un metodo, non solo una conseguenza accidentale di come si formulano i requisiti.

Tuttavia, si fa mostrare che, proprio come avete molte possibilità di esprimere la stessa cosa in inglese, si hanno molte possibilità di esprimere la stessa cosa nel vostro modello di dominio ... e nessuno di queste possibilità è intrinsecamente più corretta rispetto agli altri. Dipende dal contesto.

Ecco un esempio che è anche molto popolare nei corsi introduttivi di OO: un conto bancario. Che è in genere modellato come un oggetto con un balancecampo e un transfermetodo. In altre parole: il saldo del conto è costituito da dati e un trasferimento è un'operazione . Quale è un modo ragionevole per modellare un conto bancario.

Tranne, non è così che i conti bancari sono modellati nel software bancario del mondo reale (e in effetti, non è come le banche lavorano nel mondo reale). Invece, hai una distinta di transazione e il saldo del conto viene calcolato aggiungendo (e sottraendo) tutte le distinte di transazione per un conto. In altre parole: il trasferimento è dato e la bilancia è un'operazione ! (È interessante notare che questo rende anche il sistema puramente funzionale, poiché non è mai necessario modificare il saldo, sia gli oggetti del conto che quelli della transazione non devono mai cambiare, sono immutabili.)

Per quanto riguarda la tua domanda specifica toString, sono d'accordo. Preferisco di gran lunga la soluzione Haskell di una Showable classe di tipo . (Scala ci insegna che le classi di tipi si adattano perfettamente a OO.) Lo stesso vale per l'uguaglianza. L'uguaglianza molto spesso non è una proprietà di un oggetto ma del contesto in cui l'oggetto viene utilizzato. Basti pensare ai numeri in virgola mobile: quale dovrebbe essere epsilon? Ancora una volta, Haskell ha la Eqclasse di tipo.


Mi piacciono i tuoi confronti di haskell, rende la risposta un po 'più ... fresca. Cosa vorresti dire dei progetti che consentono ActionRecordsia di essere un modello sia di un richiedente del database? Indipendentemente dal contesto, sembra violare il principio della responsabilità singola.
Pierre Arlaud,

5
"... perché dipende così tanto da come si formula una frase." Sìì! Oh e adoro il tuo esempio bancario. Non ho mai saputo che negli anni ottanta mi era già stato insegnato la programmazione funzionale :) (progettazione incentrata sui dati e archiviazione indipendente dal tempo in cui i saldi ecc. Sono calcolabili e non dovrebbero essere memorizzati e l'indirizzo di qualcuno non dovrebbe essere cambiato, ma memorizzato come un nuovo fatto) ...
Marjan Venema il

1
Non direi che la metodologia Verb / Noun sia "ridicola". Direi che i progetti semplicistici falliranno e che è molto difficile ottenere il giusto. Perché, controesempio, un'API RESTful non è una metodologia Verb / Noun? Dove, per un ulteriore livello di difficoltà, i verbi sono estremamente limitati?
user949300

@ user949300: Il fatto che l'insieme dei verbi sia risolto, evita esattamente il problema che ho citato, vale a dire che puoi formulare frasi in diversi modi, rendendo ciò che è un sostantivo e ciò che è un verbo dipendente dallo stile di scrittura e non dall'analisi del dominio .
Jörg W Mittag,

8

Troppo spesso il principio di responsabilità singola diventa un principio di responsabilità zero e finisci con le classi anemiche che non fanno nulla (tranne setter e getter), il che porta a un disastro del Regno dei sostantivi .

Non sarai mai perfetto, ma meglio per una classe fare un po 'troppo che troppo poco.

Nel tuo esempio con Encoding, IMO, dovrebbe sicuramente essere in grado di codificare. Cosa pensi che dovrebbe fare invece? Il solo fatto di avere un nome "utf" è zero responsabilità. Ora, forse il nome dovrebbe essere Encoder. Ma, come diceva Konrad, i dati (quale codifica) e il comportamento (facendolo) appartengono insieme .


7

Un'istanza di una classe è una chiusura. Questo è tutto. Se pensi in questo modo, tutti i software ben progettati che guarderai appariranno corretti e tutti i software mal concepiti non lo faranno. Fammi espandere.

Se vuoi scrivere qualcosa da scrivere su un file, pensa a tutto ciò che devi specificare (al sistema operativo): nome file, metodo di accesso (lettura, scrittura, aggiunta), la stringa che vuoi scrivere.

Quindi costruisci un oggetto File con il nome file (e il metodo di accesso). L'oggetto File è ora chiuso sul nome del file (in questo caso probabilmente lo avrà preso come valore readonly / const). L'istanza del file è ora pronta per rispondere alle chiamate al metodo "write" definito nella classe. Questo richiede solo un argomento stringa, ma nel corpo del metodo write, l'implementazione, c'è anche l'accesso al nome file (o handle di file che è stato creato da esso).

Un'istanza della classe File inscatola quindi alcuni dati in una sorta di BLOB composito in modo che l'uso successivo di quell'istanza sia più semplice. Quando hai un oggetto File, non devi preoccuparti di quale sia il nome del file, ti preoccupi solo di quale stringa inserisci come argomento nella chiamata di scrittura. Per ripetere: l'oggetto File incapsula tutto ciò che non è necessario sapere su dove sta andando la stringa che si desidera scrivere.

La maggior parte dei problemi che si desidera risolvere sono stratificati in questo modo: cose create in anticipo, cose create all'inizio di ogni iterazione di un qualche tipo di ciclo di processo, quindi cose diverse dichiarate nelle due metà di un if else, quindi cose create in l'inizio di una sorta di sub-loop, quindi le cose dichiarate come parte dell'algoritmo all'interno di quel loop ecc. Man mano che aggiungi sempre più chiamate di funzione allo stack, stai facendo codice sempre più preciso, ma ogni volta che hai raggruppato i dati negli strati inferiori dello stack in una sorta di bella astrazione che ne facilita l'accesso. Guarda cosa stai facendo sta usando "OO" come una sorta di framework di gestione della chiusura per un approccio di programmazione funzionale.

La soluzione di collegamento ad alcuni problemi comporta lo stato mutevole degli oggetti, che non è così funzionale: stai manipolando le chiusure dall'esterno. Stai rendendo alcune cose della tua classe "globali", almeno per lo scopo sopra. Ogni volta che scrivi un setter, cerca di evitarli. Direi che è qui che hai sbagliato la responsabilità della tua classe o delle altre classi in cui opera. Ma non preoccuparti troppo: a volte è pragmatico muoversi in un po 'di stato mutevole per risolvere rapidamente alcuni tipi di problemi, senza troppo carico cognitivo, e in realtà fare qualcosa.

Quindi, in sintesi, per rispondere alla tua domanda: qual è la vera responsabilità di una classe? È presentare una specie di piattaforma, basata su alcuni dati sottostanti, per ottenere un tipo di operazione più dettagliato. Serve per incapsulare metà dei dati coinvolti in un'operazione che cambia meno frequentemente rispetto all'altra metà dei dati (Pensa al curry ...). Ad esempio nome file vs stringa da scrivere.

Spesso questi incapsulamenti assomigliano superficialmente all'oggetto / oggetto del mondo reale nel dominio problematico ma non si lasciano ingannare. Quando crei una classe Car, in realtà non è un'auto, è una raccolta di dati che forma una piattaforma su cui realizzare determinati tipi di cose che potresti considerare di voler fare su un'auto. Formare una rappresentazione in formato stringa (toString) è una di queste cose: sputare tutti quei dati interni. Ricorda che a volte una classe Car potrebbe non essere la classe giusta anche se il dominio problematico riguarda solo le auto. Contrariamente a Kingdom of nouns, sono le operazioni, i verbi su cui dovrebbe basarsi una classe.


4

Capisco anche che potrebbe non esserci una risposta esatta (che sarebbe più un saggio che una risposta seria) a questo, o che potrebbe essere basata sull'opinione. Tuttavia, vorrei ancora sapere se il mio approccio segue effettivamente il principio della responsabilità singola.

Mentre lo fa, potrebbe non necessariamente essere un buon codice. Oltre al semplice fatto che qualsiasi tipo di regola seguita ciecamente porta a un codice errato, stai iniziando una premessa non valida: gli oggetti (di programmazione) non sono (fisici) oggetti.

Gli oggetti sono un insieme di fasci di dati e operazioni coerenti (e talvolta solo uno dei due). Mentre questi spesso modellano oggetti del mondo reale, la differenza tra un modello al computer di qualcosa e quella stessa cosa richiede differenze.

Prendendo quella linea dura tra un "sostantivo" che rappresenta una cosa e altre cose che li consumano, stai andando contro un vantaggio chiave della programmazione orientata agli oggetti (avere funzione e stato insieme in modo che la funzione possa proteggere gli invarianti dello stato) . Peggio ancora, stai cercando di rappresentare il mondo fisico nel codice, che come la storia ha dimostrato, non funzionerà bene.


4

Mi sono imbattuto in questo codice C # (yay, quarto linguaggio degli oggetti utilizzato in questo post) proprio l'altro giorno:

byte[] bytes = Encoding.Default.GetBytes(myString);
myString = Encoding.UTF8.GetString(bytes);

E devo dire che per me non ha molto senso che una Encodingo una Charsetclasse codifichi effettivamente le stringhe. Dovrebbe semplicemente essere una rappresentazione di cosa sia realmente una codifica.

In teoria sì, ma penso che C # faccia un giustificato compromesso per motivi di semplicità (al contrario dell'approccio più rigoroso di Java).

Se Encodingsolo rappresentassi (o identificassi) una certa codifica - diciamo, UTF-8 - allora ovviamente avresti bisogno anche di una Encoderclasse, in modo che tu possa implementarla GetBytessu di essa - ma allora devi gestire la relazione tra Encoders e Encodings, quindi noi finiamo col nostro buon vecchio

EncodersFactory.getDefaultFactory().createEncoder(new UTF8Encoding()).getBytes(myString)

così brillantemente illuminato in quell'articolo a cui ti sei collegato (ottima lettura, a proposito).

Se ti capisco bene, mi stai chiedendo dove si trova la linea.

Bene, sul lato opposto dello spettro è l'approccio procedurale, non difficile da implementare nei linguaggi OOP con l'uso di classi statiche:

HelpfulStuff.GetUTF8Bytes(myString)

Il grosso problema è che quando l'autore di HelpfulStufffoglie e io sono seduti davanti al monitor, non ho modo di sapere dove GetUTF8Bytesè implementato.

Ti sembro per essa in HelpfulStuff, Utils, StringHelper?

Signore, aiutami se il metodo è effettivamente implementato in modo indipendente in tutti e tre questi ... il che succede molto, tutto ciò che serve è che il ragazzo prima di me non sapesse nemmeno dove cercare quella funzione, e credeva che non ce ne fosse eppure ne hanno svelato un altro e ora li abbiamo in abbondanza.

Per me, la progettazione corretta non riguarda il soddisfacimento di criteri astratti, ma la mia facilità di proseguire dopo essermi seduto davanti al tuo codice. Ora come va

EncodersFactory.getDefaultFactory().createEncoder(new UTF8Encoding()).getBytes(myString)

tasso in questo aspetto? Anche male. Non è una risposta che voglio sentire quando grido al mio compagno di lavoro "come posso codificare una stringa in una sequenza di byte?" :)

Quindi seguirei un approccio moderato e sarei liberale riguardo alla singola responsabilità. Preferirei che la Encodingclasse identificasse un tipo di codifica e facesse la codifica effettiva: dati non separati dal comportamento.

Penso che passi come un modello di facciata, che aiuta a ingrassare gli ingranaggi. Questo modello legittimo viola SOLID? Una domanda correlata: il modello di facciata viola l'SRP?

Si noti che questo potrebbe essere il modo in cui i byte codificati vengono implementati internamente (nelle librerie .NET). Le classi non sono solo pubblicamente visibili e solo questa API "facciata" è esposta all'esterno. Non è così difficile verificare se è vero, sono solo un po 'troppo pigro per farlo adesso :) Probabilmente non lo è, ma ciò non invalida il mio punto.

Puoi scegliere di semplificare l'implementazione pubblicamente visibile per motivi di cordialità pur mantenendo internamente un'implementazione canonica più severa.


1

In Java l'astrazione utilizzata per rappresentare i file è un flusso, alcuni flussi sono unidirezionali (sola lettura o sola scrittura) altri sono bidirezionali. Per Ruby la classe File è l'astrazione che rappresenta i file del sistema operativo. Quindi la domanda diventa qual è la sua unica responsabilità? In Java, la responsabilità di FileWriter è di fornire un flusso unidirezionale di byte connesso a un file del sistema operativo. In Ruby, la responsabilità di File è fornire accesso bidirezionale a un file di sistema. Entrambi soddisfano l'SRP e hanno solo responsabilità diverse.

Non è responsabilità di un'auto o di un cane convertirsi in una stringa, adesso?

Certo, perché no? Mi aspetto che la classe Car rappresenti appieno un'astrazione Car. Se ha senso utilizzare l'astrazione dell'auto dove è necessaria una stringa, allora mi aspetto che la classe Car supporti la conversione in una stringa.

Per rispondere direttamente alla domanda più ampia, la responsabilità di una classe è quella di agire come un'astrazione. Quel lavoro può essere complesso, ma questo è un po 'il punto, un'astrazione dovrebbe nascondere cose complesse dietro un'interfaccia più facile da usare, meno complessa. L'SRP è una linea guida di progettazione, quindi ti concentri sulla domanda "cosa rappresenta questa astrazione?". Se sono necessari più comportamenti diversi per astrarre completamente il concetto, così sia.


0

La classe può avere più responsabilità. Almeno nel classico OOP incentrato sui dati.

Se stai applicando il principio della responsabilità singola nella sua massima misura, otterrai un design incentrato sulla responsabilità in cui praticamente ogni metodo non helper appartiene alla propria classe.

Penso che non ci sia nulla di sbagliato nell'estrarre un pezzo di complessità da una classe di base come nel caso di File e FileWriter. Il vantaggio è che si ottiene una chiara separazione del codice che è responsabile di una determinata operazione e che è possibile sovrascrivere (sottoclasse) solo quel pezzo di codice invece di sovrascrivere l'intera classe di base (ad esempio File). Tuttavia, l'applicazione sistematica sembra essere eccessiva per me. Ottieni semplicemente molte più classi da gestire e per ogni operazione devi invocare la rispettiva classe. È la flessibilità che ha un certo costo.

Queste classi estratti dovrebbero avere nomi molto descrittivi basati sul funzionamento essi contengono, ad esempio <X>Writer, <X>Reader, <X>Builder. Nomi come <X>Manager, <X>Handlernon descrivono affatto l'operazione e se vengono chiamati in questo modo perché contengono più di un'operazione, non è chiaro cosa sia stato realizzato. Hai separato la funzionalità dai suoi dati, rompi ancora il singolo principio di responsabilità anche in quella classe estratta e se stai cercando un certo metodo potresti non sapere dove cercarlo (se in <X>o <X>Manager).

Ora, il tuo caso con UserDataHandler è diverso perché non esiste una classe base (la classe che contiene dati effettivi). I dati per questa classe sono archiviati in una risorsa esterna (database) e si sta utilizzando un API per accedervi e manipolarli. La tua classe User rappresenta un utente di runtime, che è in realtà qualcosa di diverso rispetto a un utente persistente e la separazione delle responsabilità (preoccupazioni) potrebbe tornare utile qui soprattutto se hai molte logiche di runtime per utente.

Probabilmente hai chiamato UserDataHandler in questo modo perché in pratica vi sono solo funzionalità in quella classe e nessun dato reale. Tuttavia, è anche possibile estrarre da tale fatto e considerare i dati nel database come appartenenti alla classe. Con questa astrazione puoi nominare la classe in base a ciò che è e non a ciò che fa. Puoi dargli un nome come UserRepository, che è piuttosto elegante e suggerisce anche l'uso del modello di repository .

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.