Evitare insidie ​​orientate agli oggetti, migrazione da C, cosa ha funzionato per te?


12

Sto programmando in linguaggi procedurali già da un po 'di tempo, e la mia prima reazione a un problema è iniziare a scomporlo in compiti da svolgere piuttosto che considerare le diverse entità (oggetti) esistenti e le loro relazioni.

Ho tenuto un corso universitario in OOP e ho compreso i fondamenti dell'incapsulamento, dell'astrazione dei dati, del polimorfismo, della modularità e dell'eredità.

Ho letto /programming/2688910/learning-to-think-in-the-object-oriented-way e /programming/1157847/learning-object-oriented-thinking , e guarderò alcuni dei libri indicati in quelle risposte.

Penso che molti dei miei progetti di medie e grandi dimensioni trarranno beneficio dall'uso efficace di OOP, ma come novizio vorrei evitare errori comuni che richiedono tempo.

In base alle tue esperienze, quali sono queste insidie ​​e quali sono i modi ragionevoli per aggirarle? Se potessi spiegare perché sono insidie ​​e in che modo il tuo suggerimento è efficace nell'affrontare il problema, sarebbe apprezzato.

Sto pensando a qualcosa come "È comune avere un buon numero di metodi di osservatori e modificatori e usare variabili private o esistono tecniche per consolidarle / ridurle?"

Non sono preoccupato di usare C ++ come un linguaggio OO puro, se ci sono buone ragioni per mescolare i metodi. (Ricorda i motivi per usare GOTO, anche se con parsimonia.)

Grazie!


2
non degno di una risposta completa, ma importante che mi ci è voluto molto tempo per accettarlo (cioè l'ho letto molto tempo, ma lo ho trattato come un discorso da fanboy): preferisco fions gratuiti rispetto alle funzioni dei membri. Ciò mantiene le tue lezioni minime, il che è una buona cosa.
stijn

@stijn In sostanza stai dicendo che se non è necessario essere in classe, non metterlo lì. Ad esempio, vedo molte funzioni membro dell'utilità che potrebbero essere facilmente funzioni libere nel codice che ho letto finora.
Stephen,

sì è quello. Se cerchi "preferisci non amici non membri" troverai molte informazioni a riguardo. Alla fine si tratta di aderire al principio della responsabilità singola
stijn

Risposte:


10

Una cosa importante che ho imparato è progettare classi dall'esterno. Progetta l'interfaccia prima ancora di iniziare a pensare all'implementazione. Ciò renderà la classe molto, molto più intuitiva per i tuoi utenti (coloro che usano la classe) rispetto alla scrittura degli algoritmi sottostanti e alla creazione della classe e alla scrittura di nuove funzioni di membro pubblico quando sono necessarie.


7
Aggiungendo a ciò, possiamo "programmare per intenzione", vale a dire, scrivere del codice di esempio che utilizzerebbe la nuova classe, vedere quali tipi di metodi e criteri ne rendono comodo l'uso. Quindi utilizzare tali informazioni come base per le implementazioni.

2
Fantastico in teoria: progettate le interfacce e il gioco è fatto. Ma in pratica non funziona così semplice. La progettazione e l'implementazione dell'interfaccia spesso vanno di pari passo in iterazioni consecutive fino a quando l'interfaccia finale non viene cristallizzata. In quel momento probabilmente avrai anche l'implementazione finale.
Gene Bushuyev,

9

Bene, il primo è la trappola di esporre troppe informazioni. L'impostazione predefinita dovrebbe essere private, no public.

Dopo di ciò arrivano troppi getter / setter. Diciamo che ho un membro di dati. Ho davvero bisogno di questi dati in un'altra classe? Ok, fai il getter. Devo davvero, davvero cambiare questi dati durante la durata dell'oggetto? Quindi crea un setter.

La maggior parte dei programmatori alle prime armi ha l'impostazione predefinita per creare un getter / setter per ogni membro dei dati. Ciò ingombra le interfacce e spesso sono cattive scelte di progettazione.


2
sì, è abbastanza popolare tra coloro che hanno compreso l'incapsulamento superficialmente per rendere i dati privati ​​e quindi esplodere l'incapsulamento con getter e setter.
Gene Bushuyev,

Java è l'unico linguaggio popolare che necessita ancora dei metodi get / set. Ogni altra lingua supporta le proprietà, quindi non esiste alcuna differenza sintattica tra un membro di dati pubblici e una proprietà. Anche in Java, non è un peccato avere membri di dati pubblici se controlli anche tutti gli utenti della classe.
Kevin Cline,

I membri di dati pubblici sono più difficili da mantenere. Con getter / setter (o proprietà), è possibile mantenere l'interfaccia mentre si modifica la rappresentazione interna dei dati, senza cambiare alcun codice esterno. Nelle lingue in cui le proprietà sono sintatticamente identiche alle variabili membro dal punto di vista del consumatore, questo punto non è valido.
tdammers,

Finora penso di guardare i membri privati ​​"una specie di" come variabili locali in una funzione ... il resto del mondo non ha bisogno di sapere nulla su di loro per far funzionare il fn ... e se il fn cambia, solo l'interfaccia deve essere coerente. Non tendo ad andare con lo spam getter / setter quindi potrei essere sulla buona strada, infatti guardando una classe di buffer che ho scritto, non ci sono membri di dati pubblici. Grazie!
Stephen,

2

Quando ho attraversato quell'abisso, ho optato per il seguente approccio:

0) Ho iniziato lentamente, con app procedurali di piccole / medie dimensioni e senza roba mission-critical al lavoro.

