¿Qué problemas de programación de procedimientos resuelve OOP en la práctica?

17

He estudiado el libro "C ++ desmitificado" . Ahora he comenzado a leer "Programación orientada a objetos en Turbo C ++ primera edición (1ª edición)" de Robert Lafore. No tengo ningún conocimiento de programación que esté más allá de estos libros. Este libro puede estar desactualizado porque tiene 20 años. Tengo la última edición, estoy usando la anterior porque me gusta, principalmente estoy estudiando los conceptos básicos de OOP utilizados en C ++ a través de la primera edición del libro de Lafore.

El libro de Lafore enfatiza que "OOP" solo es útil para programas más grandes y complejos . Se dice en cada libro de OOP (también en el libro de Lafore) que el paradigma de procedimiento es propenso a errores, por ejemplo, los datos globales son fácilmente vulnerables por las funciones. Se dice que el programador puede cometer errores honestos en lenguajes de procedimiento, por ejemplo, al hacer una función que corrompe accidentalmente los datos.

Hablando honestamente, estoy publicando mi pregunta porque no estoy captando la explicación dada en este libro: Programación Orientada a Objetos en C ++ (4ª Edición) No estoy captando estas declaraciones escritas en el libro de Lafore:

La programación orientada a objetos se desarrolló porque se descubrieron limitaciones en enfoques anteriores de programación ... A medida que los programas se vuelven cada vez más grandes y complejos, incluso el enfoque de programación estructurada comienza a mostrar signos de tensión ... ... Analizando las razones de Estas fallas revelan que hay debilidades en el paradigma procesal mismo. No importa qué tan bien se implemente el enfoque de programación estructurada, los programas grandes se vuelven excesivamente complejos ... ... Hay dos problemas relacionados. Primero, las funciones tienen acceso ilimitado a datos globales. En segundo lugar, las funciones y datos no relacionados, la base del paradigma procesal, proporcionan un modelo pobre del mundo real ...

He estudiado el libro "C ++ dismitificado" de Jeff Kent, me gusta mucho este libro, en este libro se explica principalmente la programación de procedimientos. ¡No entiendo por qué la programación procesal (estructurada) es débil!

El libro de Lafore explica muy bien el concepto con algunos buenos ejemplos. También he captado una intuición al leer el libro de Lafore de que la OOP es mejor que la programación de procedimientos, pero tengo curiosidad por saber cómo exactamente en la práctica la programación de procedimientos es más débil que la OOP.

Quiero verme cuáles son los problemas prácticos que uno enfrentaría en la programación de procedimientos, cómo la OOP facilitará la programación. Creo que obtendré mi respuesta solo leyendo el libro de Lafore contemplativamente, pero quiero ver con mis propios ojos los problemas en el código de procedimiento, quiero ver cómo el código de estilo OOP de un programa elimina los errores anteriores que sucederían si el el mismo programa se escribiría usando el paradigma de procedimiento.

Hay muchas características de OOP y entiendo que no es posible que alguien me explique cómo todas estas características eliminan los errores anteriores que generarían al escribir el código en un estilo de procedimiento.

Ésta es mi pregunta:

¿Qué limitaciones de la programación de procedimientos aborda OOP y cómo elimina efectivamente estas limitaciones en la práctica?

En particular, ¿hay ejemplos de programas que son difíciles de diseñar usando el paradigma de procedimiento pero que se diseñan fácilmente usando OOP?

PD: Cross publicado desde: /programming//q/22510004/3429430

usuario31782
fuente
3
La distinción entre programación procesal y programación orientada a objetos es, en cierta medida, una cuestión de presentación y énfasis. La mayoría de los lenguajes que se anuncian como orientados a objetos también son de procedimiento: los términos analizan diferentes aspectos del lenguaje.
Gilles 'SO- deja de ser malvado'
2
Reabrí la pregunta ahora; Vamos a ver cómo va esto. Tenga en cuenta que cualquier tratamiento que presente OOP es santo grial, resuelve todos los problemas, el mesías de los lenguajes de programación está diciendo tonterías. Hay pros y contras. Estás preguntando por los profesionales, y esa es una pregunta justa; No esperes más.
Raphael
La idea de diseñar una GUI de escritorio (moderna) sin OOP y algunas técnicas que crecieron en ella (por ejemplo, patrón de evento / escucha) me asusta. Sin embargo, eso no significa que no se pueda hacer (ciertamente se puede ). Además, si desea ver la falla de PP en el trabajo, mire PHP y compare la API con, por ejemplo, Ruby.
Raphael
1
"No importa qué tan bien se implemente el enfoque de programación estructurada, los programas grandes se vuelven excesivamente complejos ..." eso es muy cierto también para OOP pero básicamente maneja mejor la complejidad si los desarrolladores lo aplican de la manera prescrita ... y lo hace en gran medida a través de límites / restricciones mejores / más nítidos, es decir, una especie de sistema de compartimentación ... también conocido como APIE: Abstracción, Polimorfismo, Herencia, Encapsulación
vzn

Respuestas:

9

En un lenguaje de procedimiento, no necesariamente puede expresar las restricciones que se requieren para demostrar que la persona que llama está utilizando un módulo de manera compatible. En ausencia de una restricción comprobable del compilador, debe escribir documentación y esperar que se siga, y usar pruebas unitarias para demostrar los usos previstos.

La declaración de tipos es la restricción declarativa más obvia (es decir, "demostrar que x es flotante"). Obligar a las mutaciones de datos a pasar a través de una función que se sabe que está diseñada para esos datos es otra. La aplicación del protocolo (orden de invocación de método) es otra restricción parcialmente admitida, es decir: "constructor -> otros métodos * -> destructor".

