Design di classe orientato agli oggetti


12

Mi chiedevo un buon design di classe orientato agli oggetti. In particolare, faccio fatica a decidere tra queste opzioni:

  1. metodo statico vs istanza
  2. metodo senza parametri o valore restituito vs metodo con parametri e valore restituito
  3. sovrapposizione vs funzionalità metodo distinto
  4. metodo privato vs pubblico

Esempio 1:

Questa implementazione utilizza metodi di istanza, senza valore o parametri di ritorno, senza funzionalità sovrapposte e tutti i metodi pubblici

XmlReader reader = new XmlReader(url);
reader.openUrl();
reader.readXml();
Document result = reader.getDocument();

Esempio 2:

Questa implementazione utilizza metodi statici, con valori e parametri di ritorno, con funzionalità sovrapposte e metodi privati

Document result = XmlReader.readXml(url); 

Nell'esempio uno, tutti i metodi sono di istanza pubblica, il che li rende facili da testare. Sebbene tutti i metodi siano distinti, readXml () dipende da openUrl () in quanto openUrl () deve essere chiamato per primo. Tutti i dati sono dichiarati nei campi di istanza, quindi non ci sono valori o parametri di ritorno in nessun metodo, tranne nel costruttore e negli accessori.

Nell'esempio due, solo un metodo è pubblico, gli altri sono statici privati, il che li rende difficili da testare. I metodi si sovrappongono in quel readXml () chiama openUrl (). Non ci sono campi, tutti i dati vengono passati come parametri nei metodi e il risultato viene restituito immediatamente.

Quali principi devo seguire per una corretta programmazione orientata agli oggetti?


3
Le cose statiche vanno male quando si esegue il multi-threading. L'altro giorno, ho avuto un XMLWriter statico, come XMLWriter.write (data, fileurl). Tuttavia, poiché disponeva di un FileStream statico privato, utilizzando questa classe da più thread contemporaneamente, il secondo thread sovrascriveva i primi thread FileStream, causando un errore che sarebbe molto difficile da trovare. Le classi statiche con membri statici + multi-threading sono una ricetta per il disastro.
Per Alexandersson,

1
@Paxinum. Il problema che descrivi è un problema di stato, non un problema "statico". Se si utilizzava un singleton con membri non statici si avrebbe lo stesso problema con il multi-threading.
mike30,

2
@Per Alexandersson I metodi statici non sono male in relazione alla concorrenza. Lo stato statico è cattivo. Ecco perché la programmazione funzionale, in cui tutti i metodi sono statici, funziona molto bene in situazioni simultanee.
Yuli Bonner,

Risposte:


11

L'esempio 2 è piuttosto negativo per i test ... e non intendo dire che non puoi testare gli interni. Inoltre, non puoi sostituire il tuo XmlReaderoggetto con un oggetto finto in quanto non hai alcun oggetto.

L'esempio 1 è inutilmente difficile da usare. Che dire

XmlReader reader = new XmlReader(url);
Document result = reader.getDocument();

che non è più difficile da usare del metodo statico.

Cose come aprire l'URL, leggere XML, convertire byte in stringhe, analizzare, chiudere socket e quant'altro, non sono interessanti. Creare un oggetto e usarlo è importante.

Quindi, IMHO, il corretto design OO è quello di rendere pubbliche solo le due cose (a meno che per qualche motivo non siano realmente necessari i passaggi intermedi). L'elettricità statica è cattiva.


-1. In realtà PUOI sostituire XmlReader con un oggetto finto. Non con un framework moick open source senza cervello, ma con un buon livello industriale puoi;) Costa un paio di centinaia di dollari per sviluppatore ma fa miracoli per testare la funzionalità sigillata nelle API che pubblichi.
TomTom,

2
+1 per non ascoltare la presentazione di TomTom. Quando voglio una copertina preferirei scrivere qualcosa come Document result = new XmlReader(url).getDocument();Why? In modo che io possa aggiornarlo Document result = whateverReader.getDocument();e whateverReaderavermi consegnato da qualcos'altro.
candied_orange,

