Pensi che la programmazione orientata agli oggetti sia una soluzione alla complessità. Perché? Questo argomento potrebbe essere un po 'controverso, ma le mie intenzioni di conoscere la risposta di Why dagli esperti qui!
Non esiste una soluzione alla complessità.
In "The Mythical Man-Month", Fred Brooks discute la differenza tra la complessità accidentale ed essenziale nella programmazione. La complessità accidentale è causata dai nostri strumenti e metodi, come dover scrivere e testare codice aggiuntivo in una lingua perché non possiamo esprimere le nostre idee direttamente, e cose del genere. Nuovi metodi e tecniche possono ridurre la complessità accidentale. Posso scrivere programmi più velocemente e meglio di quanto potessi venticinque anni fa, perché ho linguaggi e strumenti migliori.
La complessità essenziale deriva dal fatto che ciò che proviamo a fare con la programmazione è intrinsecamente complicato e che esiste una complessità irriducibile. "Essenziale", in questo contesto, significa "relativo all'essenza della cosa" piuttosto che "molto necessario".
Pertanto, ha affermato che non ci sarebbe stato un proiettile d'argento, che il software di scrittura avrebbe continuato a essere difficile.
Raccomando vivamente di leggere il suo libro: in particolare, raccomando l'edizione del Silver Anniversary, con un saggio aggiuntivo "No Silver Bullet". In tal modo, esamina le soluzioni proposte per la complessità e ne valuta l'impatto. (Quello che trova il più efficace è il software a film termoretraibile - scrivi qualcosa di complesso una volta, e vendi migliaia o milioni di copie.)
Ora, la programmazione orientata agli oggetti aiuta, se fatto bene, creando astrazioni e nascondendo la complessità. Un oggetto di una classe ha un determinato comportamento definito da cui possiamo ragionare, senza preoccuparsi della complessità dell'implementazione. Le classi scritte correttamente hanno un basso accoppiamento tra loro, e divide et impera è un modo eccellente per affrontare la complessità se riesci a farla franca. Hanno anche un'elevata coesione, in quanto sono un insieme di funzioni e dati strettamente correlati tra loro.
Mi aspetto che riceverai alcune risposte migliori a breve, ma eccone una semplice:
OOP aiuta * con complessità modellando il software in un modo più vicino al modo in cui modelliamo tutto il resto del mondo. In genere è più semplice immaginare un oggetto palla che interagisce con un oggetto muro piuttosto che immaginare una serie di routine e strutture dati per fare la stessa cosa, poiché è più vicino al modo in cui interagiamo con il mondo reale.
* Perché nulla può "risolvere" la complessità
Penso che l'attuale definizione tradizionale di OOP non sia una buona soluzione per la gestione della complessità.
Se torni alle sue radici, credo che Alan Kay sia stato influenzato dalla "lisp" molto.
Poiché il Lisp non è stato corrotto dall'adozione tradizionale, probabilmente è riuscito a conservare i suoi valori fondamentali. Quindi penso che guardare a come affronta questo problema di complessità potrebbe darci qualche intuizione, e possiamo usarlo come base per giudicare quanto sia utile OOP nel trattare la complessità.
Se guardi la fine della "Lecture 3a: Henderson Escher Example" di SICP , Hal Abelson propone che la complessità sia gestita non suddividendo il compito in sotto-azioni più piccole, ma creando strati di astrazione. Al più alto livello, esprimi la soluzione al complicato problema in termini di soluzione al livello inferiore di astrazione.
Penso che OOP fosse originariamente inteso come un meccanismo per creare questi strati di astrazioni.
Sfortunatamente oggigiorno, OOP è (ab) usato per scrivere codice / strutture spaghetti.
Faccio un esempio: un gioco multi-giocatore FPS.
Al livello più alto, il gioco funziona con un gruppo di giocatori che corrono su una mappa e si sparano a vicenda usando le armi.
Al prossimo livello inferiore, dobbiamo parlare di mappe, armi e giocatori. Forse possiamo parlarne come oggetti fisici che interagiscono nel mondo del gioco.
Al prossimo livello inferiore, possiamo parlare di come gli oggetti interagiscono fisicamente (movimento, collisione, ecc.).
E così via e così via.
Che cosa significa, (e sono un po 'citando da SICP ..), è che ad ogni livello, non solo risolviamo un particolare problema specifico, ma una classe di problemi che cadono nel vicinato del problema che stiamo cercando di risolvere. Quindi, se c'è una piccola modifica nella descrizione del problema, probabilmente richiederebbe solo una piccola modifica nella soluzione.
Quindi, il modo saggio di usare OOP è creare strati di astrazioni, ad ogni livello di astrazione, risolvi il problema usando gli "oggetti" dal livello che è direttamente sotto.
Ecco il bit che stavo citando dalla lezione: link
Come al solito non sono d'accordo con tutti. Lungi dall'offrirti strumenti per gestire la complessità, l'OOP crea un'enorme quantità di complessità perché è un paradigma inadeguato e matematicamente falso. Confonde i programmatori senza fine, per provare a modellare le cose con OOP che non possono essere modellate con OOP.
Dal mio punto di vista il lavoro fondamentale qui è la costruzione di software orientato agli oggetti di Meyer. Descrive dettagliatamente una serie di requisiti, incluso uno che considero cruciale: il principio Open-Closed. Questo dice che una cosa deve essere aperta per estensione ma chiusa per l'uso, allo stesso tempo.
Meyer procede a derivare l'Orientamento degli oggetti da questi requisiti, come incorporato in Eiffel. L'incapsulamento fornisce la chiusura, l'apertura dell'ereditarietà e la "cosa" menzionata è la classe.
Considero questo lavoro una buona scienza perché Meyer era palesemente sbagliato, ed è possibile a causa della qualità del suo lavoro, individuare l'errore e correggerlo.
L'errore sta rendendo la classe, o il tipo, l'unità di modularità. Questo è sbagliato, e in modo dimostrabile. Anche Meyer ha riconosciuto il problema (chiamato il problema della covarianza), che OOP non può gestire le relazioni di arità più alte di una (cioè, OOP funziona bene per le proprietà ma fallisce nelle relazioni binarie). In Eiffel, questo problema ha comportato un errore nel sistema di tipi!
La soluzione è abbastanza chiara. L'unità di modularità deve essere più grande di un singolo tipo. Deve essere composto da diversi tipi e dai metodi che li riguardano.
Non sorprende che questo modello di astrazione sia supportato dalla teoria matematica dell'astrazione, vale a dire la teoria delle categorie: i tipi sono oggetti di una categoria ei metodi (funzioni) sono frecce.
Con questo modello, le rappresentazioni di diversi tipi sono conosciute da un insieme di funzioni. La rappresentazione è nascosta al pubblico, quindi questa è encapulsation, ma usiamo i moduli, non le classi.
Standard Meta-Language (SML) e Ocaml sono basati direttamente su questo modello. Ocaml ha anche classi e OOP: non è inutile perché OOP ti dà dispatch sulle proprietà, ovvero l'associazione dinamica. Tuttavia la maggior parte dei problemi del mondo reale coinvolgono le relazioni e non sorprende che le classi non vengano utilizzate molto in Ocaml.
Non sorprende che l'ereditarietà non sia affatto utilizzata nella libreria di template standard C ++.
Il semplice fatto è che OOP non ti dà gli strumenti giusti per gestire la complessità, non ti dà nemmeno gli strumenti per gestire problemi davvero semplici, invece ha fuorviato e confuso due generazioni di programmatori. Lontano dall'aiutare, l'OOP è la cosa più cattiva e cattiva che è accaduta alla programmazione da quando C, Fortran e Cobol hanno iniziato a stancarsi.
La programmazione orientata agli oggetti ha radici che possono essere ricondotte agli anni '60. Man mano che l'hardware e il software diventavano sempre più complessi, la gestibilità diventava spesso una preoccupazione. I ricercatori hanno studiato i modi per mantenere la qualità del software e sviluppato una programmazione orientata agli oggetti in parte per affrontare problemi comuni, sottolineando strongmente unità discrete e riutilizzabili della logica di programmazione.
Un programma orientato agli oggetti può quindi essere visto come una collezione di oggetti interagenti, al contrario del modello convenzionale, in cui un programma è visto come una lista di compiti (subroutine) da eseguire. In OOP, ciascun oggetto è in grado di ricevere messaggi, elaborare dati e inviare messaggi ad altri oggetti. Ogni oggetto può essere visto come una "macchina" indipendente con un ruolo o una responsabilità distinti. Le azioni (o "metodi") su questi oggetti sono strettamente associate all'oggetto stesso.
Questa separazione di preoccupazioni, insieme ad altre caratteristiche dell'Organizzazione degli oggetti come il polimorfismo, l'ereditarietà, il passaggio dei messaggi, il disaccoppiamento e l'incapsulamento forniscono una struttura logica e concettuale grazie alla quale la complessità dei grandi programmi può essere gestita in modo estremamente efficace.
Ci sono molti tipi di complessità nello sviluppo del software. A livello di programmazione, OOP cerca di affrontare la complessità utilizzando oggetti e classi per modellare il dominio del problema. Un noto guru ha detto che la soluzione dei problemi è solo la rappresentazione del problema in modo che la soluzione sia la rappresentazione stessa. Quindi, mediante l'astrazione utilizzando le classi, l'incapsulamento utilizzando i modificatori e i metodi di accesso, l'ereditarietà per specificare la relazione e il riutilizzo, la composizione nello stabilire relazioni e la collaborazione tra classi, il polimorfismo come mezzo per semplificare la determinazione di comportamenti diversi in oggetti simili, la complessità può essere gestita.
Esistono anche altri modi per gestire la complessità del software, ad esempio la programmazione logica (Prolog) e funzionale (Haskell).
A un livello superiore rispetto alla programmazione, abbiamo bisogno di schemi e principi di progettazione per guidare OOP. Quindi OOP sta gestendo la complessità a un livello basso (codifica) mentre queste metodologie come Design Pattern e Principi guidano la progettazione della soluzione a un livello superiore (di sistema e di applicazione) e rendono più gestibili lo sviluppo e la gestione del software.
Per rispondere alla tua domanda, sì, OOP è solo una soluzione per gestire la complessità tra molte altre soluzioni. È una soluzione a basso livello. Abbiamo bisogno di modelli e principi di progettazione per guidare OOP a un livello superiore.
La programmazione orientata agli oggetti gestisce la complessità essenziale e facoltativa, ma non riduce neanche.
Preferisco la definizione fornita da Eric Steven Raymond in The Art of Unix Programming , perché delinea tra la complessità essenziale, facoltativa e accidentale. link
OOP non fa nulla per la complessità essenziale o facoltativa, sono una funzione dei requisiti del programma. Può avere un effetto sulla complessità accidentale, in quanto è possibile creare un design più elegante a volte con OOP. A volte, tuttavia, il design è peggiore quando si utilizza OOP.
I problemi complessi non possono essere resi più semplici attraverso la tecnologia, possono solo essere gestiti tramite la tecnologia.
OOP è una tecnologia, un concetto e un modo per affrontare un problema.
OOP ti offre gli strumenti per applicare un design che può semplificare la gestione della complessità, ma puoi altrettanto facilmente avere un design errato che aumenta la tua complessità. In altre parole, se non usato correttamente, puoi avere una complessità indotta dalla tecnologia nei tuoi problemi.
Tieni presente che ci sono molti altri aspetti che determineranno il successo del tuo progetto (cioè stile di gestione del progetto, definizione del problema, gestione del cambiamento, ecc ...). La tecnologia che usi è rilevante solo in quanto ti aiuterà a gestire il problema.
Alla fine, la programmazione orientata agli oggetti non può essere una soluzione alla complessità; è solo uno strumento per gestirlo. (se usato correttamente)
L'Orientamento degli oggetti (come usato convenzionalmente) è uno strumento utile in molte circostanze, ma non è una soluzione sufficiente per la complessità.
In particolare, spesso aggiunge un sacco di " complessità accidentale ". Gli esempi sono la complessità che circonda l'ereditarietà dell'implementazione, la necessità di fornire un sacco di "standard funzionalità" suach come equals () e hashCode (), ecc. Una bella presentazione di Stuart Halloway su questo argomento: " La semplicità non è facile "
Gli oggetti nella maggior parte delle lingue tendono anche a incapsulare un sacco di stato mutevole - che in un mondo concorrente sta cominciando sempre più ad apparire come una pessima decisione progettuale. Anche in questo caso un interessante video di Rich Hickey esamina la distinzione tra identità e stato dell'oggetto, e come potrebbe essere un errore confondere i due.
La programmazione orientata agli oggetti è un modo di rappresentare un problema, niente di più, niente di meno. È, in sé e per sé, non meno complesso di qualsiasi altro paradigma di programmazione. Un sistema OOP ben progettato gestisce e riduce la complessità, ma è anche molto facile progettare un sistema che è molto più complesso del necessario e che intralcia tutto.
Come spesso si dice del C ++, OOP ti dà abbastanza corda per impiccarti.
Penso SÌ , solo perché ti consente di suddividere la complessità in piccoli "blocchi" autosufficienti che nascondono i dettagli e quindi li usa per creare la funzionalità di cui hai bisogno, passo dopo passo, strato per strato.
Dividi e conquista.
OOP è un tentativo di soluzione.
Il modo migliore per gestire la complessità è creare astrazioni. Se posso trasformare i miei dati in raccolte utili, con funzioni riconoscibili che operano su quelle collezioni, posso iniziare a pensare alle collezioni come a "cose" discrete. Questa è la base per classi e metodi. A tale riguardo, l'OOP correttamente progettato può aiutare a gestire la complessità.
Da qualche parte lungo la strada, qualcuno ha deciso che potremmo usare OOP per aiutare a risolvere il problema del riutilizzo del codice. Voglio dire, perché reinventare la ruota? Se qualcun altro ha fatto gran parte del lavoro per risolvere questo problema, fai leva su ciò che hanno fatto, aggiungi le modifiche che il tuo, particolare progetto richiede e voilà! Hai creato un'applicazione potente e sofisticata con relativamente poco lavoro da parte tua. I programmatori OO possono essere programmatori molto produttivi.
Il risultato finale è che i moderni programmatori OO finiscono per essere "apprendisti stregoni", dove legano insieme un mucchio di librerie grandi e ingombranti con poche righe di "colla" e ottengono qualcosa che funzioni. Sorta. Tipo. La maggior parte delle volte. Ci sono potenziali effetti collaterali dall'uso di questa libreria con quella? Può essere. Ma chi ha tempo per scavare davvero nel codice contenuto in quelle librerie? Soprattutto quando le biblioteche si stanno evolvendo. Il risultato è che ci ritroviamo con applicazioni gonfie, in cui un programmatore aveva bisogno di una manciata di classi e metodi da quella libreria, ma l'app deve avere il peso di tutte le ALTRE cose di cui non aveva bisogno.
Il risultato finale è che ti ritroverai con una complessità molto superiore a quella di cui hai bisogno.
Un altro meccanismo per gestire la complessità che vuoi separare dalla funzionalità. Vuoi tutte le tue funzioni di accesso ai dati in un unico posto. Vuoi tutte le funzionalità dell'interfaccia utente in un unico posto. Vuoi tutti i controller in un unico posto. Quindi crei diverse classi che gestiscono diverse parti della funzionalità. Fin qui tutto bene. E questo scala, in una certa misura; i tuoi sviluppatori che sono esperti nell'accesso ai dati possono scrivere quelle classi, le persone dell'interfaccia utente possono scrivere le classi dell'interfaccia utente, ecc. Tutto va bene.
Fino a quando non devi mantenere qualcosa scritto da qualcun altro.
Sì, è bene sapere che tutte le funzioni di accesso ai dati si trovano qui. Ma cosa li chiama?
Questo metodo sta chiamando quel metodo su quella classe. Ma quando guardo la definizione della classe, non esiste un metodo con quel nome. Oh, questo è ereditato da qualcos'altro uno o due strati nella catena dell'eredità. Apetta un minuto; quella classe ha implementato un'interfaccia? Quante classi diverse implementano quell'interfaccia? E stiamo usando un complicato sistema di runtime (ti sto guardando, Spring) per "collegare insieme" istanze di classi in fase di esecuzione? Dove è possibile utilizzare QUALSIASI classe che implementa tale interfaccia?
Finisci con un sacco di metodi piccoli e discreti che fanno cose precise. Ma questo lo chiama, in un'altra classe. Che chiama quello, in un'altra classe. Questo lo chiama, in un'altra classe ancora. Che chiama quello, in una classe aggiuntiva. Che restituisce un risultato di un tipo particolare. Su cui devi chiamare un metodo per fare una certa cosa. Che restituisce un risultato di un altro tipo. Ecc.
C'è un termine per questo: spaghetti code.
Finisci con un sistema molto complesso, necessario solo per comporre il codice. Da qui gli IDE come Visual Studio, Eclipse e NetBeans. Tutti hanno una curva di apprendimento significativa. Infatti, molti di loro sono in grado di incapsulare / aggregare più strumenti, sviluppati da diversi gruppi, ognuno dei quali ha le proprie curve di apprendimento.
Questa è la gestione della complessità?
Il debug del codice è due volte più difficile della sua scrittura. Buona fortuna per il debug di alcune di queste cose. Soprattutto se utilizza librerie multiple, "cablate insieme" in fase di esecuzione utilizzando una sorta di sistema di iniezione delle dipendenze.
In sintesi: OOP fornisce quello che sembra uno strumento promettente per aiutare a gestire la complessità. La realtà è che il codice risultante tende ad essere orribilmente gonfio (perché non puoi estrarre solo i pezzi che ti servono da tutte quelle librerie collegate) e hai bisogno di strumenti sofisticati solo per navigare nel codice. Diventa rapidamente un incubo di manutenzione.
IMHO, è una perdita netta; aggiunge più complessità di quella che elimina. Ti permette di fare cose che sarebbero estremamente difficili, forse anche impossibili, senza di essa. Ma qualsiasi progetto di grandi dimensioni si evolve rapidamente in un disordine irraggiungibile.
Se sai già come funziona, e puoi ricordarlo, potresti avere una possibilità di mantenerlo.
Ricorda di applicare la legge di Eagleson: qualsiasi codice personale, che non hai guardato in sei mesi, potrebbe anche essere scritto da qualcun altro.
In una certa misura ...
Perché? Perché facilita la modularità molto logica. Almeno in confronto alla programmazione procedurale in cui è fin troppo allettante scrivere solo enormi pile di codice spaghetti.
La ragione per cui la programmazione orientata agli oggetti sembra aiutarci a gestire la complessità è perché ci costringe a scrivere codice in un modo specifico anziché in una grande varietà di modi. La programmazione orientata alle attività è molto più intuitiva, motivo per cui la programmazione è iniziata in questo modo. L'orientamento all'oggetto richiede allenamento e pratica per comprendere e utilizzare in modo efficace, ma limitando la programmazione in un determinato percorso, consente a chi è addestrato di mantenere efficacemente il codice che è stato scritto.
Non è più logico o reale di qualsiasi altro metodo, è solo un modo per focalizzare la nostra risoluzione dei problemi attraverso obiettivi simili. Molte specialità tecniche usano il paradigma di una metodologia rigida non intuitiva per gestire la complessità dei loro compiti.
Un terzo metodo per gestire la complessità sarebbe la programmazione funzionale e probabilmente ci saranno anche altri nuovi metodi in futuro.
Penso che sia più una soluzione per la manutenibilità, perché, come programmatore, dovresti mettere i metodi in cui hai i dati, creando così un modello a oggetti della tua applicazione.
Sì, è anche una soluzione alla complessità fornendo un modello per "vedere" il codice in modo naturale, come oggetti che hanno proprietà e possibili azioni
Leggi altre domande sui tag object-oriented complexity