También hay beneficios reales (y algunos inconvenientes) cuando el compilador conoce el patrón. La escritura estática con tipos polimórficos es un problema cuando se emula la encapsulación de datos desde un lenguaje de procedimiento. Por ejemplo:

el tipo x1 es un subtipo de x, t1 es un subtipo de t

Esta es una forma de encapsular datos en un lenguaje de procedimiento para tener un tipo t con los métodos fyg, y una subclase t1 que hace lo mismo:

t_f (t, x, y, z, ...), t_g (t, x, y, ...) t1_f (t1, x, y, z, ...)

Para utilizar este código tal como está, debe hacer una verificación de tipo y activar el tipo de t antes de decidir el tipo de f que invocará. Podrías solucionarlo así:

escriba t {d: datos f: función g: función}

De modo que invoque tf (x, y, z) en su lugar, donde una verificación de tipo y cambio para encontrar el método ahora se reemplaza con solo tener cada puntero de método de almacenamiento de instancia explícitamente. Ahora, si tiene una gran cantidad de funciones por tipo, esta es una representación inútil. Entonces, podría usar otra estrategia, como hacer que t señale a una variable m que contiene todas las funciones miembro. Si esta característica es parte del lenguaje, puede dejar que el compilador descubra cómo lidiar con una representación eficiente de este patrón.

Pero la encapsulación de datos es el reconocimiento de que el estado mutable es malo. La solución orientada a objetos es ocultarlo detrás de los métodos. Idealmente, todos los métodos en un objeto tendrían un orden de llamada bien definido (es decir: constructor -> abrir -> [leer | escribir] -> cerrar -> destruir); que a veces se llama 'protocolo' (investigación: "Singularidad de Microsoft"). Pero más allá de la construcción y la destrucción, estos requisitos generalmente no son parte del sistema de tipos, ni están bien documentados. En este sentido, los objetos son instancias concurrentes de máquinas de estado que se hacen transición mediante llamadas a métodos; de modo que pueda tener varias instancias y usarlas de forma arbitrariamente intercalada.

Pero al reconocer que el estado compartido mutable es malo, se puede observar que la orientación del objeto puede crear un problema de concurrencia porque la estructura de datos del objeto es un estado mutable al que muchos objetos tienen referencia. La mayoría de los lenguajes orientados a objetos se ejecutan en el hilo de la persona que llama, lo que significa que hay condiciones de carrera en las invocaciones de métodos; y mucho menos en secuencias no atómicas de llamadas a funciones. Como alternativa, cada objeto podría obtener mensajes asíncronos en una cola y atenderlos a todos en el hilo del objeto (con sus métodos privados), y responder al llamante enviándole mensajes.

Compare las llamadas al método Java en un contexto de subprocesos múltiples con los procesos de Erlang que envían mensajes (que solo hacen referencia a valores inmutables) entre sí.

La orientación irrestricta del objeto en combinación con el paralelismo es un problema debido al bloqueo. Existen técnicas que van desde la memoria transaccional de software (es decir, transacciones ACID en objetos de memoria similares a las bases de datos) hasta el uso de un enfoque de "compartir memoria mediante comunicación (datos inmutables)" (híbrido de programación funcional).

En mi opinión, la literatura de Orientación de Objetos se enfoca demasiado en la herencia y no lo suficiente en el protocolo (orden de invocación de métodos verificables, condiciones previas, condiciones posteriores, etc.). La entrada que consume un objeto generalmente debe tener una gramática bien definida, expresable como un tipo.

Robar
fuente
¿Está diciendo que en los lenguajes OO, el compilador puede verificar si los métodos se usan en el orden prescrito u otras restricciones en el uso de módulos? ¿Por qué el "reconocimiento de [...] encapsulación de datos de que el estado mutable es malo"? Cuando hablas de polimorfismo, ¿estás asumiendo que estás usando un lenguaje OO?
babou
En OO, la capacidad más importante es poder ocultar la estructura de datos (es decir, requerir que las referencias sean de este tipo .x) para demostrar que todos los accesos pasan por métodos. En un lenguaje OO estáticamente tipado, estás declarando aún más restricciones (según los tipos). La nota sobre la ordenación de métodos solo dice que OO obliga al constructor a llamarse primero y al destructor último; que es el primer paso para prohibir estructuralmente las órdenes de llamadas incorrectas. Las pruebas fáciles son un objetivo importante en el diseño del lenguaje.
Rob
8

La programación procesal / funcional no es de ninguna manera más débil que OOP , incluso sin entrar en argumentos de Turing (mi lenguaje tiene poder de Turing y puede hacer cualquier cosa que otro haga), lo que no significa mucho. En realidad, las técnicas orientadas a objetos se experimentaron por primera vez en lenguajes que no las tenían incorporadas. En este sentido, la programación OO es solo un estilo específico de programación de procedimientos . Pero ayuda a aplicar disciplinas específicas, como la modularidad, la abstracción y el ocultamiento de información que son esenciales para la comprensión y el mantenimiento del programa.

Algunos paradigmas de programación evolucionan desde la visión teórica de la computación. Un lenguaje como Lisp evolucionó a partir del cálculo lambda y la idea de la meta-circularidad de los lenguajes (similar a la reflexividad en el lenguaje natural). Las cláusulas de Horn engendraron Prolog y la programación de restricciones. La familia Algol también se debe al cálculo lambda, pero sin reflexividad incorporada.

Lisp es un ejemplo interesante, ya que ha sido el banco de pruebas de muchas innovaciones de lenguaje de programación, que se puede rastrear hasta su doble herencia genética.

