Classe che non rappresenta nulla - è corretta?

42

Sto solo progettando la mia applicazione e non sono sicuro di aver compreso correttamente SOLID e OOP. Le classi dovrebbero fare 1 cosa e farlo bene, ma dall'altra parte dovrebbero rappresentare gli oggetti reali con cui lavoriamo.

Nel mio caso eseguo un'estrazione di funzionalità su un set di dati e poi eseguo un'analisi di apprendimento automatico. Suppongo di poter creare tre classi

  1. FeatureExtractor
  2. DataSet
  3. Analizzatore

Ma la classe FeatureExtractor non rappresenta nulla, fa qualcosa che lo rende più una routine che una classe. Avrà solo una funzione che verrà utilizzata: extract_features ()

È corretto creare classi che non rappresentano una cosa ma fanno una cosa?

EDIT: non sono sicuro se è importante, ma sto usando Python

E se extract_features () fosse così: vale la pena creare una classe speciale per contenere quel metodo?

def extract_features(df):
    extr = PhrasesExtractor()
    extr.build_vocabulary(df["Text"].tolist())

    sent = SentimentAnalyser()
    sent.load()

    df = add_features(df, extr.features)
    df = mark_features(df, extr.extract_features)
    df = drop_infrequent_features(df)
    df = another_processing1(df)
    df = another_processing2(df)
    df = another_processing3(df)
    df = set_sentiment(df, sent.get_sentiment)
    return df
    
posta Alicja Głowacka 15.04.2018 - 13:36
fonte

8 risposte

96

Classes should do 1 thing and do it well

Sì, è generalmente un buon approccio.

but from the other hand they should represent real object we work with.

No, questo è un comune equivoco IMHO. Un buon accesso da principiante a OOP è spesso "iniziare con oggetti che rappresentano cose dal mondo reale" , è vero.

Tuttavia, tu non dovresti interrompere questo !

Le classi possono (e dovrebbero) essere utilizzate per strutturare il tuo programma in vari modi. La modellazione di oggetti dal mondo reale è un aspetto di questo, ma non l'unico. La creazione di moduli o componenti per un'attività specifica è un altro caso d'uso ragionevole per le classi. Un "feature extractor" è probabilmente un tale modulo, e anche se contiene solo un metodo public extract_features() , sarei sorpreso se non contenga molti metodi privati e forse qualche stato condiviso . Quindi avere una classe FeatureExtractor introdurrà una posizione naturale per questi metodi privati.

Nota a margine: in linguaggi come Python che supportano un concetto di modulo separato si può anche usare un modulo FeatureExtractor per questo, ma nel contesto di questa domanda, questa è una differenza trascurabile per IMHO.

Inoltre, un "estrattore di caratteristiche" può essere immaginato come "una persona o un bot che estrae funzionalità". Questa è una "cosa" astratta, forse non è una cosa che troverai nel mondo reale, ma il nome stesso è un'astrazione utile, che dà a tutti un'idea di quale sia la responsabilità di quella classe. Quindi non sono d'accordo sul fatto che questa classe non "rappresenti nulla".

    
risposta data 15.04.2018 - 14:35
fonte
43

Doc Brown è azzeccato: le classi non hanno bisogno di rappresentare oggetti del mondo reale. Devono solo essere utili . Le classi sono fondamentalmente solo tipi aggiuntivi, e a cosa corrisponde int o string nel mondo reale? Sono descrizioni astratte, non concrete, cose tangibili.

Detto questo, il tuo caso è speciale. In base alla tua descrizione:

And if extract_features() would look like that: is it worth to create a special class to hold that method?

Hai assolutamente ragione: se il tuo codice è come mostrato, è inutile inserirlo in una classe. C'è un famoso discorso che sostiene che tali usi delle classi in Python sono l'odore del codice, e quelle semplici funzioni sono spesso sufficienti. Il tuo caso è un perfetto esempio di questo.

L'uso eccessivo delle classi è dovuto al fatto che OOP è diventato mainstream con Java negli anni '90. Sfortunatamente a quel tempo Java mancava di molte funzionalità linguistiche moderne (come le chiusure), il che significa che molti concetti erano difficili o impossibili da esprimere senza l'uso di classi. Ad esempio, fino a poco tempo fa era impossibile in Java avere metodi che trasportavano lo stato (vale a dire chiusure). Invece, dovevi scrivere una classe per portare lo stato, e che esponeva un singolo metodo (chiamato qualcosa come invoke ).

