Design di classe orientato agli oggetti

11

Mi stavo chiedendo un buon design di classe orientato agli oggetti. In particolare, ho difficoltà a decidere tra queste opzioni:

  1. statico rispetto al metodo di istanza
  2. metodo senza parametri o valore di ritorno rispetto a metodo con parametri e valore di ritorno
  3. funzionalità di metodo distinto rispetto a
  4. privato vs metodo pubblico

Esempio 1:

Questa implementazione utilizza metodi di istanza, senza alcun valore di ritorno o parametri, senza alcuna funzionalità sovrapposta 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 istanze pubbliche, il che li rende facili da testare. Sebbene tutti i metodi siano distinti, readXml () dipende da openUrl () in cui openUrl () deve essere chiamato per primo. Tutti i dati sono dichiarati nei campi di istanza, quindi non ci sono valori di ritorno o parametri in alcun metodo, tranne nel costruttore e nei metodi di accesso.

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

Quali principi dovrei seguire per fare una corretta programmazione orientata agli oggetti?

    
posta siamii 23.07.2011 - 07:20
fonte

7 risposte

7

L'esempio 2 è piuttosto negativo per i test ... e non intendo che non sia possibile testare gli interni. Inoltre, non puoi sostituire il tuo oggetto XmlReader con un oggetto fittizio perché non hai alcun oggetto.

L'Esempio 1 è inutilmente difficile da usare. Che dire di

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

che non è più difficile da usare rispetto al metodo statico.

Sembra che l'apertura dell'URL, la lettura di XML, la conversione di byte in stringhe, l'analisi, la chiusura di socket e quant'altro non siano interessanti. Creare un oggetto e usarlo è importante.

Quindi IMHO il corretto OO Design è di rendere pubbliche solo le due cose (a meno che tu non abbia davvero bisogno dei passaggi intermedi per qualche motivo). Static is evil.

    
risposta data 23.07.2011 - 07:39
fonte
5

Ecco la cosa - non c'è una risposta giusta, e non esiste una definizione assoluta di "corretta progettazione orientata agli oggetti" (alcune persone te ne offriranno una, ma sono ingenue ... dai loro il tempo).

Tutto si riduce ai tuoi OBIETTIVI.

Sei un artista e il foglio è vuoto. È possibile disegnare un ritratto laterale in bianco e nero delicato e finemente a matita, o un dipinto astratto con enormi squarci di neons misti. O qualsiasi altra via di mezzo.

Quindi cosa SENTIENE per il problema che stai risolvendo? Quali sono le lamentele 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 in che modo puoi aiutarlo a scorrere meglio per loro?

Gradirebbero più concisione? Gli piacerebbe che fosse molto intelligente nel capire i valori di default dei parametri, quindi non devono specificare molto (o nulla) e suppone correttamente? È possibile automatizzare le attività di impostazione e pulizia richieste dalla libreria per impedire loro di dimenticarle? Cos'altro puoi fare per loro?

Diavolo, quello che probabilmente devi fare è codificarlo in 4 o 5 modi diversi, quindi mettere il tuo cappello del consumatore e scrivere il codice che usa tutti i 5, e vedere quale si sente meglio. Se non puoi farlo per l'intera libreria, fallo per un sottoinsieme. E hai bisogno di aggiungere anche altre alternative alla tua lista - che dire di un'interfaccia fluente, o un approccio più funzionale, o parametri con nome, o qualcosa basato su DynamicObject in modo da poter creare "pseudo-metodi" significativi che li aiutino fuori?

Perché jQuery è il re in questo momento? Poiché Resig e il team hanno seguito questo processo, fino a quando non hanno trovato un principio sintattico che incredibilmente ha ridotto la quantità di codice JS necessaria per lavorare con dom ed eventi. Quel principio sintattico non era chiaro a loro né a nessun altro quando iniziarono. L'hanno TROVATO.

Come programmatore, questo è ciò che è la tua più alta vocazione. Ti aggiri nel buio cercando cose finché non lo trovi. Quando lo farai, lo saprai. E darai ai tuoi utenti un salto di produttività enorme . E questo è il design (nel regno del software).

    
risposta data 23.07.2011 - 09:31
fonte
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 delle unità testerei solo l'interfaccia e non i componenti interni se vuoi veramente spostare gli interni da privato a protetto.

    
risposta data 23.07.2011 - 07:25
fonte
2

Concentrati 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 possibili implementazioni diverse.

Il problema principale con il tuo linguaggio di apertura / lettura è che il client deve conoscere l'ordine in cui chiamare i metodi, quando vuole solo ottenere un lavoro semplice. Ovvio qui, ma in una classe più ampia è tutt'altro che scontato.