Sin embargo, los idiomas evolucionan, a menudo bajo nuevos nombres. Un factor importante de la evolución es la práctica de programación. Los usuarios identifican las prácticas de programación que mejoran las propiedades de los programas, como la legibilidad, la mantenibilidad y la capacidad de prueba de la corrección. Luego, intentan agregar a los idiomas características o restricciones que admitirán y, a veces, impondrán estas prácticas para mejorar la calidad de los programas.

Lo que esto significa es que estas prácticas ya son posibles en un lenguaje de programación anterior, pero requiere comprensión y disciplina para usarlas. Incorporarlos a nuevos lenguajes como conceptos primarios con sintaxis específica hace que estas prácticas sean más fáciles de usar y de entender fácilmente, particularmente para los usuarios menos sofisticados (es decir, la gran mayoría). También hace la vida un poco más fácil para los usuarios sofisticados.

De alguna manera, es para diseñar el lenguaje lo que un subprograma / función / procedimiento es para un programa. Una vez que se identifica el concepto útil, se le da un nombre (posiblemente) y una sintaxis, de modo que pueda usarse fácilmente en todos los programas desarrollados con ese lenguaje. Y cuando tenga éxito, también se incorporará en futuros idiomas.

Ejemplo: recreación de la orientación a objetos

Ahora trato de ilustrar eso en un ejemplo (que sin duda podría pulirse aún más, con el tiempo). El propósito del ejemplo no es mostrar que un programa orientado a objetos puede escribirse en un estilo de programación procesal, posiblemente a expensas de la lisibilidad y la facilidad de mantenimiento. Prefiero tratar de mostrar que algunos lenguajes sin instalaciones OO pueden usar funciones de orden superior y estructura de datos para crear los medios para imitar efectivamente la Orientación de Objetos , para beneficiarse de sus cualidades con respecto a la organización del programa, incluyendo modularidad, abstracción y ocultación de información. .

Como dije, Lisp fue el banco de pruebas de mucha evolución del lenguaje, incluido el paradigma OO (aunque lo que podría considerarse el primer lenguaje OO fue Simula 67, en la familia Algol). Lisp es muy simple, y el código para su intérprete básico es menos de una página. Pero puedes hacer programación OO en Lisp. Todo lo que necesitas es funciones de orden superior.

No utilizaré la sintaxis esotérica de Lisp, sino más bien el seudocódigo, para simplificar la presentación. Y consideraré un problema esencial simple: ocultación de información y modularidad . Definir una clase de objetos mientras se evita que el usuario acceda (la mayoría de) la implementación.

Supongamos que quiero crear una clase llamada vector, que represente vectores bidimensionales, con métodos que incluyen: suma de vectores, tamaño de vector y paralelismo.

function vectorrec () {  
  function createrec(x,y) { return [x,y] }  
  function xcoordrec(v) { return v[0] }  
  function ycoordrec(v) { return v[1] }  
  function plusrec (u,v) { return [u[0]+v[0], u[1]+v[1]] }  
  function sizerec(v) { return sqrt(v[0]*v[0]+v[1]*v[1]) }  
  function parallelrec(u,v) { return u[0]*v[1]==u[1]*v[0]] }  
  return [createrec, xcoordrec, ycoordrec, plusrec, sizerec, parallelrec]  
  }  

Entonces puedo asignar el vector creado a los nombres de funciones reales que se utilizarán.

[vector, xcoord, ycoord, vplus, vsize, vparallel] = vectorclass ()

¿Por qué ser tan complicado? Porque puedo definir en la función construcciones intermedias vectorrec que no quiero que sean visibles para el resto del programa, a fin de preservar la modularidad.

Podemos hacer otra colección en coordenadas polares.

function vectorpol () {  
  ...  
  function pluspol (u,v) { ... }  
  function sizepol (v) { return v[0] }  
  ...  
  return [createpol, xcoordpol, ycoordpol, pluspol, sizepol, parallelpol]  
  }  

Pero es posible que desee utilizar indistintamente ambas implementaciones. Una forma de hacerlo es agregar un componente de tipo a todos los valores y definir todas las funciones anteriores en el mismo entorno: luego puedo definir cada una de las funciones devueltas para que primero pruebe el tipo de coordenadas, luego aplique la función específica para ello.

function vector () {  
    ...  
    function plusrec (u,v) { ... }  
    ...  
    function pluspol (u,v) { ... }  
    ...  
    function plus (u,v) { if u[2]='rec' and v[2]='rec'  
                            then return plusrec (u,v) ... }  

    return [ ..., plus, ...]  
    }

Lo que he ganado: las funciones específicas permanecen invisibles (debido al alcance de los identificadores locales), y el resto del programa solo puede usar las funciones más abstractas devueltas por la llamada a vectorclass.

Una objeción es que podría definir directamente cada una de las funciones abstractas en el programa y dejar dentro de la definición de las funciones dependientes del tipo de coordenadas. Entonces estaría oculto también. Eso es cierto, pero luego el código para cada tipo de coordenadas se cortaría en pequeños trozos repartidos por el programa, lo que es menos legible y mantenible.

En realidad, ni siquiera necesito darles un nombre, y podría mantener los valores funcionales como anónimos en una estructura de datos indexada por el tipo y una cadena que representa el nombre de la función. Esta estructura que es local para el vector de funciones sería invisible para el resto del programa.

Para simplificar el uso, en lugar de devolver una lista de funciones, puedo devolver una sola función llamada aplicar tomando como argumento un valor de tipo explícito y una cadena, y aplicar la función con el tipo y nombre adecuados. Esto se parece mucho a llamar a un método para una clase OO.

Me detendré aquí, en esta reconstrucción de una instalación orientada a objetos.