Purtroppo questo stile di programmazione è diventato popolare ben oltre Java (in parte a causa di un influente libro di ingegneria del software che altrimenti sarebbe molto utile ), anche in lingue che non richiedono tali soluzioni.

In Python, le classi sono ovviamente uno strumento molto importante e dovrebbero essere utilizzate liberamente. Ma non sono lo strumento solo e non c'è motivo di usarli laddove non hanno senso. È un malinteso comune che le funzioni libere non abbiano posto in OOP.

    
risposta data 15.04.2018 - 21:48
fonte
35

I am just designing my application and I am not sure if I understand SOLID and OOP correctly.

Sono passati più di 20 anni e non ne sono nemmeno sicuro.

Classes should do 1 thing and do it well

Difficile sbagliare qui.

they should represent real objects we work with.

Oh davvero? Permettetemi di presentarvi la singola classe più popolare e di successo di tutti i tempi: String . Lo usiamo per il testo. E l'oggetto del mondo reale che rappresenta è questo:

Perchéno,nontuttiiprogrammatorisonoossessionatidallapesca.Quistiamousandoqualcosachiamatametafora.Vabenecrearemodellidicosecheinrealtànonesistono.Èl'ideachedeveesserechiara.Staicreandoimmagininellamentedeituoilettori.Quelleimmagininondevonoesserereali.Compresofacilmente.

UnbuonprogettoOOPraggruppaimessaggi(metodi)attornoaidati(stato)inmodochelereazioniatalimessaggipossanovariareasecondadeidati.Sefarequestomodellaqualcosadelmondoreale,spiffy.Seno,vabbè.Finchéhasensoperillettore,vabene.

Oracerto,potrestipensareinquestomodo:

festivoletteresospesedaunastringaleggi"FACCIAMO COSE!" "> </a> </p>

<p> ma se pensi che questo debba esistere nel mondo reale prima di poter utilizzare la metafora, beh, la tua carriera di programmazione coinvolgerà molte arti e mestieri. </p>
    </Div>                                    </div>
                                    <div class="action-time">
                                        risposta data
                                                                                                                                                                    <span title="369434" class="relativetime"> 16.04.2018 - 11:21</span>
                                    </div>
                                    <a class="a-link" href="https://softwareengineering.stackexchange.com/questions/369390/class-that-does-not-represent-anything-is-it-correct/369434#369434" target="_blank" rel="noopener">fonte</a>
                                </div>
                            </div>
                        </div>
                                                                                <div class="answer" id="369438" itemscope="" itemtype="http://schema.org/Answer">
                            <div class="left-row">
                                <div class="votes">
                                    <i class="fa fa-caret-up vote"></i>
                                    <div class="vote-count" itemprop="upvoteCount">6</div>
                                    <i class="fa fa-caret-down vote"></i>
                                </div>
                            </div>
                            <div class="answer-row">
                                <div class="answer-text">
                                    <div class="description" itemprop="text">
                                        <div class="post-text" itemprop="text">
<P> Attenzione! In nessun luogo SOLID dice che una classe dovrebbe "fare solo una cosa". Se così fosse, le classi avrebbero sempre un solo metodo e non ci sarebbe davvero differenza tra classi e funzioni. </P>

<p> SOLID dice che una classe dovrebbe rappresentare <em> una singola responsabilità </em>. Questi sono come le responsabilità delle persone in una squadra: il conducente, l

Il punto è questo: se c'è un cambiamento nei requisiti, idealmente hai solo bisogno di modificare una singola classe. Questo rende il codice più facile da capire, più facile da modificare e riduce i rischi.

Non esiste una regola per cui un oggetto dovrebbe rappresentare "una cosa reale". Questa è solo una tradizione legata al carico dal momento che OO è stato inizialmente inventato per l'uso nelle simulazioni. Ma il tuo programma non è una simulazione (sono poche le applicazioni OO moderne), quindi questa regola non si applica. Finché ogni classe avrà una responsabilità ben definita, dovresti stare bene.

