Strutturale procedurale rispetto al codice OO

7

Ho trascorso la maggior parte della mia carriera di programmazione utilizzando Java e C ++ basato su OO. Sono davvero interessato a imparare a pensare in modo più procedurale, quindi ho iniziato a fare pratica usando un sottoinsieme più ristretto di C ++ (fondamentalmente C) e cercando di mantenere il mio codice molto procedurale.

La mia domanda si trova nell'area di come strutturare un programma. In Java, penso costantemente agli oggetti ... quindi quando creo una classe, penso a:

  • Che cos'è questo oggetto che rappresenta?
  • Cosa può fare questo oggetto?
  • Che cosa devono essere gli altri per poterlo fare?
  • Che cosa espongo, cosa nascondo?
  • Etc

Nel caso del codice procedurale, come funziona questo genere di cose? Diciamo che volevo una struttura per rappresentare una macchina. Vorrei creare un'intestazione per definirlo in ... ma dove vanno le funzioni che dovrebbero funzionare su un tipo di auto? Definiresti anche tutte le funzioni relative alla Car nella stessa linea di file di una classe Java? Sto pensando completamente a questo nel modo sbagliato, nel senso che non dovrebbero esserci funzioni correlate all'auto, dal momento che l'enfasi non dovrebbe essere sull'oggetto Car ma sulle funzioni stesse? L'idea di una funzione membro è totalmente buttata fuori dalla finestra?

La linea di fondo è che il mio cervello è stato cablato per pensare a Oggetti, Incapsulamento, Polimorfismo, ecc. e non riesco a pensare a nient'altro. Come posso esercitarmi?

Qualche consiglio è apprezzato!

    
posta JoeP 24.04.2015 - 21:06
fonte

8 risposte

10

Un sacco di codice procedurale è molto simile a OOP. In sostanza, anziché object.function(params) , fai function(object, params) . Puoi raggruppare i tuoi file di conseguenza.

Tuttavia, ciò che molti programmatori OOP di lunga data non si rendono conto di quanto sia limitante è che una funzione debba appartenere a una sola classe, e che tutte queste funzioni debbano essere raggruppate in un unico file di conseguenza. Lo stile procedurale consente molti altri tipi di raggruppamenti che spesso si adattano meglio in determinate circostanze:

  • Combinazioni di due oggetti, come tutte le funzioni che riguardano sia engines che transmissions .
  • Raccolte di oggetti, come tutte le funzioni che si occupano di elenchi di cars .
  • Tutte le implementazioni di un'interfaccia. Ad esempio, le implementazioni di steer() per diversi tipi di vehicles potrebbero essere raggruppate in un unico file.
  • Funzioni che convertono da un tipo di struttura dati a un'altra. Ad esempio, funzioni che accettano una raccolta di parts e restituiscono una car .

Fondamentalmente, tutte quelle volte in cui non riuscivi a capire quale classe fosse la cosa migliore per qualcosa, in procedurale non è davvero un problema. Non fraintendermi, il modo in cui i gruppi OOP funzionano ha successo perché è un valore predefinito molto ragionevole. Tuttavia, ciò non lo rende il miglior raggruppamento in tutte le circostanze.

    
risposta data 25.04.2015 - 00:04
fonte
6

Per essere onesti, sembra un po 'come tornare indietro e programmare con una mano dietro la schiena, ma se per "strutturato", intendi, come il modo in cui le persone hanno creato programmi prima dell'orientamento degli oggetti nei linguaggi procedurali, allora si tratta di come inizia a pensare al problema. Nel tuo quarto paragrafo, stai essenzialmente ancora pensando in un modo orientato agli oggetti. Il modo in cui le persone hanno scritto in lingue come C, Pascal e BASIC nel corso della giornata era iniziare con il codice non i dati. Un'analogia sarebbe che un programma per computer era come una ricetta; una serie di passaggi da seguire.

Quindi non diresti "Diciamo che volevi una struttura per rappresentare una macchina". Diresti "Voglio portarmi al supermercato". Avresti solo iniziato a pensare a oggetti come "Car" quando sei arrivato al passaggio che lo richiedeva.

Per usare un esempio più concreto: Supponiamo di voler inviare un gruppo di lettere agli indirizzi in un file. Pensando a OO, probabilmente dovresti iniziare a pensare a cose come gli oggetti degli indirizzi. Ma con i vecchi linguaggi strutturati, probabilmente dovresti prima pensare ai passaggi:

  1. Apri file
  2. Leggi un indirizzo
  3. Formato informazioni indirizzo
  4. Stampa indirizzo
  5. Vai a 2

Trasformeresti ognuno di questi in passaggi, come

4:

  1. Stampa nome
  2. Stampa numero civico
  3. Stampa street
  4. Stampa città
  5. Stampa stato
  6. Stampa codice postale

Per rendere tutto più semplice, dovresti quasi certamente inserire i dati dell'indirizzo in una sorta di struttura per tenerlo insieme, ma ciò sarebbe guidato dai passi che avresti intrapreso. Non inizieresti con quello.