Lo que intenté hacer es mostrar que no es demasiado difícil construir una orientación de objeto utilizable en un lenguaje suficientemente potente, incluida la herencia y otras características similares. La metacircularidad del intérprete puede ayudar, pero principalmente en un nivel sintáctico, que aún está lejos de ser insignificante.

Los primeros usuarios de la orientación a objetos experimentaron los conceptos de esa manera. Y eso es generalmente cierto para muchas mejoras en los lenguajes de programación. Por supuesto, el análisis teórico también tiene un papel y ayudó a comprender o refinar estos conceptos.

Pero la idea de que los lenguajes que no tienen características OO están condenados al fracaso en algunos proyectos es simplemente injustificada. Si es necesario, pueden imitar la implementación de estas características de manera bastante efectiva. Muchos lenguajes tienen el poder sintáctico y semántico para orientar los objetos de manera bastante efectiva, incluso cuando no está integrado. Y eso es más que un argumento de Turing.

OOP no aborda las limitaciones de otros lenguajes, pero admite o impone metodologías de programación que ayudan a escribir un mejor programa, ayudando así a los usuarios menos experimentados a seguir las buenas prácticas que los programadores más avanzados han estado utilizando y desarrollando sin ese soporte.

Creo que un buen libro para entender todo esto podría ser Abelson & Sussman: estructura e interpretación de programas de computadora .

babou
fuente
8

Un poco de historia está en orden, creo.

La era desde mediados de los años sesenta hasta mediados de los setenta se conoce hoy como la "crisis del software". No puedo decirlo mejor que Dijkstra en su conferencia de premiación de Turing de 1972:

¡La principal causa de la crisis del software es que las máquinas se han vuelto más poderosas en varios órdenes de magnitud! Para decirlo sin rodeos: mientras no hubiera máquinas, la programación no era un problema en absoluto; cuando teníamos algunas computadoras débiles, la programación se convirtió en un problema leve, y ahora tenemos computadoras gigantes, la programación se ha convertido en un problema igualmente gigantesco.

Esta fue la época de las primeras computadoras de 32 bits, los primeros multiprocesadores verdaderos y las primeras computadoras integradas, y fue claro para los investigadores que iban a ser importantes para la programación en el futuro. Fue un momento en la historia cuando la demanda del cliente superó la capacidad del programador por primera vez.

Como era de esperar, fue un momento notablemente fértil en la investigación de programación. Antes de mediados de la década de 1960, teníamos LISP y AP / L, pero los idiomas "principales" eran fundamentalmente de procedimiento: FORTRAN, ALGOL, COBOL, PL / I, etc. Desde mediados de la década de 1960 hasta mediados de la década de 1970, obtuvimos Logo, Pascal, C, Forth, Smalltalk, Prolog, ML y Modula, y eso sin contar DSL como SQL y sus predecesores.

También fue un momento en la historia cuando se desarrollaron muchas de las técnicas clave para implementar lenguajes de programación. En este período, obtuvimos el análisis LR, el análisis del flujo de datos, la eliminación de subexpresiones comunes y el primer reconocimiento de que ciertos problemas del compilador (por ejemplo, la asignación de registros) eran NP-hard, e intentamos abordarlos como tales.

Este es el contexto en el que surgió la POO. Entonces, a principios de la década de 1970, su respuesta a su pregunta sobre qué problemas resuelve OOP en la práctica, la primera respuesta es que parecía resolver muchos de los problemas (tanto contemporáneos como anticipados) que enfrentaban los programadores en ese período de la historia. Sin embargo, este no es el momento en que OO se convirtió en la corriente principal. Llegaremos a eso pronto.

Cuando Alan Kay acuñó el término "orientado a objetos", la imagen que tenía en mente era que los sistemas de software se estructurarían como un sistema biológico. Tendría algo como células individuales ("objetos") que interactúan entre sí enviando algo análogo a las señales químicas ("mensajes"). No podías (o, al menos, no) mirar dentro de una celda; solo interactuarías con él a través de las rutas de señalización. Además, podría tener más de uno de cada tipo de célula si fuera necesario.

Puede ver que hay algunos temas importantes aquí: el concepto de un protocolo de señalización bien definido (en terminología moderna, una interfaz), el concepto de ocultar la implementación desde el exterior (en terminología moderna, privacidad) y el concepto de tener múltiples "cosas" del mismo tipo dando vueltas al mismo tiempo (en terminología moderna, instanciación).

Falta algo que puede notar, y es la herencia, y hay una razón para esto.

La programación orientada a objetos es una noción abstracta, y la noción abstracta se puede implementar de diferentes maneras en diferentes lenguajes de programación. El concepto abstracto de un "método", por ejemplo, podría implementarse en C usando punteros de función, y en C ++ usando funciones miembro, y en Smalltalk usando métodos (lo cual no debería sorprender, ya que Smalltalk implementa el concepto abstracto casi directamente). Esto es lo que las personas quieren decir cuando señalan (con bastante razón) que puedes "hacer" POO en (casi) cualquier idioma.

La herencia, por otro lado, es una característica concreta del lenguaje de programación. La herencia puede ser útil para implementar sistemas OOP. O al menos, este fue el caso hasta principios de la década de 1990.

La época de mediados de los ochenta a mediados de los noventa también fue un momento en la historia en que las cosas estaban cambiando. Durante este tiempo, tuvimos el auge de la computadora barata y ubicua de 32 bits, por lo que las empresas y muchos hogares podían permitirse el lujo de poner una computadora que era casi tan poderosa como las computadoras centrales más bajas del día en cada escritorio. También fue el apogeo de Esto también fue la era del surgimiento de la GUI moderna y el sistema operativo en red.

Fue en este contexto que surgió el Análisis y Diseño Orientado a Objetos.