Se una classe ha solo un singolo metodo e la classe non ha alcuno stato, potresti considerare di renderla una funzione autonoma. Questo è sicuramente soddisfacente e segue i principi KISS e YAGNI: non è necessario fare un corso se è possibile risolverlo con una funzione. D'altra parte, se hai motivo di credere che potresti aver bisogno di uno stato interno o di più implementazioni, potresti anche renderlo un corso di classe. Dovrai usare il tuo miglior giudizio qui.

    
risposta data 16.04.2018 - 12:51
fonte
5

Is it correct to create classes that do not represent one thing but do one thing?

In generale va bene.

Senza una descrizione un po 'più specifica di ciò che la classe FeatureExtractor dovrebbe fare esattamente è difficile da capire.

Comunque, anche se FeatureExtractor espone solo una funzione pubblica extract_features() , potrei pensare di configurarla con un Strategy class, che determina come deve essere eseguita esattamente l'estrazione.

Un altro esempio è una classe con una funzione Modello .

E ci sono altri Modelli di progettazione comportamentale , che si basano su modelli di classe.

Come hai aggiunto del codice per chiarimenti.

And if extract_features() would look like that: is it worth to create a special class to hold that method?

La linea

 sent = SentimentAnalyser()

comprende esattamente cosa intendevo dire che potresti configurare una classe con una strategia .

Se hai un'interfaccia per quella classe SentimentAnalyser , puoi passarla alla classe FeatureExtractor nel suo punto di costruzione, invece di accoppiare direttamente a quella specifica implementazione nella tua funzione.

    
risposta data 15.04.2018 - 14:07
fonte
0

Modelli e tutti i linguaggi / concetti fantasiosi a parte: ciò che hai trovato è un Job o un Processo batch .

Alla fine della giornata, anche un puro programma OOP deve essere in qualche modo guidato da qualcosa, per eseguire effettivamente il lavoro; ci deve essere un punto di ingresso in qualche modo. Nel pattern MVC, ad esempio, l'ontroller "C" riceve eventi click ecc. Dalla GUI e quindi orchestra gli altri componenti. Nei classici strumenti a linea di comando, una funzione "principale" farebbe lo stesso.

Is it correct to create classes that do not represent one thing but do one thing?

La tua classe rappresenta un'entità che fa qualcosa e orchestra qualsiasi altra cosa. Puoi chiamarlo Controller , Lavoro , Principale o qualsiasi altra cosa ti venga in mente.

And if extract_features() would look like that: is it worth to create a special class to hold that method?

Dipende dalle circostanze (e non ho familiarità con il solito modo in cui ciò viene fatto in Python). Se questo è solo uno strumento da riga di comando one-shot, allora un metodo invece di una classe dovrebbe andare bene. Sicuramente la prima versione del tuo programma può farla franca con un metodo. Se, in seguito, scoprirai che ti ritroverai con dozzine di tali metodi, magari anche con variabili globali mescolate, allora è il momento di rifattorizzare in classi.

    
risposta data 15.04.2018 - 21:17
fonte
0

Possiamo pensare a OOP come modellare il comportamento di un sistema. Nota che il sistema non deve esistere nel "mondo reale", anche se le metafore del mondo reale a volte possono essere utili (ad esempio "condutture", "fabbriche", ecc.).

Se il nostro sistema desiderato è troppo complicato per modellare tutto in una volta, possiamo scomporlo in parti più piccole e modellare quelle (il "dominio del problema"), che potrebbe comportare una rottura ulteriore, e così via fino a quando non arriviamo a pezzi il cui comportamento corrisponde (più o meno) a quello di qualche oggetto linguaggio incorporato come un numero, una stringa, una lista, ecc.

Una volta che abbiamo questi semplici pezzi, possiamo combinarli insieme per descrivere il comportamento di pezzi più grandi, che possiamo combinare insieme in pezzi ancora più grandi, e così via fino a che non possiamo descrivere tutti i componenti del dominio che sono necessari per un intero sistema.