Il fatto è che questo diventa davvero difficile da gestire una volta arrivati a programmi di qualsiasi dimensione significativa. Questo è il motivo per cui quasi tutti pensano negli oggetti oggigiorno a meno che non stiano scrivendo un piccolo script veloce.

    
risposta data 24.04.2015 - 21:30
fonte
5

Inizia da strutture di dati.

Scrivi funzioni che operano su quelle strutture di dati.

Se vuoi l'incapsulamento, fallo al livello dei moduli, non degli oggetti.

Se vuoi il polimorfismo, usa le funzioni di ordine superiore, non quelle virtuali.

Questo è tutto. OOP come praticato non è una partenza significativa dalla programmazione procedurale. È principalmente un insieme di impostazioni predefinite ragionevoli per la costruzione di programmi procedurali.

    
risposta data 29.04.2015 - 20:43
fonte
4

Il modo in cui usavamo fare sort-of OOP ai giorni di C era dichiarando un struct e quindi dichiarando le funzioni che accettano tale struct come loro primo parametro. A volte, la necessità di polimorfismo potrebbe essere avviata, quindi avremmo il nostro struct non solo per i campi di dati, ma anche per le funzioni. È piuttosto inutile, in realtà, fare a mano ciò che C ++ farebbe per noi con molte meno righe di codice, in modo più sicuro, più elegante e in un modo che tutti sanno che il C ++ capisce, invece di ogni negozio che fa da sé.

Se veramente vuoi abbandonare l'OOP a favore della programmazione strutturata, devi allontanarti dagli oggetti e iniziare a pensare in termini di moduli, ma presto vedrai che naturalmente e inevitabilmente gravitano verso l'OOP, e non c'è nulla di sbagliato in questo, perché l'OOP è un modo molto conveniente di pensare alla programmazione, e oserei anche dire una necessità per costruire sistemi complessi.

Prima del C ++ usavamo scrivere moduli che esponevano funzioni, e abbastanza spesso questi moduli erano gestori che contenevano una pluralità di entità, quindi avrebbero esposto una sorta di "handle" per indicare quale entità ti stavi riferendo, come per esempio il caso è con il sistema operativo "handle di file" di vecchio, che potresti aver sentito parlare. Era già OOP, anche se non era ancora chiamato con quel nome. All'inizio l'handle di solito era un int ed era usato dal modulo come indice in un array interno di struct s, ma a un certo punto qualcuno ha capito che dal momento che il codice utente non dovrebbe cercare di interpretare il gestire in alcun modo, l'handle potrebbe altrettanto facilmente essere un puntatore effettivo al struct interno; nessuno se ne accorgerebbe, e nessuno dovrebbe preoccuparsene.

Quindi, per fare qualcosa a) utile e b) su larga scala devi pensare a OOP, ma se non usi un linguaggio OOP, la tua implementazione sarà dietro un'interfaccia piatta monolitica, e tu resterai dobbiamo fare un sacco di lavoro manuale spensierato per ottenere certe cose come l'ereditarietà e il polimorfismo (che sarà necessario se cercherete di fare qualcosa di non banale complessità) e quale sarà fornito per te se usi un linguaggio OOP.

    
risposta data 24.04.2015 - 22:03
fonte
3

The bottom line is my brain has been wired to think about Objects, Encapsulation, Polymorphism, etc.. and I cant seem to wrap my head around anything else. How can I practice?

La più grande - se non l'unica - differenza tra programmazione procedurale e programmazione orientata agli oggetti è la nozione di ereditarietà. 1 Ad esempio, è semplice implementare un tipo di lista polimorfico in C, ma la traduzione delle relazioni di ereditarietà richiederebbe molto di lavoro. Finché non sono coinvolte relazioni ereditarie, scrivendo

Car myCar;
myCar.start();

in C ++ è solo un modo elegante per scrivere

Car myCar = car_construct();
car_start(myCar);

in C. Quindi, nella programmazione procedurale, risolvi i problemi definendo strutture dati e trattamenti su queste strutture dati, e questo funziona in modo simile come nei linguaggi OO, tranne che non puoi usare astrazioni basate sull'eredità .²

Se vuoi progredire nella programmazione procedurale, suggerirei di studiare programmi scritti da altri e di scrivere programmi simili da soli. Imparerai molto confrontando la tua soluzione con l'altra e chiedendoti quanto siano importanti le differenze. Leggere e scrivere script di shell è probabilmente un buon passo imparare la programmazione procedurale, perché questa è una competenza che è anche utile da sola. Di solito trovi molti script in bundle con il tuo sistema operativo e puoi trovare script di buona qualità in modo casuale su Internet.

Vale anche la pena di notare che uno dei pochi programmi diffusi, di notevole complessità e praticamente privi di errori è stato scritto in un linguaggio procedurale. Il programma a cui penso è TeX che è stato scritto in un linguaggio simile a Pascal e la sorgente del codice è descritta ampiamente in un libro TeX: il programma che potresti considerare di leggere. È facile trovare legalmente o produrre copie elettroniche del libro, e il campus scientifico più vicino avrà una copia cartacea nella sua biblioteca.