1) una semplice mappatura di primo passaggio su come scriverei il programma da zero in stile OO - cosa più importante per me in quel momento, e questo è soggettivo - era di capire tutte le classi di base. Il mio obiettivo era incapsulare il più possibile nelle classi di base. Metodi virtuali puri per tutto il possibile nelle classi di base.

2) Quindi il passo successivo è stato creare le derivazioni.

3) il passaggio finale è stato: nel codice procedurale originale, separare le strutture di dati dal codice obsever / modificatore. Quindi utilizzare il nascondimento dei dati e mappare tutti i dati comuni nelle classi di base e nelle sottoclassi sono andati i dati che non erano comuni in tutto il programma. E lo stesso trattamento per il codice osservatore / modificatore procedurale - tutta la logica "usata ovunque" è andata nelle classi di base. E visualizzare / modificare la logica che ha agito solo su un sottoinsieme dei dati è andato nelle classi derivate.

Questo è soggettivo ma è VELOCE, se conosci bene il codice procedurale e le strutture dei dati. Un GUASTO in una revisione del codice è quando un po 'di dati o logica non viene visualizzato nelle classi di base ma viene utilizzato ovunque.


2

Dai un'occhiata ad altri progetti di successo che usano OOP per avere un senso di buon stile. Consiglio di guardare Qt , un progetto a cui guardo sempre quando prendo le mie decisioni di progettazione.


Sì! Prima che una persona diventi un maestro di qualcosa, impara a imitare i grandi maestri che hanno lavorato prima di lui. Studiare un buon codice implementato da altri è un buon modo per imparare. Consiglierei di dare un'occhiata al boost, partendo da progetti semplici e approfondendo man mano che la comprensione migliora.
Gene Bushuyev,

1

A seconda della persona con cui parli, tutti i dati in OOP dovrebbero essere privati ​​o protetti e disponibili solo tramite accessori e mutatori. In generale, penso che questa sia una buona pratica, ma ci sono occasioni in cui divergo da questa norma. Ad esempio, se si dispone di una classe (in Java, diciamo) il cui unico scopo è di raggruppare alcuni pezzi di dati in un'unità logica, ha senso lasciare pubblici i campi. Se è una capsula immutabile, basta contrassegnarli come finali e inizializzarli nel costruttore. Questo riduce la classe (in questo caso) a poco più di una struttura (infatti, usando C ++, dovresti effettivamente chiamarla una struttura. Funziona proprio come una classe, ma la visibilità predefinita è pubblica e l'intenzione è più chiara), ma Penso che scoprirai che usarlo è molto più comodo in questi casi.