6

Ecco la cosa: non esiste una risposta giusta e non esiste una definizione assoluta di "design orientato agli oggetti" (alcune persone te ne offriranno una, ma sono ingenui ... dai loro tempo).

Tutto dipende dai tuoi obiettivi.

Sei un artista e il foglio è vuoto. Puoi disegnare un ritratto laterale in bianco e nero delicatamente disegnato a matita o un dipinto astratto con enormi squarci di neon misti. O qualsiasi altra via di mezzo.

Quindi, QUELLO CHE GIUSTA per il problema che stai risolvendo? Quali sono i reclami delle persone che hanno bisogno di usare le tue lezioni per lavorare con XML? Cosa c'è di difficile nel loro lavoro? Che tipo di codice stanno cercando di scrivere che circonda le chiamate alla tua libreria e come puoi aiutarlo a fluire meglio?

Vorrebbero più succinta? Vorrebbero che fosse molto intelligente nel capire i valori predefiniti per i parametri, quindi non devono specificare molto (o niente) e indovina correttamente? Riesci ad automatizzare le attività di installazione e pulizia richieste dalla tua libreria per cui è impossibile per loro dimenticare quei passaggi? Cos'altro puoi fare per loro?

Al diavolo, quello che probabilmente devi fare è codificarlo in 4 o 5 modi diversi, quindi indossare il cappello del consumatore e scrivere il codice che utilizza tutti e 5, e vedere quale si sente meglio. Se non puoi farlo per l'intera libreria, fallo per un sottoinsieme. E devi aggiungere anche altre alternative alla tua lista - che dire di un'interfaccia fluida, o di un approccio più funzionale, o di parametri nominati o qualcosa basato su DynamicObject in modo da poter inventare "pseudo-metodi" significativi che li aiutino su?

Perché jQuery è il re in questo momento? Perché Resig e il team hanno seguito questo processo, fino a quando non si sono imbattuti in un principio sintattico che ha incredibilmente ridotto la quantità di codice JS necessario per lavorare con la dom e gli eventi. Quel principio sintattico non era chiaro a loro o a chiunque altro quando hanno iniziato. L'hanno trovato.

Come programmatore, questa è la tua più alta vocazione. Tenti a tentoni nell'oscurità provando cose finché non le trovi. Quando lo farai, lo saprai. E offrirai ai tuoi utenti un enorme salto di produttività. E questo è il design (nel regno del software).


1
Ciò implica che non ci sono differenze tra le migliori pratiche e altre pratiche diverse da come "si sente". Questo è il modo in cui ti ritrovi con palle di fango non mantenibili, perché per molti sviluppatori che si estendono oltre i confini della Classe, ecc., "Sembra" semplicemente fantastico.
Amy Blankenship,

@Amy Blankenship, direi inequivocabilmente che non esiste un "modo migliore" per fare le scelte che l'OP sta chiedendo. Dipende da un milione di cose e ci sono un milione di gradi di libertà. Tuttavia, penso che ci sia un posto per le "Best Practices", ovvero in un ambiente di squadra , in cui sono già state fatte alcune scelte , e abbiamo bisogno che il resto della squadra sia coerente con quelle precedenti. In altre parole, in un contesto specifico , potrebbero esserci delle ragioni per etichettare alcune cose come "migliori pratiche". Ma l'OP non ha fornito alcun contesto. Sta costruendo qualcosa e ...
Charlie Flowers il

... affronta tutte queste possibili scelte. Non esiste una "risposta giusta" a queste scelte. È guidato dagli obiettivi e dai punti deboli del sistema. Vi garantisco che i programmatori Haskell non pensano che tutti i metodi dovrebbero essere metodi di istanza. E i programmatori del kernel Linux non pensano che rendere le cose accessibili a TDD sia molto importante. E i programmatori di giochi C ++ preferiscono spesso raggruppare i loro dati in una struttura di dati stretta nella memoria piuttosto che incapsulare tutto in oggetti. Ogni "Best Practice" è solo una "best practice" in un dato contesto ed è un anti-modello in qualche altro contesto.
Charlie Flowers,

