Adéu, programació orientada a objectes

Bloc

Adéu, programació orientada a objectes

Adéu, programació orientada a objectes

Fa dècades que programa en llenguatges orientats a objectes. El primer llenguatge OO que vaig utilitzar va ser C ++ i després Smalltalk i finalment .NET i Java.



Em van voler aprofitar els avantatges Herència , Encapsulació , i Polimorfisme . Els tres pilars del paradigma.

Tenia moltes ganes de guanyar la promesa de reutilitzar i aprofitar la saviesa adquirida per aquells que van venir abans que jo en aquest nou i emocionant paisatge.



No podia contenir la meva il·lusió per la idea de mapar els meus objectes del món real a les seves classes i esperava que tot el món quedés perfectament al seu lloc.

No m’hauria pogut equivocar més.



L'herència, el primer pilar que cau

A primera vista, l’herència sembla ser el major benefici del paradigma orientat a objectes. Tots els exemples simplistes de jerarquies de formes que es mostren com a exemples dels recentment adoctrinats semblen tenir un sentit lògic.

I Reutilitzar és la paraula del dia. No ... fes que sigui l'any i potser sempre més.

Em vaig empassar tot això i vaig sortir corrent al món amb la meva nova comprensió.

Banana Monkey Jungle Problema

Amb la religió al cor i els problemes per resoldre, vaig començar a construir jerarquies de classes i a escriure codi. I tot estava bé amb el món.

Mai no oblidaré aquell dia en què estava disposat a cobrar la promesa de reutilitzar heretant d’una classe existent. Aquest era el moment que esperava.

Va sorgir un nou projecte i vaig tornar a pensar en aquella classe que tant m’agradava en el meu darrer projecte.

Cap problema. Reutilitzeu al rescat. Tot el que he de fer és simplement agafar aquesta classe de l’altre projecte i utilitzar-la.

Bé ... en realitat ... no només aquesta classe. Necessitarem la classe dels pares. Però ... Però això és tot.

Uf ... Espera ... Sembla que també necessitarem els pares dels pares ... I després ... Necessitarem TOTS els pares. D'acord ... D'acord ... m'encarrego d'això. Cap problema.

I genial. Ara no es compilarà. Per què?? Oh, ja ho veig… Això objecte conté aquest altre objecte. Així que també ho necessitaré. Cap problema.

Espera ... no només necessito això objecte. Necessito el pare principal de l'objecte i el pare dels pares, etc., i així successivament, amb tots els objectes continguts i TOTS els pares del que contenen, juntament amb els pares, pares, pares ...

Uf

Hi ha una gran cita de Joe Armstrong , el creador d'Erlang:

El problema dels llenguatges orientats a objectes és que tenen tot aquest entorn implícit que porten amb ells. Volíeu un plàtan, però el que teníeu era un goril·la que tenia el plàtan i tota la jungla.

Puc domesticar aquest problema sense crear jerarquies massa profundes. Però si l’herència és la clau per a la reutilització, els límits que poso en aquest mecanisme segurament limitaran els beneficis de la reutilització. Dret?

Dret.

Llavors, què pot fer un pobre programador orientat a objectes, que ha tingut una bona ajuda de Kool-aid?

reaccionar la memòria cau d’imatges natives

Contenir i delegar. Més informació sobre això més endavant.

El problema del diamant

Tard o d’hora, el següent problema generarà un cap lleig i, segons l’idioma, insoluble.

La majoria dels llenguatges OO no admeten això, tot i que això sembla tenir sentit lògic. Què té de difícil donar suport a això en idiomes OO?

Bé, imagineu-vos el pseudocodi següent:

Class PoweredDevice { } Class Scanner inherits from PoweredDevice { function start() { } } Class Printer inherits from PoweredDevice { function start() { } } Class Copier inherits from Scanner, Printer { }

Tingueu en compte que tant Escàner classe i el Impressora implementen una funció anomenada començar .

Per tant, quina funció d'inici fa Per copiar heretar de classe? El Escàner un? El Impressora un? No poden ser les dues coses.

La solució de diamants

La solució és senzilla. No ho facis.

Sí, és correcte. La majoria dels idiomes OO no us permeten fer això.

Però, però ... i si he de modelar això? Vull la meva reutilització!

Llavors hauràs de fer-ho Contenir i delegar .

Class PoweredDevice { } Class Scanner inherits from PoweredDevice { function start() { } } Class Printer inherits from PoweredDevice { function start() { } } Class Copier { Scanner scanner Printer printer function start() { printer.start() } }

Fixeu-vos aquí que el fitxer Per copiar La classe ara conté una instància de Impressora i d'un Escàner . Delega el començar funció a la Impressora implementació de la classe. Es podria delegar amb la mateixa facilitat al Escàner .