Una cosa che sicuramente non vuoi fare è avere un campo che deve essere verificato per coerenza nel mutatore e lasciarlo pubblico.


3
Suggerirei anche, se si dispone di tali classi che contengono solo dati pubblici, è necessario dichiararle come structs: non fa alcuna differenza semantica, ma chiarisce l'intenzione e rende il loro utilizzo più naturale, specialmente per i programmatori C.

1
Sì, sono d'accordo qui se sei in C ++ (che la domanda ha menzionato), ma faccio la maggior parte del mio lavoro in Java e, purtroppo, non abbiamo alcuna struttura.

è necessario distinguere chiaramente tra classi aggregate (caso raro) e classi con funzionalità (caso generale). Quest'ultimo deve praticare l'incapsulamento e accedere ai propri membri solo tramite interfaccia pubblica, nessun dato deve essere esposto al pubblico.
Gene Bushuyev,

1

Il libro di Kent Beck Implementation Patterns è un'ottima base su come usare, non usare impropriamente, meccanismi orientati agli oggetti.


1

Non sono preoccupato di usare C ++ come un linguaggio OO puro, se ci sono buone ragioni per mescolare i metodi. (Ricorda i motivi per usare GOTO, anche se con parsimonia.)

Non pensavo davvero di avere molto da offrire alla conversazione fino a quando non ho visto questo pezzo. Non sono d'accordo con il sentimento. OOP è solo uno dei paradigmi che possono e devono essere utilizzati in C ++. Francamente, secondo me non è una delle sue caratteristiche più forti.

Da un punto di vista OO penso che il C ++ in realtà non sia all'altezza. L'idea di avere funzioni non virtuali, ad esempio, è un segno di spunta in questo senso. Ho avuto discussioni con quelli che non sono d'accordo con me, ma i membri non virtuali non si adattano al paradigma per quanto mi riguarda. Il polimorfismo è un componente chiave di OO e le classi con funzioni non virtuali non sono polimorfiche in senso OO. Quindi come linguaggio OO penso che C ++ sia in realtà piuttosto debole rispetto a linguaggi come Java o Objective-C.

Programmazione generica d'altra parte, C ++ ha questo abbastanza buono. Ho sentito dire che ci sono anche lingue migliori per questo, ma la combinazione di oggetti e funzioni generiche è qualcosa di abbastanza potente ed espressivo. Inoltre può essere dannatamente veloce sia in tempo di programmazione che in tempo di elaborazione. È proprio in quest'area che penso che il C ++ brilli, anche se è vero che potrebbe essere migliore (supporto del linguaggio per concetti per esempio). Qualcuno che pensa che dovrebbero attenersi al paradigma OO e trattare gli altri sull'ordine della dichiarazione di goto in livelli di immoralità si sta davvero perdendo non guardando questo paradigma.

Anche la capacità di metaprogrammazione dei modelli è piuttosto impressionante. Dai un'occhiata alla libreria Boost.Units per esempio. Questa libreria fornisce il supporto del tipo per quantità dimensionali. Ho fatto ampio uso di questa biblioteca nella società di ingegneria per cui lavoro attualmente. Fornisce solo un feedback molto più immediato per un aspetto di un possibile programmatore o addirittura un errore di specifica. È impossibile compilare un programma che utilizza una formula in cui entrambi i lati dell'operatore '=' non sono dimensionalmente equivalenti senza il cast esplicito. Personalmente non ho esperienza con nessun altro linguaggio in cui ciò sia possibile, e certamente non con uno che abbia anche la potenza e la velocità del C ++.

La metaprogrammazione è un paradigma puramente funzionale.

Quindi davvero, penso che tu stia già entrando nel C ++ con alcune sfortunate idee sbagliate. Gli altri paradigmi oltre a OO non devono essere evitati, devono essere LEVERAGGIATI. Usa il paradigma naturale per l'aspetto del problema su cui stai lavorando. Non forzare oggetti su ciò che essenzialmente non è un problema soggetto ad oggetti. Per quanto mi riguarda, OO non è nemmeno metà della storia per C ++.