@AmyBlankenship Un'altra cosa: non sono d'accordo sul fatto che estendersi oltre i confini della Classe "sia semplicemente fantastico". Porta a palle di fango non mantenibili, che sembrano orribili . Penso che stai cercando di risolvere il problema che alcuni lavoratori sono sciatti / non motivati ​​/ molto inesperti. In tal caso, qualcuno che è attento, motivato ed esperto dovrebbe fare le scelte chiave e chiamarle "Best Practices". Ma la persona che sceglie quelle "Best Practices" sta ancora facendo delle scelte in base a ciò che "si sente giusto" e non ci sono risposte giuste. Stai solo controllando chi fa le scelte.
Charlie Flowers,

Ho lavorato con diversi programmatori che si consideravano come senior e pensavano in quel modo dal management che credeva fermamente che statica e Singleton fossero assolutamente il modo giusto di gestire i problemi di comunicazione. La parte statica di questa domanda non sarebbe nemmeno stata posta se raggiungere i confini della Classe in quel modo "sembri" sbagliato per gli sviluppatori, né la risposta che sostiene l'alternativa statica riceverebbe voti positivi.
Amy Blankenship,

3

La seconda opzione è migliore in quanto è più semplice da usare per le persone (anche se sei solo tu), molto più semplice.

Per i test unitari testerei semplicemente l'interfaccia e non gli interni, se vuoi davvero spostare gli interni da privati ​​a protetti.


2
È inoltre possibile rendere privato il pacchetto dei metodi (impostazione predefinita), se i casi di test si trovano nello stesso pacchetto, ai fini del test dell'unità.

Un buon punto: è meglio.

3

1. Statico vs. istanza

Penso che ci siano linee guida molto chiare su cosa sia un buon design OO e cosa no. Il problema è che la blogosfera rende difficile separare il buono dal cattivo e il cattivo. Puoi trovare una sorta di riferimento a supporto anche della peggiore pratica che ti viene in mente.

E la peggior pratica che mi viene in mente è lo stato globale, comprese le statistiche che hai citato e il Singleton preferito da tutti. Alcuni estratti del classico articolo di Misko Hevery sull'argomento .

Per comprendere veramente le dipendenze, gli sviluppatori devono leggere ogni riga di codice. Provoca un'azione spettrale a distanza: quando si eseguono suite di test, lo stato globale mutato in un test può causare il fallimento inaspettato di un test successivo o parallelo. Rompere la dipendenza statica usando l'iniezione manuale o Guice.

L'azione spettrale a distanza è quando eseguiamo una cosa che crediamo sia isolata (dal momento che non abbiamo passato alcun riferimento) ma interazioni impreviste e cambiamenti di stato avvengono in posizioni distanti del sistema di cui non abbiamo parlato con l'oggetto. Questo può avvenire solo tramite lo stato globale.

Forse non ci hai mai pensato prima, ma ogni volta che usi lo stato statico, crei canali di comunicazione segreti e non li chiarisci nell'API. Spooky Action at a Distance costringe gli sviluppatori a leggere ogni riga di codice per comprendere le potenziali interazioni, ridurre la produttività degli sviluppatori e confondere i nuovi membri del team.

Ciò si riduce al fatto che non è necessario fornire riferimenti statici a qualsiasi cosa che abbia una sorta di stato memorizzato. L'unico posto in cui uso la statica è per le costanti enumerate, e ho dei dubbi anche su questo.

2. Metodi con parametri di input e valori di ritorno vs. metodi con nessuno

La cosa che devi capire è che i metodi che non hanno parametri di input e parametri di output sono praticamente garantiti per funzionare su una sorta di stato memorizzato internamente (altrimenti, cosa stanno facendo?). Ci sono intere lingue che si basano sull'idea di evitare lo stato memorizzato.