È questa fase di "combinazione" in cui potremmo scrivere alcune classi. Scriviamo classi quando non esiste un oggetto esistente che si comporta nel modo in cui vogliamo. Ad esempio, il nostro dominio potrebbe contenere "foos", raccolte di foos chiamate "barre" e raccolte di barre chiamate "baz". Potremmo notare che i foos sono abbastanza semplici da modellare con le stringhe, quindi lo facciamo. Troviamo che le barre richiedono che il loro contenuto obbedisca a qualche particolare vincolo che non corrisponde a qualcosa che Python fornisce, nel qual caso potremmo scrivere una nuova classe per far rispettare questo vincolo. Forse i baz non hanno tali peculiarità, quindi possiamo semplicemente rappresentarli con una lista.

Nota che potremmo scrivere una nuova classe per ognuno di questi componenti (foos, barre e baz), ma non bisogno di se c'è già qualcosa con il comportamento corretto. In particolare, affinché una classe sia utile deve "fornire" qualcosa (dati, metodi, costanti, sottoclassi, ecc.), Quindi anche se abbiamo molti livelli di classi personalizzate, dobbiamo usare infine alcune funzionalità integrate; per esempio, se scrivessimo una nuova classe per foos probabilmente conterrebbe solo una stringa, quindi perché non dimenticare la classe foo e la classe della barra contiene invece quelle stringhe? Tieni presente che le classi sono anche un oggetto incorporato, sono solo particolarmente flessibili.

Una volta ottenuto il nostro modello di dominio, possiamo prendere alcune istanze particolari di quei pezzi e organizzarli in una "simulazione" del particolare sistema che vogliamo modellare (ad esempio "un sistema di apprendimento automatico" per ... ").

Una volta che abbiamo questa simulazione, possiamo eseguirla e presto, abbiamo un sistema di apprendimento automatico (simulazione di a) per ... (o qualsiasi altra cosa stavamo modellando).

Ora, nella tua situazione particolare, stai cercando di modellare il comportamento di un componente "feature extractor". La domanda è: ci sono oggetti built-in che si comportano come un "feature extractor", o hai bisogno di suddividerlo in cose più semplici? Sembra che gli estrattori di feature si comportino in modo molto simile agli oggetti funzione, quindi penso che staresti bene a utilizzarli come modello.

Una cosa da tenere a mente quando si impara su questo tipo di concetti è che linguaggi diversi possono fornire diverse funzionalità e oggetti incorporati (e, naturalmente, alcuni non usano nemmeno termini come "oggetti"!). Quindi le soluzioni che hanno senso in una lingua potrebbero essere meno utili in un'altra (questo può anche applicarsi a versioni diverse della stessa lingua!).

Storicamente, un lotto della letteratura OOP (in particolare "schemi di progettazione") si è concentrato su Java, che è molto diverso da Python. Ad esempio, le classi Java non sono oggetti, Java non ha avuto oggetti funzione fino a poco tempo fa, Java ha un rigoroso controllo dei tipi (che incoraggia interfacce e sottoclassi) mentre Python incoraggia la tipizzazione anatra, Java non ha oggetti modulo, interi Java / galleggianti / etc. non sono oggetti, la meta-programmazione / introspezione in Java richiede "riflessione" e così via.

Non sto provando a scegliere su Java (come un altro esempio, molta teoria OOP ruota attorno a Smalltalk, che è ancora molto diversa da Python), sto solo cercando di far notare che dobbiamo pensare molto attentamente il contesto e i vincoli in cui sono state sviluppate le soluzioni e se questo corrisponde alla situazione in cui ci troviamo.

Nel tuo caso, un oggetto funzione sembra una buona scelta. Se ti stai chiedendo perché alcune linee guida sulle "migliori pratiche" non menzionino gli oggetti funzione come una possibile soluzione, potrebbe semplicemente essere perché quelle linee guida sono state scritte per vecchie versioni di Java!

    
risposta data 16.04.2018 - 14:55
fonte
0

In termini pragmatici, quando ho una "cosa varia che fa qualcosa di importante e dovrebbe essere separata", e non ha una casa chiara, la metto in una sezione Utilities e la uso come mia convenzione di denominazione. vale a dire. FeatureExtractionUtility .

Dimentica il numero di metodi in una classe; un singolo metodo oggi potrebbe aver bisogno di crescere fino a cinque metodi domani. Ciò che conta è una struttura organizzativa chiara e coerente, come un'area di utilità per raccolte di funzioni varie.

    
risposta data 16.04.2018 - 21:18
fonte

Leggi altre domande sui tag