La influencia de OOAD, el trabajo de los "tres Amigos" (Booch, Rumbar y Jacobson) y otros (por ejemplo, el método Shlaer-Mellor, el diseño basado en la responsabilidad, etc.) no puede ser subestimado. Es la razón por la cual la mayoría de los nuevos lenguajes que se han desarrollado desde principios de la década de 1990 (al menos, la mayoría de los que ha oído hablar) tienen objetos de estilo Simula.

Entonces, la respuesta a su pregunta de la década de 1990 es que es compatible con la mejor solución (en ese momento) para el análisis orientado al dominio y la metodología de diseño.

Desde entonces, debido a que teníamos un martillo, aplicamos OOP a casi todos los problemas que surgieron desde entonces. OOAD y el modelo de objetos que utilizaba fomentaron y permitieron el desarrollo ágil y basado en pruebas, el clúster y otros sistemas distribuidos, etc.

Las GUI modernas y los sistemas operativos que se diseñaron en los últimos 20 años tienden a proporcionar sus servicios como objetos, por lo que cualquier nuevo lenguaje de programación práctico necesita, como mínimo, una forma de unirse a los sistemas que usamos hoy en día.

Entonces, la respuesta moderna es: resuelve el problema de la interfaz con el mundo moderno. El mundo moderno se basa en OOP por la misma razón que el mundo de 1880 se construyó a vapor: lo entendemos, podemos controlarlo y hace el trabajo lo suficientemente bien.

Eso no quiere decir que la investigación se detenga aquí, por supuesto, pero sí indica que cualquier nueva tecnología necesitará OO como un caso limitante. No tiene que ser OO, pero no puede ser fundamentalmente incompatible con él.

Seudónimo
fuente
Una cosa aparte que no quería poner en el ensayo principal es que las GUI de WIMP y la POO parecen ser un ajuste extremadamente natural. Se pueden decir muchas cosas malas de las jerarquías de herencia profundas, pero esta es una situación (probablemente la ÚNICA situación) en la que parece tener algún tipo de sentido.
Seudónimo
1
OOP apareció primero en Simula-67 (simulación), en la organización interna de los sistemas operativos (la idea de "clase de dispositivo" en Unix es esencialmente una clase de la que heredan los controladores). "Sobre los criterios que se utilizarán en los sistemas de descomposición en módulos" de Parnas , CACM 15:12 (1972), págs. 1052-1058, el lenguaje de Wirth Modula de los años setenta, los "tipos de datos abstractos" son todos precursores de una manera o otro.
vonbrand
Todo eso es cierto, pero mantengo que OOP no fue visto como una "solución a un problema de programación de procedimientos" hasta mediados de los años 70. Definir "OOP" es notoriamente difícil. El uso original del término de Alan Kay no está de acuerdo con el modelo de Simula, y desafortunadamente el mundo se ha estandarizado en el modelo de Simula. Algunos modelos de objetos tienen una interpretación similar a Curry-Howard, pero la de Simula no. Stepanov probablemente tenía razón cuando notó que la herencia no es sólida.
Seudónimo
6

Ninguno, de verdad. OOP realmente no resuelve un problema, estrictamente hablando; no hay nada que pueda hacer con un sistema orientado a objetos que no pueda hacer con un sistema no orientado a objetos; de hecho, no hay nada que pueda hacer con eso que no pueda hacerse con una máquina Turing. Con el tiempo, todo se convierte en código de máquina, y ASM ciertamente no está orientado a objetos.

Lo que el paradigma OOP hace por usted es que facilita la organización de variables y funciones, y le permite moverlas juntas más fácilmente.

Digamos que quiero escribir un juego de cartas en Python. ¿Cómo representaría las cartas?

Si no supiera sobre OOP, podría hacerlo de esta manera:

cards=["1S","2S","3S","4S","5S","6S","7S","8S","9S","10S","JS","QS","KS","1H","2H",...,"10C","JC","QC","KC"]

Probablemente escribiría un código para generar esas tarjetas en lugar de simplemente escribirlas a mano, pero entiendes el punto. "1S" representa el 1 de Picas, "JD" representa el Jack de Diamantes, y así sucesivamente. También necesitaría un código para el Joker, pero solo fingiremos que no hay Joker por ahora.

Ahora, cuando quiero barajar el mazo, solo necesito "barajar" la lista. Luego, para sacar una carta de la parte superior del mazo, saco la entrada superior de la lista, dándome la cadena. Sencillo.

Ahora, si quiero averiguar con qué tarjeta estoy trabajando para mostrarla al jugador, necesitaría una función como esta:

def card_code_to_name(code):
    suit=code[1]

    if suit=="S":
        suit="Spades"
    elif suit=="H"
        suit="Hearts"
    elif suit=="D"
        suit="Diamonds"
    elif suit=="C"
        suit="Clubs"

    value=code[0]

    if value=="J":
        value="Jack"
    elif value="Q":
        value="Queen"
    elif value="K"
        value="King"

    return value+" of "+suit

Un poco grande, largo e ineficiente, pero funciona (y es muy poco fónico, pero eso no viene al caso aquí).

¿Qué sucede si quiero que las cartas puedan moverse por la pantalla? Tengo que almacenar su posición de alguna manera. Podría agregarlo al final del código de su tarjeta, pero eso podría ser un poco difícil de manejar. En cambio, hagamos otra lista de dónde está cada tarjeta:

cardpositions=( (1,1), (2,1), (3,1) ...)

Luego escribo mi código para que el índice de la posición de cada carta en la lista sea el mismo que el índice de la carta en el mazo.

O al menos debería serlo. A menos que cometa un error. Lo cual podría muy bien, porque mi código tendrá que ser bastante complejo para manejar esta configuración. Cuando quiero barajar las cartas, tengo que barajar las posiciones en el mismo orden. ¿Qué sucede si saco una carta del mazo por completo? También tendré que tomar su posición y ponerla en otro lugar.

