Objetivos
Material requerido.
Un café cargadito siempre ayuda |
Los lenguajes de programación.
A lo largo de la historia de los computadores, los lenguajes de programación han ido evolucionando para resolver los problemas a medida que iban apareciendo y el objetivo a los que se dedicaban.
Así, empezamos programando en código maquina (Una locura creedme, yo lo sufrí con los primeros microprocesadores) y después se desarrolló el concepto de ensamblador que separaba el código fuente y el código máquina o ejecutable.
Luego fueron surgiendo lenguajes de más alto nivel como el Fortran (FORmula TRANslator) para efectuar cálculos matemáticos, o el COBOL (Common Buissnes Oriented Lenguaje) para gestionar las necesidades de contabilidad y financieras de las empresas.
BASIC (Begginers All Purpose Symbolic Code) se populariza con las primeras máquinas de Apple y se desarrolló pensando en un sistema sencillo para iniciar a los no informáticas en la programación.
Enseguida aparecieron entre otros Pascal y C y cuando yo empecé a trabajar y a programar todas las universidades y cátedros en España estaban empeñadas en Pascal en contra de C porque te impedía equivocarte, decían, mientras que C te deja equivocarte con punteros y demás y eso era muy peligroso y había que evitarlo.
Ni que decir tiene que a los que programábamos nos gustaba C, que no nos consideraba idiotas y nos dejaba hacer lo que queríamos. Que iba a ganar la guerra a Pascal era evidente para cualquiera que no fuera cátedro y a la vista está el resultado.
En esa época la letanía era: Programación estructurada, las cosas claras, muchas funciones pequeñas y fáciles de seguir y variables globales cuando fueran necesarias. Los programas no solían ser demasiado grandes y los proyectos generalmente involucraba uno o dos programadores como mucho.
Pero cuando los PCs empezaron a florecer en todos los escritorios, el mercado del Soft pasó de ser una cosa de freakys a ser un negocio de muchos ceros, y se empezaron a plantear proyectos mastodónticos en una escala como no se había conocido antes.
Los sistemas operativos como Windows, Mac OSX, crecían sin límite y en lo que a los Unix se refiere, que yo recuerde, Microsoft tenia uno, HP tenia HP_UX, IBM con AIX, SUN el suyo Solaris.
Los programas como Office y sus competencias, Las Bases de datos como Oracle, los sistemas operativos de red que empezaban y demás hicieron que el tamaño de los programas se empezaran a medir en años y décadas hombre y ya no eran cosa de un par de freakys sino de un negocio y había que gestionar cientos de programadores durante años en proyectos inabarcables que nuca se acababan y cuyo precio y plazo se iban estirando hasta el infinito.
Pero ya antes de esto las costuras de la programación estructurada empezaban a romperse y demostraban la incapacidad o al menos la dificultad extrema de gestionar con acierto un proyecto más allá de unos pocos programadores involucrados.
Por eso había que buscar un nuevo paradigma de programación que permitiese gestionar mejor este tipo de macro proyectos. La solución fue la programación orientada a objetos u OOP por sus siglas en ingles.
Y por eso, antes de empezar vale la pena mencionar cuales son estos problemas que necesitamos resolver.
Problemas de la programación estructurada
Cuando hablamos del ámbito de las variables ya avanzamos algunos de esos problemas. En C, las variables son locales por definición, lo que evita que diferentes partes del programa mezclen las variables.
Si todas las variables fueran globales, un error en la comprensión de una o un error al asignar a una variable un nombre tipo como contador que ya se usa en otro sitio provoca errores muy difíciles de detectar y por eso en C las variables son en principio locales.
Pero claro, para que no sea fácil, a veces es preciso usar variables globales a más de una función. Mientras solo un para de amigos lo usan, la cosa puede funcionar, pero cuando hay 120 programadores trabajando en distintas secciones del programa (Y en distintos países , con diferentes idiomas) que deben comunicarse entre sí, la cosa se vuelve peliaguda.
La probabilidad de que un porcentaje alto de ellos sean unos cretinos que usan alegremente las variables globales es 1, y eso lleva a desesperados y frecuentes aullidos. Hay soluciones más o menos parciales como los name spaces (De los que ya hablaremos) pero el problema persiste.
Además el software se vende y no es raro encontrar gente que vende librerías para resolver problemas concretos, pero naturalmente no se les entrega el fuente sino solo las librerías dispuestas, y a veces queremos hacer cosas con las librerías que no están todo lo bien documentadas que deberían y las librerías no suelen llevarse demasiado bien con variables globales y demás zarandajas.
El problema habitual de la programación estructurada, que las funciones tienen acceso ilimitado a los datos globales del programa, de modo que cualquier cretino puede corromper los datos de los que depende nuestro programa sin que nos enteremos, empeora por el hecho de que el modelo separa conceptualmente datos y funciones como si fueran cosas independientes.
Pero nuestro cerebro relaciona ambas cosas por analogía, y separarlas solo sirve para que nos resulte más complicado entender sus relaciones y dependencias a medida que el conjunto crece y vamos perdiendo perspectiva.
Otro problema viene de que la experiencia muestra que los programadores tienden a resolver los mismos problemas una y otra vez y por eso la reutilización de un código previo es clave para mantener los plazos y los precios bajos. Pero la programación estructurada no es demasiado propensa a reusar el código con facilidad más que a base de librerías de funciones nunca suficientemente documentadas y propensas a uso incorrecto.
Conseguir un procedimiento que facilite la reutilización de programas es clave en la productividad y algo que influye fuertemente en la calidad coste y plazo de los proyectos.
Necesitamos por tanto una manera de blindar nuestros programas contra cretinos (O novatos, que todos hemos empezado en algún momento), pero permitiéndoles que los utilicen al máximo de su potencial sin tener que salir corriendo cada poco a ver que la han montado.
La OOP se diseña con todos estos problemas en mente y algunos otros, (de los que aún es pronto para hablar) y por eso poneros cómodos porque vamos a entrar en materia.
La programación Orientada a objetos OOP
El concepto de OOP no es exclusivo de C++, sino que responde a un paradigma conceptual diferente de la programación estructurada y que se puede encontrar en cualquier lenguaje moderno que se precie como Python, Java o cualquiera de los que soporta Visual Studio de Microsoft.
C++ se diseña como una extensión de C, para que, manteniendo su esencia (El lenguaje de mayor éxito de la historia), incorpore los mecanismos precisos para implementar los conceptos de OOP, pero manteniendo la total compatibilidad con el lenguaje C anterior.
La OOP es un marco conceptual (No un lenguaje) en el que modelar el problema a resolver mediante programación, en base a objetos abstractos que se asemejan a los objetos reales lo más posible.
Estos objetos interactúan entre sí mediante procedimientos definidos (Métodos) y van evolucionando su información interna (Propiedades) en función de su historia.
En cierto modo esto se parece mucho a cómo funciona digamos una empresa. Cada uno de los departamentos internos como ventas, administración, personal o producción, son objetos abstractos a los que el director solicita información (Datos) o procesos (Sacar las nóminas) sin que este conozca muy bien cuáles son los procedimientos internos a realizar (Ni le importan)
El director tiene una imagen de la responsabilidad de cada sección (Objeto) y de los procedimientos que cada objeto puede realizar, o de la información que le puede conseguir, y por eso el reporte de ventas por producto, sabe que corresponde a Ventas y no a nóminas, pero no se le ocurre pedir los datos concretos, sino solo el resumen procesado de acuerdo a ciertos criterios establecidos.
Además vale la pena destacar, que cada departamento de la empresa dispone de sus propias datos internos privados, que utiliza para cumplir sus procedimientos, es decir, que al igual que la OOP mezcla datos y procedimientos en el modelo de trabajo.
La OOP tiene tres características básicas que se suelen presentar como Encapsulación, Polimorfismo y Herencia. Vamos con cada una de ellas.
Centrando el tema
La encapsulación es un procedimiento por el cual, los datos y las funciones se encierran en un contenedor llamado objeto, que usamos para aislar ambos elementos de la manipulación exterior y forzar a que esta se haga de un modo controlado y validado por nosotros mismos.
El mecanismo que C++ utiliza para encapsular estos objetos se llaman clases y ya hemos tenido ejemplo de varios casos, aun sin entrar en muchos detalles.
Un objeto C++ encapsula datos llamados variables miembros, miembros, o propiedades (según el autor) del mismo modo que lo hace una estructura, y además encapsula las funciones que manipulan estos datos que se llaman funciones miembros o métodos indistintamente.
Estas propiedades y métodos pueden definirse como públicas o privadas, lo que permite limitar la capacidad de algún memo externo de manipular incorrectamente datos sensibles.
Cuando creamos un objeto, se crea una caja negra con acceso a todas las variables y métodos internos, pero de forma que desde el exterior solo se puede acceder a los miembros públicos del objeto, mientras se mantienen ocultas las funciones y variables definidas como privadas.
La encapsulación garantiza que desde el exterior solo se verá lo que queramos, independientemente de que desde el interior tengamos acceso a todo.
Desde el punto de vista de la OOP los objetos son la representación conceptual del mundo exterior, las variables miembros o propiedades representan las características de estos objetos, y los métodos o funciones miembros definen la forma de tratar con ese objeto, lo que se puede y no se puede hacer.
Por eso, el modelo de un programa OOP, se acerca conceptualmente mucho más a nuestro modelo mental de los objetos reales, porque estos tienen tanto atributos (Propiedades) como comportamiento (Métodos) y facilita la comprensión y modelización del problema.
La idea fundamental de la OOP es combinar en una única entidad (Los objetos), tanto los datos como las funciones que manipulan estos datos.
Si hacemos privados todos los datos internos pero definimos procedimientos públicos para manejarlos, garantizamos de un plumazo la integridad estructural de los datos, porque no pueden ser modificados fuera de los procedimientos previstos para ello. Es el sueño de cualquier funcionario.
El concepto de polimorfismo está muy extendido en nuestra sociedad tecnológica aunque no nos solemos dar cuenta (Es un nombre raro, para un concepto muy sencillo)
Todos los chismes eléctricos / electrónicos tienen un botón de ON/OFF y nos parece lo normal, pero el procedimiento físico que enciende un ordenador no se parece en nada al que enciende un motor eléctrico o uno de explosión. Sin embargo, todos ellos comparten un concepto abstracto genérico de encendido apagado independientemente del procedimiento específico en cada caso.
Este es la base del concepto de polimorfismo. Por ejemplo en C++ podemos sumar números enteros o números float con el mismo símbolo “+”, y nos parece tan normal, pero en realidad los procedimientos que se aplican son completamente distintos.
De hecho podemos sumar dos Strings con el + impunemente, pero realiza una operación muy distinta: Una concatenación. Nuestro cerebro digiere bien esto porque de algún modo encontramos que son cosas similares y no le dedicamos un segundo pensamiento.
El polimorfismo de C++ es la capacidad de redefinir los operadores (Y otras cosas de las que hablaremos) para que modifiquen su comportamiento en función del tipo de los operandos, y es algo que nos facilita mucho la comprensión del programa usando un mecanismo sencillo.
Se suele definir el polimorfismo como: Un interface único, múltiples métodos. Y es algo que reduce la complejidad de los programas de una forma notable (A poco listo que sea el que define el polimorfismo, claro)
A usar las funciones u operadores de diferentes maneras dependiendo del tipo de los datos, le llamamos polimorfismo y cuando redefinimos un operador o función para comportarse de forma diferente con esos datos, decimos que el operador esta sobrecargado (Overloaded) y por eso hablaremos de Operator overloading.
La herencia es un proceso por el cual un objeto hereda las propiedades y métodos de otro, sin necesidad de volverlas a definir desde el principio.
Nos permite desarrollar una clasificación jerárquica de objetos organizados que replica conceptualmente la forma en que clasificamos el mundo.
Por ejemplo para nosotros existe una clase bastante difusa que podríamos llamar animal, que incluye desde una almeja a un elefante, pasando por esponjas y calamares, y sería bastante complicado definir que tienen todos estos bichos en común.
Podemos por tanto definir un objeto perteneciente a la clase Animal, con propiedades bastante difusas, y luego podríamos derivar una clase llamada mamífero, que tendría dos ojos y 4 extremidades. De esta derivaríamos otra clase llamada Primates y de esta otra tipo Hombre.
La virtud de la herencia es que podemos ahorrarnos la definición de las cosas que ya están definidas en las clases de las que descienden (Que me perdone Darwin)
La herencia y los objetos guardan una relación importante con otro concepto clave que es la reusabilidad del código.
Una vez que un objeto con sus propiedades y métodos está listo, reutilizarlo en otro programa es tan sencillo, como hacer un include y empezar a tirar de sus métodos (Algo que ya hacéis rutinariamente) con la garantía de que su mal uso no va a corromper de ningún modo el programa o sus datos, ya que están encapsulados.
La herencia tiene una implicación aún mayor en la idea de reusabilidad del código, ya que si queremos modificar un objeto para añadirle características que no estaban previstas, basta con derivar otro objeto a partir del primero y añadir lo que necesitamos sin tocar el código original. No me digáis que no es limpio y elegante.
Porque pasarnos a la OOP
La pregunta del millón. Con lo cómodos que estábamos con la programación estructurada y ahora tenemos que pasarnos a la OOP, menudo rollo.
Bueno la respuesta es sí y no.
Si tus programas se mantienen a un nivel pequeño, puedes pasar directamente de la OOP ya que no te dará grandes ventajas, pero a cambio tampoco te da ningún inconveniente.
Si tus programas crecen de tamaño o vas a publicarlos, entonces la OOP te ofrece un paradigma conceptual mucho más amplio y más rico que la programación estructurada y si mañana tienes que cambiar de lenguaje, los conceptos que aprendas aquí serán transportables inmediatamente.
La OOP requiere por regla general más planificación que la programación estructurada y por eso suele ser más interesante para programadores que trabajan en modo estratégico (Con desarrollo a largo plazo) mientras que la programación estructurada suele interesar más a los que gustan de trabajar en modo táctico (Resolver sobre la marcha y luego ya veremos, o sea los que estáis leyendo esto).
Pero elegir un modo no supone renunciar al otro, puedes programar con un modo o el otro según el caso.
Fíjate que casi todas las librerías que descargamos para manejar displays, sensores o sistemas de comunicaciones, son clases y objetos, precisamente porque nos permite usarlos más fácilmente que otro tipo de sistemas, y además nos evita errores.
En realidad ya estáis acostumbrados a usar los objetos en Arduino C++ y ahora lo que nos falta es conocer cómo definir clases y métodos y no vale la pena que lo rechacéis por vagancia porque es algo que merece la pena.
Somos plenamente conscientes de la sensación pánico que la idea de volver a empezar a con el modelo de programación provoca. Y más cuando la terminología que rodea la OOP es bastante intimidante: Objetos, abstracción, modelo conceptual, polimorfismo, herencia, sobrecarga…
Pero tened en cuenta de que son solo palabras que la normativa anti paletos impone para darse importancia, pero que responden a conceptos sencillos que podéis dominar sin el menor problema… con solo un poco de esfuerzo, no tengáis duda.
En esta humilde casa hemos elegido el camino de empezar con C y ahora pasar a hablar de C++ a sabiendas de que hay quien recomienda entrar a saco con C++ y los objetos de entrada y luego ya hablaremos de lo demás.
Pero nuestra experiencia es que los aficionados a Arduino hacen pinitos con un subset del lenguaje C++ mucho antes de entrar en constructos conceptuales y por eso hemos preferido seguir el camino inverso y dejar la OOP para quienes quieran seguir avanzando.
Podéis enredar con los Arduinos toda la vida y no preocuparos de la OOP y las clases, utilizando simplemente a nivel básico C++ como hasta ahora. Pero si queréis avanzar y hacer programas complejos, publicar vuestros proyectos y mejorar en vuestra capacidad de entender la tecnología, entonces el camino que tenéis es claro: Apunta en la dirección de la OOP.
Resumen de la sesión