Aquest problema és una altra escletxa del pilar de l’herència.

El problema de la classe base fràgil

Per tant, estic reduint les meves jerarquies i evitant que siguin cícliques. Cap diamant per a mi.

I tot estava bé amb el món. És a dir fins que ...

Un dia, el meu codi funciona i l'endemà deixa de funcionar. Aquí teniu el xutador. No he canviat el meu codi .

Bé, potser és un error ... Però espereu ... Alguna cosa va canviar ...

Però no estava al meu codi . Resulta que el canvi va pertànyer a la classe que vaig heretar.

Com podria un canvi a la classe Base trencar el meu codi ??

Així és com…

Imagineu la següent classe Base (està escrita en Java, però hauria de ser fàcil d’entendre si no coneixeu Java):

import java.util.ArrayList; public class Array { private ArrayList a = new ArrayList(); public void add(Object element) { a.add(element); } public void addAll(Object elements[]) { for (int i = 0; i

IMPORTANT : Observeu la línia de codi comentada. Aquesta línia es canviarà més endavant, cosa que trencarà les coses.

Aquesta classe té 2 funcions a la seva interfície, add () i addAll () . El add () La funció afegirà un sol element i addAll () afegirà diversos elements trucant a la funció add .

I aquí teniu la classe Derived:

public class ArrayCount extends Array { private int count = 0; @Override public void add(Object element) { super.add(element); ++count; } @Override public void addAll(Object elements[]) { super.addAll(elements); count += elements.length; } }

El ArrayCount classe és una especialització del general Matriu classe. El només la diferència de comportament és que el ArrayCount manté un comptar del nombre d'elements.

Vegem amb detall aquestes dues classes.

El Matriu add () afegeix un element a un local ArrayList .

El Matriu addAll () crida al local ArrayList afegir per a cada element.

El ArrayCount **** add () ** crida als seus pares add () i després augmenta el fitxer comptar .

El ArrayCount addAll () crida als seus pares addAll () i després augmenta el fitxer comptar pel nombre d'elements.

I tot funciona bé.

Ara pel trencant el canvi . La línia de codi comentada a la classe Base es canvia a la següent:

public void addAll(Object elements[]) { for (int i = 0; i

Pel que fa al propietari de la classe Base, encara funciona tal com s’anuncia. I totes les proves automatitzades encara superen .

Però el propietari no té en compte la classe derivada. I el propietari de la classe Derived té un despert groserós.

Ara ArrayCount addAll () crida als seus pares addAll () quin internament crida al add () que ha estat ANUL·LAT per la Derivat classe.

Això provoca la comptar que s'incrementarà cada vegada que Derivat classe add () es diu i llavors és incrementada DE NOU pel nombre d'elements que es van afegir al fitxer Derivat classe addAll () .

ES COMPTA DOS VEGADES.

Si això pot passar i ho fa, l'autor de la classe Derivada ha de SABER com s'ha implementat la classe Base. I se'ls ha d'informar de cada canvi de la classe Base, ja que podria trencar la seva classe derivada de maneres imprevisibles.

Uf! Aquesta enorme esquerda amenaça per sempre l’estabilitat d’un preciós pilar d’herència.

La solució de classe base fràgil

Una vegada més Contingueu i delegueu al rescat.

on puc comprar bytecoin

En utilitzar Contain and Delegate, passem de la programació de White Box a la programació de Black Box. Amb la programació de White Box, hem de mirar la implementació de la classe base.

Amb la programació de Black Box, podem ignorar completament la implementació, ja que no podem injectar codi a la classe Base substituint una de les seves funcions. Només ens hem de preocupar per la interfície.

Aquesta tendència és inquietant ...

Se suposava que l’herència seria una gran victòria per a Reuse.

Els idiomes orientats a objectes no faciliten el contingut i el delegat. Van ser dissenyats per facilitar l’herència.

Si sou com jo, començareu a preguntar-vos per això de l’Herència. Però, el que és més important, hauria de fer trontollar la vostra confiança en el poder de la classificació mitjançant jerarquies.

El problema de la jerarquia

Cada vegada que començo per una empresa nova, tinc dificultats amb el problema quan creo un lloc on posar els meus documents d’empresa, p. el manual dels empleats.

Puc crear una carpeta anomenada Documents i després crear una carpeta anomenada Empresa?

O puc crear una carpeta anomenada Company i després crear una carpeta anomenada Documents?

Tots dos funcionen. Però, què és correcte? Quin és el millor?

La idea de les jerarquies categòriques era que hi havia classes base (pares) més generals i que les classes derivades (nens) eren versions més especialitzades d’aquestes classes. I encara més especialitzats a mesura que anem baixant per la cadena d’herències. (Vegeu la jerarquia de formes més amunt)

Però si un pare i un fill podien canviar de lloc arbitràriament, és clar que alguna cosa no funciona amb aquest model.

La solució de la jerarquia

El que està malament és ...

Les jerarquies categòriques no funcionen .

Llavors, per a què serveixen les jerarquies?

Contenció .

Si mireu el món real, veureu jerarquies de contenció (o propietat exclusiva) a tot arreu.

El que no trobareu és Jerarquies categòriques. Deixeu que això s’enfonsi un moment. El paradigma orientat a objectes es basava en el món real, ple d’objectes. Però després fa servir un model trencat, és a dir. Jerarquies categòriques, on no hi ha analogia del món real.

Però el món real està ple de jerarquies de contenció. Un gran exemple de jerarquia de contenció són els mitjons. Es troben en un calaix de mitjons que es troba en un calaix de la vostra còmoda que es troba al vostre dormitori que es troba a casa vostra, etc.

Els directoris del disc dur són un altre exemple de jerarquia de contenció. Contenen fitxers.

Llavors, com classifiquem llavors?

Doncs bé, si penseu en els documents de l’empresa, gairebé no importa on els posi. Puc posar-los en una carpeta de documents o en una carpeta anomenada Coses.

La meva manera de classificar-lo és amb etiquetes. Etiqueto el fitxer amb les etiquetes següents:

Document Company Handbook

Les etiquetes no tenen cap ordre ni jerarquia. (Això també resol el problema del diamant).

Les etiquetes són similars a les interfícies, ja que podeu tenir diversos tipus associats al document.

Però amb tantes esquerdes, sembla que el pilar de l’Herència ha caigut.

Adéu, Herència.

L’encapsulació, el segon pilar que cau

A primera vista, l’encapsulació sembla ser el segon benefici més gran de la programació orientada a objectes.

Les variables d'estat de l'objecte estan protegides contra l'accés extern, és a dir, estan encapsulades a l'objecte.

Ja no haurem de preocupar-nos per les variables globals a les quals accedeix qui-sap-qui.

L'encapsulament és segur per a les vostres variables.

Això de l’encapsulació és INCREIBLE !!

Visca l’encapsulament ...

És a dir fins que ...

El problema de referència

Per motius d’eficiència, els objectes es passen a les funcions NO pel seu valor, sinó per referència.

El que això significa és que les funcions no passaran l'objecte, sinó que passaran a referència o punter a l'objecte.

Si es passa un objecte per referència a un constructor d'objectes, el constructor pot posar aquesta referència d'objecte en una variable privada que estigui protegida per l'encapsulació.

Però el passat L'objecte NO és segur.

Perquè no? Perquè alguna altra peça de codi té un punter cap a l’objecte, és a dir, el codi que anomenava el constructor. DEU tenir una referència a l’objecte en cas contrari, no el podria passar al constructor?

La solució de referència

El constructor haurà de clonar el passat a Object. I no un clon superficial, sinó un clon profund, és a dir, tots els objectes que es contenen en els objectes passats i tots els objectes d’aquests objectes, etc., etc.

Tant per eficiència.

I aquí teniu el xutador. No es poden clonar tots els objectes. Alguns tenen recursos del sistema operatiu associats, cosa que fa que la clonació sigui inútil o, en el pitjor dels casos, impossible.

escurçador d'URL del node js

I CADA mainstream únic Llenguatge OO té aquest problema.

Adéu, encapsulament.

El polimorfisme, el tercer pilar que cau

El polimorfisme era el fillastre de pèl-roja de la Trinitat Orientada a Objectes.

És una mena de Larry Fine del grup.

Arreu on anaven hi era, però només era un personatge secundari.

No és que el polimorfisme no sigui genial, és que no necessiteu un llenguatge orientat a objectes per aconseguir-ho.

Les interfícies us donaran això. I sense tot l’equipatge d’OO.

I amb Interfaces, no hi ha límit de quants comportaments diferents podeu barrejar.

Així doncs, sense molt de més, ens acomiadem OO Polimorfisme i hola a basat en interfície Polimorfisme.

Promeses trencades

Bé, OO segur que va prometre molt els primers dies. I aquestes promeses encara es fan als programadors ingènus que seuen a les aules, llegeixen blocs i prenen cursos en línia.

He trigat anys a adonar-me de com OO em mentia. Jo també tenia els ulls oberts, inexpert i confiant.

I em vaig cremar.

Adéu, programació orientada a objectes.

Llavors, què?

Hola, Programació funcional . Ha estat molt agradable treballar amb vosaltres durant els darrers anys.

Perquè ho sàpiga, NO prenc cap de les vostres promeses al màxim. Ho hauré de veure per creure-ho.

Un cop cremat, dues vegades tímid i tot.

Ho entens.

#oop # desenvolupament web