¿Y si quiero almacenar aún más información sobre las tarjetas? ¿Qué sucede si quiero almacenar si se voltea cada tarjeta? ¿Qué sucede si quiero un motor de física de algún tipo y necesito saber también la velocidad de las cartas? ¡Necesitaré otra lista por completo para almacenar los gráficos de cada tarjeta! ¡Y para todos estos puntos de datos, necesitaré un código separado para mantenerlos todos organizados correctamente para que cada tarjeta se asigne de alguna manera a todos sus datos!

Ahora intentemos esto de la manera OOP.

En lugar de una lista de códigos, definamos una clase de Tarjeta y construyamos una lista de objetos de Tarjeta a partir de ella.

class Card:

    def __init__(self,value,suit,pos,sprite,flipped=False):
        self.value=value
        self.suit=suit
        self.pos=pos
        self.sprite=sprite
        self.flipped=flipped

    def __str__(self):
        return self.value+" of "+self.suit

    def flip(self):
        if self.flipped:
            self.flipped=False
            self.sprite=load_card_sprite(value, suit)
        else:
            self.flipped=True
            self.sprite=load_card_back_sprite()

deck=[]
for suit in ("Spades","Hearts","Diamonds","Clubs"):
    for value in ("1","2","3","4","5","6","7","8","9","10","Jack","Queen","King"):
        sprite=load_card_sprite(value, suit)
        thecard=Card(value,suit,(0,0),sprite)
        deck.append(thecard)

Ahora, de repente, todo es mucho más simple. Si quiero mover la carta, no tengo que averiguar dónde está en el mazo, luego usar eso para sacar su posición del conjunto de posiciones. Solo tengo que decirlo thecard.pos=newpos. Cuando saco la tarjeta de la lista principal del mazo, no tengo que hacer una nueva lista para almacenar todos esos otros datos; cuando el objeto de la carta se mueve, todas sus propiedades se mueven con él. Y si quiero una tarjeta que se comporte de manera diferente cuando se voltea, no tengo que modificar la función de volteo en mi código principal para que detecte estas tarjetas y realice ese comportamiento diferente; Solo tengo que subclasificar Card y modificar la función flip () en la subclase.

Pero nada de lo que hice allí no podría haberse hecho sin OO. Es solo que con un lenguaje orientado a objetos, el lenguaje está haciendo mucho trabajo para mantener las cosas juntas para usted, lo que significa que tiene muchas menos posibilidades de cometer errores, y su código es más corto y más fácil de leer y escribir.

O, para resumir aún más, OO le permite escribir programas más simples que parecen hacer el mismo trabajo que los programas más complejos al ocultar muchas de las complejidades comunes de manejar datos detrás de un velo de abstracción.

Schilcote
fuente
1
Si lo único que le quita a OOP es la "administración de memoria", entonces no creo que lo haya entendido muy bien. ¡Existe toda una filosofía de diseño y una generosa botella de "correcto por diseño"! Además, la gestión de la memoria no es nada inherente a la orientación de los objetos (C ++?), Aunque la necesidad de hacerlo sea más pronunciada.
Raphael
Claro, pero esa es la versión de una oración. Y también utilicé el término de una manera bastante no estándar. Quizás sería mejor decir "manejo de información" que "gestión de memoria".
Schilcote
¿Hay algún lenguaje que no sea OOP que permita que una función tome un puntero a algo, así como un puntero a una función cuyo primer parámetro es un puntero a ese mismo tipo de cosas, y haga que el compilador valide que la función es apropiada? para el puntero pasado?
supercat
3

Habiendo escrito C incrustado durante algunos años administrando cosas como dispositivos, puertos seriales y paquetes de comunicaciones entre los puertos seriales, puertos de red y servidores; Me encontré a mí mismo, un ingeniero eléctrico capacitado con experiencia limitada en programación de procedimientos, inventando mis propias abstracciones del hardware que terminó manifestándose en lo que más tarde me di cuenta de lo que la gente normal llama 'Programación Orientada a Objetos'.

Cuando me mudé al lado del servidor, estuve expuesto a una fábrica que configuró la representación de objetos de cada dispositivo en la memoria en la instanciación. No entendí las palabras o lo que estaba sucediendo al principio; solo sabía que fui al archivo llamado así y tal y escribí el código. Más tarde, me encontré, nuevamente, finalmente reconociendo el valor de la POO.

Personalmente, creo que esta es la única forma de enseñar la orientación a objetos. Tuve una clase de Introducción a OOP (Java) en mi primer año y fue completamente sobre mi cabeza. Las descripciones de OOP basadas en la clasificación de gatito-> gato-> mamífero-> vivo-> cosa u hoja-> rama-> árbol-> jardín son, en mi humilde opinión, metodologías absolutamente ridículas porque nunca nadie va a tratar de resolver esas problemas, si pudieras llamarlos problemas ...

Creo que es más fácil responder a su pregunta si la mira en términos menos absolutos, no "qué resuelve", sino más bien desde una perspectiva de "aquí hay un problema, y ​​así es como lo hace más fácil". En mi caso particular de puertos seriales, tuvimos un montón de #ifdefs en tiempo de compilación que agregaron y eliminaron código que abrió y cerró estáticamente los puertos seriales. Las funciones de puerto abierto se llamaron por todas partes, y se podían ubicar en cualquier lugar de las 100k líneas de código del sistema operativo que teníamos, y el IDE no atenuó lo que no estaba definido: tenía que rastrearlo manualmente, y llévalo en tu cabeza. Inevitablemente podrías tener varias tareas tratando de abrir un puerto serie dado esperando su dispositivo en el otro extremo, y luego ninguno de los códigos que acaba de escribir funciona, y no puede entender por qué.