¹ Che non menzioni sulla tua domanda!
² L'ereditarietà è ottima per risolvere problemi di ingegneria del software, ma non risolve alcun problema pratico. Quindi, in considerazione del problema risolto dal software, l'ereditarietà risolve i problemi meta .
³ In realtà non ne conosco un altro!

    
risposta data 25.04.2015 - 02:26
fonte
2

Per come la vedo io, per passare dalla corretta programmazione orientata agli oggetti a uno stile procedurale, devi:

  • rimuove il comportamento dai tuoi oggetti; rendili semplicemente contenitori di dati (strutture dati)

  • implementa il comportamento in classi separate (servizi); prenderanno le strutture dei dati come argomenti ed eseguiranno alcune azioni in base ai casi di utilizzo delle applicazioni

  • usa gli stessi criteri di qualità di progettazione che useresti in OOP (coesione, accoppiamento, ecc.), applicali solo ai servizi

Come altri hanno sottolineato, questa è probabilmente una regressione da OOP, specialmente per progetti di grandi dimensioni. Potrebbe essere un approccio valido per scenari semplici.

    
risposta data 24.04.2015 - 21:48
fonte
1

What is this object representing? What can this object do? What do others need to be able to do to it? What do I expose, what do I hide? Etc

Quando crei una struttura, l'unica cosa a cui hai bisogno di rispondere è quale dovrebbe essere la sua struttura dati. Non può fare nulla, non può nascondere nulla, tutto è un metodo conveniente per accedere alla memoria normale. Potrebbe essere una buona idea provare a rispondere ad alcune altre domande sulla struttura, ma non esiste una grammatica formale per scrivere la risposta a queste domande. (Questo vale anche per molte buone domande sugli oggetti OO.)

but where do functions go that should operate on a car type?

Ovunque tu pensi che si adatti meglio, la struttura non prende una decisione per te, non impone nemmeno che il codice pertinente debba essere una funzione dedicata. Decidi un'implementazione quando devi effettivamente usarla.

IS the idea of a member function totally thrown out the window?

Non deve essere, la struttura function(object, parameters) è equivalente all'utilizzo di base delle funzioni membro. Dovrebbe compilare praticamente la stessa cosa.

Encapsulation

Un po 'semplificato, tutto ciò che l'incapsulamento fa mai è lanciare un messaggio di errore se si tenta di romperlo. Certo, può essere bello avere delle garanzie formali su cosa il codice non può fare, ma per lo più ottieni gli stessi benefici definendo i limiti non controllati dal compilatore. In ogni caso, l'unico metodo di incapsulamento che effettivamente perdi è costituito da membri e metodi privati. Nella mia esperienza, quelli non sono importanti quanto gli ambiti variabili.

Polymorphism

Il grande caso per OO. Risolve molti problemi strani e non ha alcuna sostituzione semplice. Ma ci sono molte strutture che fanno cose simili, eccone alcune:

  • Crea una struttura con lo spazio per tutti i dati necessari a qualsiasi sottotipo desiderato, con una variabile che contrassegna quale sottotipo è. È possibile utilizzare più di tali marchi per creare qualcosa che assomigli all'ereditarietà multipla. Piuttosto che sovraccaricare metodi, il codice che si occupa di tali strutture deve semplicemente controllare il tipo e agire di conseguenza. Potrebbe sprecare un po 'di memoria, ma nella maggior parte dei casi questo probabilmente non è un grosso problema. Se hai molti sottotipi diversi, potrebbe comunque diffondere il codice definendo le differenze tra questi sottotipi in tutto il tuo programma.

  • Includi i puntatori di funzione come variabili membro, questi possono essere impostati sulle funzioni corrispondenti al sottotipo.

  • Includi puntatori alle sotto-strutture, con memoria per questo sottotipo specifico.

  • Crea tutti i sottotipi desiderati struttura distinta e copia-incolla qualsiasi codice valido per tutti. Non suona bene, ma se le differenze richiedono molto più codice rispetto alle somiglianze, potrebbe essere una scelta ragionevole. La versione don-do-this-at-home è dove si costruisce il layout della memoria in modo che lo stesso codice funzioni su diverse strutture.

Per motivi di apprendimento, penso che sia una buona idea riporre una funzionalità avanzata, specialmente se si è abituati ad abusarne. Nel codice del mondo reale, dovresti ovviamente utilizzare le funzioni OO dove ha senso.

    
risposta data 29.04.2015 - 19:50
fonte
1

Consiglierei di leggere alcuni dei "classici" della programmazione strutturata: libri di Ed Yourdon, Larry Constantine, in particolare un sacco di cose di Dave Parnas. Parlano molto del design top-down, usando diagrammi di struttura e diagrammi di flusso di dati, rompendo il codice in moduli e come gestire l'accoppiamento e la coesione. È una lettura interessante e alcuni degli strumenti (ad es. I diagrammi del flusso di dati) possono ancora essere usati oggi. A volte sento che un approccio strutturato è più adatto ad alcuni problemi (quelli che coinvolgono le macchine di analisi o di stato, di solito), ma non è un'opinione popolare.

    
risposta data 11.05.2015 - 20:17
fonte

Leggi altre domande sui tag