Ogni volta che hai uno stato memorizzato, hai la possibilità di effetti collaterali, quindi assicurati di usarlo sempre consapevolmente. Questo implica che dovresti preferire le funzioni con input e / o output definiti.

E, in effetti, le funzioni che hanno definito ingressi e uscite sono molto più facili da testare: non è necessario eseguire una funzione qui e andare lì per vedere cosa è successo e non è necessario impostare una proprietà da qualche parte altrimenti prima di eseguire la funzione sotto test.

È inoltre possibile utilizzare questo tipo di funzione in modo sicuro come statica. Tuttavia, non lo farei, perché se in seguito volessi utilizzare un'implementazione leggermente diversa di quella funzione da qualche parte, piuttosto che fornire un'istanza diversa con la nuova implementazione, sono bloccato senza alcun modo per sostituire la funzionalità.

3. Sovrapposizione vs. distinta

Non capisco la domanda. Quale sarebbe il vantaggio in 2 metodi sovrapposti?

4. Privato vs. Pubblico

Non esporre nulla che non è necessario esporre. Tuttavia, non sono neanche un grande fan del privato. Non sono uno sviluppatore C #, ma uno sviluppatore ActionScript. Ho trascorso molto tempo nel codice Adobe Flex Framework, che è stato scritto intorno al 2007. E hanno fatto delle scelte davvero sbagliate su cosa rendere privato, il che rende un incubo cercare di estendere le loro lezioni.

Quindi, a meno che tu non pensi di essere un architetto migliore degli sviluppatori Adobe intorno al 2007 (dalla tua domanda, direi che hai ancora qualche anno prima che tu abbia la possibilità di avanzare tale pretesa), probabilmente vorrai semplicemente impostare come protetto .


Ci sono alcuni problemi con i tuoi esempi di codice che indicano che non sono ben progettati, quindi non è possibile scegliere A o B.

Per prima cosa, probabilmente dovresti separare la creazione del tuo oggetto dal suo uso . Quindi di solito non avresti il new XMLReader()diritto proprio accanto a dove viene utilizzato.

Inoltre, come dice @djna, dovresti incapsulare i metodi utilizzati nei tuoi XML Reader, quindi la tua API (esempio di istanza) potrebbe essere semplificata per:

_document Document = reader.read(info);

Non so come funzioni C #, ma dal momento che ho lavorato con una serie di tecnologie Web, sarei sospettoso che non saresti sempre in grado di restituire immediatamente un documento XML (tranne forse come una promessa o un tipo futuro oggetto), ma non posso darti consigli su come gestire un carico asincrono in C #.

Si noti che con questo approccio, è possibile creare diverse implementazioni che possono prendere un parametro che dice loro dove / cosa leggere e restituire un oggetto XML e scambiarli in base alle esigenze del progetto. Ad esempio, potresti leggere direttamente da un database, da un negozio locale o, come nell'esempio originale, da un URL. Non puoi farlo se usi un metodo statico.


2

Concentrarsi sulla prospettiva del cliente.

IReader reader = new XmlReader.readXml(url);  // or injection, or factory or ...
Document document = reader.read();

I metodi statici tendono a limitare l'evoluzione futura, il nostro cliente sta lavorando in termini di un'interfaccia fornita da forse diverse implementazioni.

Il problema principale con il tuo linguaggio aperto / letto è che il cliente deve conoscere l'ordinamento in cui chiamare i metodi, quando vuole solo fare un semplice lavoro. Ovvio qui, ma in una classe più ampia è tutt'altro che ovvio.

Il metodo principale da testare è read (). I metodi interni possono essere resi visibili ai programmi di test rendendoli né pubblici né privati ​​e inserendo i test nello stesso pacchetto: i test possono ancora essere separati dal codice rilasciato.


I metodi con visibilità predefinita sono ancora visibili se la suite di test si trova in un progetto diverso?
siamii,