La abstracción fue, aunque todavía en C, una 'clase' de puerto serie (bueno, solo un tipo de datos de estructura) que teníamos una matriz de uno para cada puerto serie y en lugar de tener [el equivalente DMA en puertos serie] Funciones "OpenSerialPortA" "SetBaudRate" etc. llamadas directamente en el hardware desde la tarea, llamamos a una función auxiliar a la que le pasó todos los parámetros de comunicación (baudios, paridad, etc.), que primero verificó la matriz de estructura para ver si eso el puerto ya se había abierto, si es así, por qué tarea, que le diría como una impresión de depuración, para que pudiera saltar inmediatamente a la sección de código que necesitaba deshabilitar, y si no, procedió a configurar todos los parámetros a través de sus funciones de ensamblaje HAL, y finalmente abrieron el puerto.

Por supuesto, también hay peligros para la POO. Cuando finalmente limpié esa base de código e hice que todo estuviera limpio y ordenado, escribir nuevos controladores para esa línea de productos finalmente fue una ciencia calculable y predecible, mi gerente eliminó el producto específicamente porque era un proyecto menos que necesitaría para administrar, y él estaba en el límite de la administración intermedia extraíble. Lo que me quitó mucho / Me pareció muy desalentador, así que dejé mi trabajo. Jajaja

Aumentar
fuente
1
¡Hola! Esto parece más una historia personal que una respuesta a la pregunta. A partir de su ejemplo, veo que reescribió un código horrible en un estilo orientado a objetos, lo que lo hizo mejor. Pero no está claro si la mejora tuvo mucho que ver con la orientación a objetos o si fue solo porque usted era un programador más competente para entonces. Por ejemplo, una buena parte de su problema parece haber sido el código que se esparció por todas partes sobre el lugar. Eso podría haberse resuelto escribiendo una biblioteca de procedimientos, sin ningún objeto.
David Richerby
2
@DavidRicherby teníamos la biblioteca de procedimientos, pero eso es lo que desaprobamos, no se trataba solo de que el código estuviera por todas partes. El punto fue que hicimos esto al revés. Nadie intentaba OOP nada, simplemente sucedió naturalmente.
paIncrease
@DavidRicherby, ¿puede dar ejemplos de implementaciones de bibliotecas de procedimientos para asegurarme de que estamos hablando de lo mismo?
paIncrease
2
Gracias por tu respuesta y +1. Hace mucho tiempo, otro programador con experiencia compartió cómo POO hizo su proyecto más fiable forums.devshed.com/programming-42/... programación orientada a objetos supongo que fue diseñado de manera muy inteligente por parte de algunos profesionales que podrían haber enfrentado algunos problemas en enfoque de procedimiento.
user31782
2

Hay muchos reclamos e intenciones sobre qué / dónde la programación de OOP tiene una ventaja sobre la programación de procedimientos, incluso por parte de sus inventores y usuarios. pero simplemente porque una tecnología fue diseñada para ciertos propósitos por sus diseñadores no garantiza que tendrá éxito en esos objetivos. Esta es una comprensión clave en el campo de la ingeniería de software que data del famoso ensayo de Brooks "No Silver Bullet", que sigue siendo relevante a pesar de la revolución de la codificación OOP. (Ver también el Ciclo Gartner Hype para nuevas tecnologías).

muchos de los que han usado ambos también tienen opiniones de la experiencia anecdótica, y esto tiene cierto valor, pero se sabe en muchos estudios científicos que el análisis autoinformado puede ser inexacto. parece haber poco análisis cuantitativo de estas diferencias o, si lo hay, no se cita tanto. Es algo sorprendente cuántos científicos informáticos hablan con autoridad sobre ciertos temas centrales de su campo, pero en realidad no citan la investigación científica para respaldar sus puntos de vista y no se dan cuenta de que realmente están transmitiendo la sabiduría convencional en su campo (aunque generalizada ).

Dado que este es un sitio / foro de ciencias , aquí hay un breve intento de poner las numerosas opiniones en una posición más firme y cuantificar la diferencia real. Puede haber otros estudios y espero que otros puedan señalarlos si se enteran de alguno.(pregunta zen: si realmente hay una diferencia importante, y se ha aplicado / invertido tanto esfuerzo masivo en el campo de la ingeniería de software comercial y en otros lugares para lograrlo, ¿por qué debería ser tan difícil obtener evidencia científica de esto? alguna referencia clásica y altamente citada en el campo que cuantifica definitivamente la diferencia?)