Il metodo principale da testare è read (). I metodi interni possono essere resi visibili per testare i programmi rendendoli né pubblici né privati e mettendo test nello stesso pacchetto - i test possono essere mantenuti separati dal codice rilasciato.

    
risposta data 23.07.2011 - 07:36
fonte
1

static vs instance method

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

method with no parameters or return value vs method with parameters and return value

Se non hai bisogno di parametri sul metodo, non aggiungerli. Lo stesso vale per il valore di ritorno. Tenere questo a mente semplificherà il tuo codice e ti assicurerà che non stai codificando per una moltitudine di scenari che non finiscono mai.

overlapping vs distinct method functionality

È una buona idea cercare di evitare la sovrapposizione delle 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

private vs public method

Gettatori, setter e costruttori comuni dovrebbero essere pubblici. Tutto il resto vorrete provare a mantenere privato a meno che non ci sia un caso in cui un'altra classe ha bisogno di eseguirla. Mantenere i metodi predefiniti come privati aiuterà a mantenere Incapsulamento . Lo stesso vale per i campi, abituarsi al privato come predefinito

    
risposta data 25.04.2013 - 19:26
fonte
1

1. Statico contro istanza

Penso che ci siano linee guida molto chiare su ciò che è buono design OO e cosa no. Il il problema è che la blogosfera rende difficile separare il buono dal brutto e il cattivo. Puoi trovare alcuni tipi di riferimento che supportano anche le peggiori pratiche a cui puoi pensare.

E la peggiore pratica che mi viene in mente è Global State, inclusa la statica che hai menzionato e il singleton preferito di tutti. Alcuni estratti dall'articolo di Misko Hevery classico sull'argomento .

To really understand the dependencies, developers must read every line of code. It causes Spooky Action at a Distance: when running test suites, global state mutated in one test can cause a subsequent or parallel test to fail unexpectedly. Break the static dependency using manual or Guice dependency injection.

Spooky Action at a Distance is when we run one thing that we believe is isolated (since we did not pass any references in) but unexpected interactions and state changes happen in distant locations of the system which we did not tell the object about. This can only happen via global state.

You may not have thought of it this way before, but whenever you use static state, you’re creating secret communication channels and not making them clear in the API. Spooky Action at a Distance forces developers to read every line of code to understand the potential interactions, lowers developer productivity, and confuses new team members.

Ciò a cui si riduce è che non si dovrebbero fornire riferimenti statici a qualcosa che ha una sorta di stato memorizzato. L'unico posto in cui utilizzo la statistica è per le costanti numerate e ho anche dei dubbi in proposito.

2. Metodi con parametri di input e valori di ritorno rispetto a metodi con none

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

Ogni volta che hai lo stato memorizzato, hai la possibilità di effetti collaterali, quindi assicurati di utilizzarlo sempre in modo consapevole. Ciò implica che dovresti preferire funzioni con input e / o output definiti.

E, in effetti, le funzioni che hanno input e output definiti sono molto più facili da testare - non devi eseguire una funzione qui e andare a guardare là per vedere cosa è successo, e non devi impostare una proprietà da qualche altra parte prima di eseguire la funzione in prova.

Puoi anche tranquillamente usare questo tipo di funzione come statica. Tuttavia, non lo farei, perché se in seguito avrei voluto utilizzare un'implementazione leggermente diversa di quella funzione da qualche parte, anziché fornire un'istanza diversa con la nuova implementazione, non sono in grado di sostituire la funzionalità.

3. Sovrapposizione e Distinzione

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

4. Privato vs. Pubblico

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

Quindi, a meno che non pensi di essere un architetto migliore degli sviluppatori Adobe nel 2007 (dalla tua domanda, direi che hai ancora qualche anno prima di avere la possibilità di presentare tale richiesta), probabilmente vorresti solo predefinito su protetto.

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

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

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

_document Document = reader.read(info);

Non so come funzioni C #, ma dal momento che ho lavorato con un certo numero di tecnologie web, sarei sospettoso che non saresti sempre in grado di restituire immediatamente un documento XML (tranne forse come una promessa o futuro tipo 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 assumere un parametro che indichi dove / cosa leggere e restituire un oggetto XML e scambiarle in base alle esigenze del progetto. Ad esempio, potresti leggere direttamente da un database, da un negozio locale o, come nel tuo esempio originale, da un URL. Non puoi farlo se usi un metodo statico.

    
risposta data 25.04.2013 - 23:24
fonte
1

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

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 conosco C # ... in Java si chiama String). Per es.

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

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

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

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

}
    
risposta data 02.04.2014 - 23:30
fonte

Leggi altre domande sui tag