La parola chiave virtuale non fornisce funzionalità di classe virtuale in C ++? Per quanto riguarda il commento goto, quello che stavo guidando era che non mi preoccupo di infrangere le regole empiriche, purché capisca il ragionamento. Tuttavia, ho cercato di evitare imperativi / procedurali a favore di OO di quanto non fosse stato necessario. Grazie.
Stephen,

i metodi virtuali non sono un prerequisito per il polimorfismo, è solo un modo comune per farlo. in effetti, tutto il resto è uguale, quindi rendere virtuale un metodo indebolisce effettivamente l'incapsulamento, perché stai aumentando la dimensione dell'api della classe e rendi più difficile assicurarti di seguire Liskov e così via. rendi qualcosa di virtuale solo se hai bisogno di una cucitura, un posto in cui iniettare nuovi comportamenti tramite ereditarietà (sebbene l'ereditarietà sia anche qualcosa di cui fare attenzione in OOP). virtual per il bene del virtuale non rende una classe "più OOP"
sara,

1

Volevo accettare una risposta a questa domanda, ma non potevo decidere una risposta per assegnare il segno di spunta. Come tale ho votato gli autori originali e l'ho creato come una risposta di sintesi. Grazie a tutti quelli che hanno impiegato qualche minuto, ho scoperto che l'intuizione che mi hai fornito mi ha dato una buona direzione e una piccola rassicurazione che non ero fuori dai binari.

@nightcracker

Bene, il primo è la trappola di esporre troppe informazioni. L'impostazione predefinita dovrebbe essere privata, non pubblica. Dopo di ciò arrivano troppi getter / setter.

Sentivo di aver osservato questo problema in azione in passato. I tuoi commenti mi hanno fatto anche ricordare che nascondendo le variabili sottostanti e la loro implementazione, sono libero di cambiare la loro implementazione senza distruggere nulla che dipenda da loro.

Domenico Gurto

Progetta l'interfaccia prima ancora di iniziare a pensare all'implementazione. La progettazione e l'implementazione dell'interfaccia di Gene Bushuyev spesso vanno di pari passo in iterazioni consecutive fino a quando l'interfaccia finale non viene cristallizzata.

Ho pensato che il commento di Dominic fosse un grande ideale a cui aspirare, ma penso che il commento di Gene colpisca davvero la realtà della situazione. Finora l'ho visto in azione ... e mi sento un po 'meglio che non sia raro. Penso che quando maturerò come programmatore mi sposterò verso progetti più completi, ma in questo momento soffro ancora di saltare e ottenere un po 'di codice scritto.

wantTheBest

Ho iniziato lentamente, con app procedurali di piccole / medie dimensioni e senza roba mission-critical al lavoro. nel codice procedurale originale, separare le strutture di dati dal codice obsever / modificatore

Questo ha molto senso ... Mi piaceva l'idea di far funzionare le cose al lavoro, ma di riformattare alcune delle cose non critiche con le classi.

JPM

Una cosa che sicuramente non vuoi fare è avere un campo che deve essere verificato per coerenza nel mutatore e lasciarlo pubblico

Ho saputo per un po 'che questo è uno dei punti di forza dell'incapsulamento dei dati ... essere in grado di imporre coerenza e, per quel motivo, condizioni / intervalli / ecc.

Crazy Eddie

Qualcuno che pensa che dovrebbero attenersi al paradigma OO e trattare gli altri sull'ordine della dichiarazione di goto in livelli di immoralità si sta davvero perdendo non guardando questo paradigma. Anche la capacità di metaprogrammazione dei modelli è piuttosto impressionante.

Inizialmente mi mancava molto nella risposta di Crazy Eddie, penso perché non avevo letto alcuni degli argomenti citati ... come la metaprogrammazione. Penso che il grande messaggio nel post di CE sia stato che il C ++ è una tale miscela di capacità e stili che ognuno dovrebbe essere usato per il suo miglior potenziale ... incluso l'imperativo se questo è ciò che ha senso.

Quindi, grazie a tutti quelli che hanno risposto!


0

La più grande trappola è la convinzione che OOP sia un proiettile d'argento, o "l'unico paradigma perfetto".

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.