Este documento emplea análisis experimentales / cuantitativos / científicos y apoya específicamente que la comprensión por parte de programadores novatos se mejora con los métodos de codificación OOP en algunos casos, pero no fue concluyente en otros casos (en relación con los tamaños de los programas). tenga en cuenta que esta es solo una de las muchas / principales afirmaciones sobre la superioridad de OOP que se está promoviendo en otras respuestas y por los defensores de OOP. el estudio posiblemente midió un elemento psicológico conocido como "carga cognitiva / sobrecarga" de comprensión de codificación de wrt.

  • Una comparación de la comprensión de los programas orientados a objetos y de procedimientos por parte de programadores novatos que interactúan con las computadoras. Susan Wiedenbeck, Vennila Ramalingam, Suseela Sarasamma, Cynthia L Corritore (1999)

    Este artículo informa sobre dos experimentos que comparan las representaciones mentales y la comprensión del programa por parte de los principiantes en los estilos orientado a objetos y de procedimiento. Los sujetos eran programadores novatos inscritos en un segundo curso de programación que enseñaba el paradigma orientado a objetos o el de procedimiento. El primer experimento comparó las representaciones mentales y la comprensión de programas cortos escritos en los estilos de procedimientos y orientados a objetos. El segundo experimento extendió el estudio a un programa más grande que incorpora características de lenguaje más avanzadas. Para los programas cortos no hubo diferencias significativas entre los dos grupos con respecto al número total de preguntas respondidas correctamente, pero los sujetos orientados a objetos fueron superiores a los sujetos de procedimiento al responder preguntas sobre la función del programa. Esto sugiere que la información de la función estaba más fácilmente disponible en sus representaciones mentales de los programas y respalda el argumento de que la notación orientada a objetos resalta la función a nivel de la clase individual. Para el programa largo no se encontró un efecto correspondiente. La comprensión de los sujetos procesales fue superior a los sujetos orientados a objetos en todo tipo de preguntas. Las dificultades experimentadas por los sujetos orientados a objetos para responder preguntas en un programa más amplio sugieren que enfrentaron problemas para reunir información y sacar inferencias de ella. Sugerimos que este resultado puede estar relacionado con una curva de aprendizaje más larga para los principiantes del estilo orientado a objetos, así como con las características del estilo OO y la notación de lenguaje OO particular.

ver también:

vzn
fuente
1
Respeto los estudios experimentales. Sin embargo, existe la cuestión de determinar que aborden las preguntas correctas. Hay demasiadas variables en lo que se puede llamar OOP, y en formas de usarlo, para que un solo estudio sea significativo, en mi humilde opinión. Como muchas cosas en la programación, OOP fue creado por expertos para satisfacer sus propias necesidades . Cuando se discute la utilidad de OOP (que no tomé como tema del OP, que es más bien si trata una deficiencia de la programación de procedimientos), uno puede preguntarse: ¿qué característica, para quién, con qué propósito? Entonces solo los estudios de campo se vuelven completamente significativos.
babou
1
Advertencia de anécdota: si un problema es pequeño (por ejemplo, hasta alrededor de 500-1000 líneas de código), OOP no hace ninguna diferencia en mi experiencia, incluso podría interferir al tener que preocuparse por cosas que hacen muy poca diferencia. Si el problema es grande, y tiene alguna forma de "piezas intercambiables" que, además, debe ser posible agregar más tarde (ventanas en una GUI, dispositivos en un sistema operativo, ...) la organización que proporciona la disciplina OOP es inevitable. Ciertamente puede programar OOP sin soporte de idiomas (ver, por ejemplo, el kernel de Linux).
vonbrand
1

Ten cuidado. Lea el clásico de R. King "My Cat is Object-Oriented" en "Conceptos orientados a objetos, bases de datos y aplicaciones" (Kim y Lochovsky, eds) (ACM, 1989). "Orientado a objetos" se ha convertido más en una palabra de moda que en un concepto claro.

Además, hay muchas variaciones sobre el tema, con poco en común. Hay lenguajes basados ​​en prototipos (la herencia es de objetos, no hay clases como tales) y lenguajes basados ​​en clases. Hay idiomas que permiten la herencia múltiple, otros que no. Algunos lenguajes tienen una idea como las interfaces de Java (se puede tomar como una forma de herencia múltiple diluida). Existe la idea de los mixins. La herencia puede ser bastante estricta (como en C ++, realmente no puede cambiar lo que obtienes en una subclase), o manejarse de manera muy liberal (en Perl la subclase puede redefinir casi todo). Algunos lenguajes tienen una única raíz para la herencia (típicamente llamada Objeto, con comportamiento predeterminado), otros permiten que el programador cree múltiples árboles. Algunos idiomas insisten en que "todo es un objeto", otros manejan objetos y no objetos, algunos (como Java) tienen "la mayoría son objetos, pero estos pocos tipos aquí no lo son". Algunos lenguajes insisten en la encapsulación estricta del estado en los objetos, otros lo hacen opcional (C ++ 'privado, protegido, público), otros no tienen encapsulación en absoluto. Si entrecierra los ojos en un lenguaje como Scheme desde el ángulo correcto, verá OOP integrado sin ningún esfuerzo especial (puede definir funciones que devuelven funciones que encapsulan algún estado local).

vonbrand
fuente
0

Para ser conciso, la programación orientada a objetos aborda los problemas de seguridad de datos presentes en la programación de procedimientos. Esto se hace utilizando el concepto de encapsular los datos, permitiendo que solo las clases legítimas hereden los datos. Los modificadores de acceso facilitan el logro de este objetivo. Espero que ayude.:)

manu
fuente
¿Cuáles son los problemas de seguridad de datos presentes en la programación de procedimientos?
user31782
En la programación de procedimientos no se puede restringir el uso de una variable global. Cualquier función podría usar su valor. Sin embargo, en OOP podría restringir el uso de una variable solo a una determinada clase o solo a las clases que la heredan.
manu
En la programación de procedimientos también podemos restringir el uso de la variable global al usar la variable para ciertas funciones, es decir, al no declarar ningún dato global.
usuario31782
Si no lo declara globalmente, no es una variable global.
manu
1
"Seguro" o "Correcto" no significan nada sin una especificación. Estas cosas son intentos de poner una especificación en el código para ese objetivo: Tipos, Definiciones de clase, DesignByContract, etc. Obtiene "Seguridad" en el sentido de que puede hacer inviolables los límites de los datos privados; suponiendo que debe obedecer el conjunto de instrucciones de la máquina virtual para ejecutar. La orientación a objetos no ocultará la memoria interna de alguien que pueda leer la memoria directamente, y un diseño de protocolo de objeto incorrecto está entregando secretos por diseño.
Rob