Java non conosce i progetti. I progetti sono un costrutto IDE. Il compilatore e JVM esaminano i pacchetti in cui si trovano le classi testate e tester - stesso pacchetto, la visibilità predefinita è consentita. In Eclipse utilizzo un singolo progetto, con due diverse directory di origine. Ho appena provato con due progetti, funziona.

2

metodo statico vs istanza

In pratica, i metodi statici sono generalmente limitati alle classi di utilità e non dovrebbero ingombrare oggetti, gestori, controller o DAO del dominio. I metodi statici sono molto utili quando tutti i riferimenti necessari possono essere ragionevolmente passati come parametri e forniscono alcune funzionalità che possono essere riutilizzate in molte classi. Se ti ritrovi a utilizzare un metodo statico come soluzione alternativa per avere un riferimento a un oggetto istanza, chiediti perché non hai semplicemente quel riferimento.

metodo senza parametri o valore restituito vs metodo con parametri e valore restituito

Se non hai bisogno di parametri sul metodo, non aggiungerli. Lo stesso vale con il valore restituito. Tenendo presente questo, si semplifica il codice e si assicura che non si sta codificando per una moltitudine di scenari che non finiscono mai.

sovrapposizione con funzionalità metodo distinto

È una buona idea provare a evitare la sovrapposizione di funzionalità. A volte può essere difficile, ma quando è necessario un cambio di logica, è molto più semplice cambiare un metodo che viene riutilizzato, piuttosto che cambiare un sacco di metodi con funzionalità simili

metodo privato vs pubblico

Comunemente getter, setter e costruttori dovrebbero essere pubblici. Tutto il resto che vorrai provare a mantenere privato a meno che non ci sia un caso in cui un'altra classe deve eseguirlo. Mantenere i metodi predefiniti come privati ​​aiuterà a mantenere l' incapsulamento . Lo stesso vale per i campi, abituarsi al privato come predefinito


1

Non risponderò alla tua domanda, ma penso che un problema sia stato creato dai termini che hai usato. Per esempio

XmlReader.read => twice "read"

Penso che tu abbia bisogno di un XML, quindi creerò un oggetto XML che può essere creato da un tipo testuale (non so C # ... in Java si chiama String). Per esempio

class XML {
    XML(String text) { [...] }
}

Puoi provarlo ed è chiaro. Quindi, se hai bisogno di una factory, puoi aggiungere un metodo factory (e può essere statico come il tuo secondo esempio). Per esempio

class XML {
    XML(String text) { [...] }

    static XML fromUrl(url) { [...] }

}

0

Puoi seguire alcune semplici regole. Aiuta a comprendere i motivi delle regole.

metodo statico vs istanza

Per i metodi non è necessario prendere questa decisione consapevolmente. Se sembra che il tuo metodo non stia utilizzando alcun membro del campo (il tuo analizzatore preferito dovrebbe dirti così), dovresti aggiungere la parola chiave statica.

metodo senza parametri o valore restituito vs metodo con parametri e valore restituito

La seconda opzione è migliore a causa dell'ambito. Si dovrebbe sempre mantenere il campo di applicazione stretto. Raggiungere le cose di cui hai bisogno è male, dovresti avere input, lavorarci su localmente e restituire un risultato. La tua prima opzione è sbagliata per lo stesso motivo per cui le variabili globali sono in genere cattive: sono significative solo per una parte del tuo codice ma sono visibili (e quindi rumorose) ovunque e possono essere manomesse da qualsiasi luogo. Questo rende difficile ottenere un quadro completo della tua logica.

sovrapposizione con funzionalità metodo distinto

Non penso che questo sia un problema. I metodi che chiamano altri metodi vanno bene se questo riduce le attività in piccoli blocchi distintamente funzionali.

metodo privato vs pubblico

Rendi tutto privato a meno che tu non ne abbia bisogno per essere pubblico. L'utente della tua classe può fare a meno del rumore, vuole vedere solo ciò che conta per lui.

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.