Programación orientada a objetos
Abdiel E. Cáceres González Centro de Investigación y de Estudios Avanzados - IPN México D.F., México. 2004
¿Porqué programación orientada a objetos?
¿Porqué programación orientada a objetos?
La POO no es tanto una técnica de codificación como una técnica de empaquetamiento del código, una forma con la que los proveedores de código pueden encapsular la funcionalidad para suministrársela a los que la consumen. La diferencia entre la programación orientada a objetos y la programación convencional es este aumento del énfasis que se hace en la relación entre consumidores y proveerores de código.
¿Porqué programación orientada a objetos?
La POO no es tanto una técnica de codificación como una técnica de empaquetamiento del código, una forma con la que los proveedores de código pueden encapsular la funcionalidad para suministrársela a los que la consumen. La diferencia entre la programación orientada a objetos y la programación convencional es este aumento del énfasis que se hace en la relación entre consumidores y proveerores de código.
Proveedor Software encapsulado
¿Porqué programación orientada a objetos?
La POO no es tanto una técnica de codificación como una técnica de empaquetamiento del código, una forma con la que los proveedores de código pueden encapsular la funcionalidad para suministrársela a los que la consumen. La diferencia entre la programación orientada a objetos y la programación convencional es este aumento del énfasis que se hace en la relación entre consumidores y proveerores de código. Programación convencional
Computadora Programas Programación convencionalProgramación convencionalProgramación convencionalProgramación convencionalProgramación convencionalProgramación convencionalProgramación convencionalProgramación convencionalProgramación convencional
Proveedor Software encapsulado
¿Porqué programación orientada a objetos?
La POO no es tanto una técnica de codificación como una técnica de empaquetamiento del código, una forma con la que los proveedores de código pueden encapsular la funcionalidad para suministrársela a los que la consumen. La diferencia entre la programación orientada a objetos y la programación convencional es este aumento del énfasis que se hace en la relación entre consumidores y proveerores de código. Programación convencional
Computadora Programas Programación convencionalProgramación convencionalProgramación convencionalProgramación convencionalProgramación convencionalProgramación convencionalProgramación convencionalProgramación convencionalProgramación convencional
Programación orientada a objetos
Proveedor Software encapsulado
Consumidor Creador de un sistema
¿Porqué programación orientada a objetos?
La integración es el proceso de conjuntar la funcionalidad de diferentes proveedores, para formar el código del consumidor.
Proveedor Proveedor
Proveedor Consumidor Proveedor
Proveedor
¿Porqué programación orientada a objetos?
La integración es el proceso de conjuntar la funcionalidad de diferentes proveedores, para formar el código del consumidor.
Proveedor Proveedor
Proveedor Consumidor Proveedor
Proveedor
¿Porqué programación orientada a objetos?
La integración es más de lo que los programadores conocen como “interconectar” (linking), y que consiste en determinar qué módulos binarios deben combinarse para producir una imagen ejecutable, en asignar una dirección de memoria a cada uno de ellos, y en resolver las referencias externas, sustituyéndolas por las direcciones de memoria correctas.
La integración es el proceso mediante el cual operadores y operandos de muchos tipos son publicados por los proveedores, y utilizados por los consumidores.
Hay varias escuelas de pensamiento acerca del momento y la forma en que se deben realizar estas integraciones (binding), cada una con sus “pros” y sus “contras”
¿Porqué programación orientada a objetos?
El punto de vista de integración temprana (early binding) es la que está más difundida, porque es la única aproximación que se ofrece en la mayoría de los lenguajes convencionales. Con la integración temprana, la integración se hace en tiempo de compilación, de manera que el consumidor y sus herramientas (el compilador) son los responsables de la integración.
Proveedor
Consumidor
Responsable
¿Porqué programación orientada a objetos?
La integración tardía (late binding), que también se conoce como “ligadura tardía” o “ligadura dinámica”, significa que la integración se hace después del momento de la compilación, generalmente mientras el programa se está ejecutando. La integración tardía traslada la responsabilidad de la integración a los operandos, es decir, al proveedor que haya definido este tipo de operando
Proveedor
Responsable
Consumidor
El universo abierto frente al universo cerrado
Las aproximaciones de integración temprana y tardía que se han tratado antes, no son equivalentes. La integración temprana funciona en un universo cerrado, en el cual todas las interacciones potenciales entre las diferentes partes del entorno se pueden declarar cuando estas partes son creadas por el compilador. La integración tardía pasa a ser esencial en un universo abierto, en el cual las partes que pueden interactuar no se conocen mientras no se ejecuta el programa.
Pheaton-VW
El universo abierto frente al universo cerrado
Un motor de automóvil es una colección de universo cerrado. Porque los tipos de sus componentes pueden ser especificados de forma estricta mucho antes de que se utilice el motor.
Pheaton-VW
El universo abierto frente al universo cerrado
La cajuela es una colección de universo abierto. Porque el tipo de objetos que contendrá no se puede predecir de antemano.
Pheaton-VW
El universo abierto frente al universo cerrado
Por ejemplo, supóngase que estamos construyendo una clase Punto en la cual las coordenadas x e y son ambas enteros de 16 bits. La clase Punto es un universo cerrado, porque hemos decidido (y tenemos la intención de imponer) que sólo puedan aparecer en ella números enteros de 16 bits. Si alguien quiere reutilizar la clase Punto en un dominio real, en el cual las coordenadas deben ser números reales, tendrán que descartar nuestro trabajo, y volver a construir de nuevo, o bien tendrán que editar nuestra clase y volver a compilar.
Punto int x,y
¿?
Punto float x,y
El universo abierto frente al universo cerrado
El ejemplo del buzón es completamente diferente. El buzón es un universo abierto, donde no se puede prohibir el cambio, un buzón debe contener cualquier objeto que responda a un protocolo publicado, puede ser utilizado tal como está, sin tener que modificarlo cada vez que sea necesario enviar un nuevo tipo de objeto.
Buzon
Buzon
id
refacciones
El universo abierto frente al universo cerrado
El ejemplo del buzón es completamente diferente. El buzón es un universo abierto, donde no se puede prohibir el cambio, un buzón debe contener cualquier objeto que responda a un protocolo publicado, puede ser utilizado tal como está, sin tener que modificarlo cada vez que sea necesario enviar un nuevo tipo de objeto.
Buzon
Buzon
id
refacciones
Productividad en software
Casi todo el mundo estaría de acuerdo en que la productividad del SW es baja; ciertamente, es más baja de lo que nos gustaría y, probablemente, es más baja de lo que necesita ser. Resulta más difícil llegar a un acuerdo en lo tocante a lo que es la productividad, al menos en un sentido cuantificable que permitiera definir y medir científicamente experimentos controlados. ¿Qué es la productividad en software? o bien, algo muy parecido: ¿Cómo se mide? al medir algo, uno espera entender los factores que influyen en ese algo y, a partir de ellos, descubrir formas de controlarlo con provecho. Este es el objetivo de la Métrica de Software, el estudio de los factores que influyen en la productividad, midiendo proyectos de desarrollo de software, naturales o artificiales.
“Al por mayor” es malo
Aunque muchas de las hipótesis de este campo no estan probadas y hay controversia acerca de ellas, hay poco desacuerdo con la más básica: “Al por mayor” es malo. Un estudio tras otro ha confirmado que los costos de desarrollo se incrementan, y que disminuye la productividad, de manera más que lineal con el tamaño del proyecto.
“Al por mayor” es malo
¿Quién no ha conseguido una productividad asombrosa como individuo solitario, diseñando primero y construyendo después algún pequeño proyecto como tarea solitaria?
“Al por mayor” es malo
Y, ¿quién no se ha sentido frustrado por el progreso más lento de un proyecto más grande que requería un trabajo en equipo coordinado?
A medida que aumenta el tamaño del proyecto, el número de individuos se incrementa, y esto incrementa la cantidad de esfuerzo que se desperdicia en gastos de coordinación
“Al por mayor” es malo
Una medida frecuente de la importancia de un código es contar el número de líneas de código, y una forma corriente de medir la productividad en software es contar el número de líneas de código que se han producido por unidad de tiempo. Estos números son realmente fáciles de obtener y la verdad es que han mostrado un éxito notable a efectos de hacerse rápidamente a la idea de cómo están las cosas. Pero, desde luego, la medida del código total no satisface a nadie. Con seguridad, la productividad en software implica algo más que el código total. Si la productividad ese puede medir realmente como la velocidad con que se producen líneas, ¿por qué no hacer un ciclo pequeñito, que produzca el código a toda velocidad, y mandar a casa a los programadores?
La superficie de contacto es mala
Cuando se estaba construyendo la presa de Aswan en Egipto, otra actividad diferente se desarrollaba aguas arriba.
La superficie de contacto es mala
El pantano de la presa se iba a inundar, eventualmente, un cierto número de antiquísimos templos y monumentos, de un valor histórico inmenso. para preservar estos tesoros, algunos podían ser protegidos mediante diques, pero no había más remedio que trasladar los demás.
La superficie de contacto es mala
Hubo templos enteros que partieron con todo cuidado en pedazos de proporciones manejables, luego se enviaron a una nueva y segura ubicación, y se reensamblaron después para adoptar su forma original, como un gigantesco compecabezas. El costo fue tremendo, y debió haber sido examinado con tanto cuidado como se estudian hoy en día los costos del desarrollo de software.
La superficie de contacto es mala
Debe haber habido tiempo para medir la productividad cuando se trasladaron los primeros templos, y expertos que estudiasen estas cifras con la intención de optimizar la productividad del traslado de templos. Claramente, los costos de trasladar un templo dependen del número y tamaño de los templos que se muevan, o la masa bruta de templos. Pero también depende de esto el costo de casi cualquier cosa. Pero la masa no era en absoluto el único factor, ni siquiera un factor predominante, en el costo de trasladar los templos. Dado que el resultado final deberían ser templos y no piedras y grava, el costo dependía también del número y complejidad de las piedras de templos que tendrían que ser reensambladas. El costo de trasladar piedras y grava depende sobre todo de la masa; el costo de trasladar un templo depende también del área superficial.
La superficie de contacto es mala
La productividad del software depende no sólo de la mera masa, sino también del área superficial, esto es, del número de cosas que deben ser comprendidad y tratadas correctamente para que el código de un programador funcione correctamente en combinación con el de otros. Entre las cosas que influyen en el área superficial del software están: 1) 2) 3) 4) 5) 6)
Ocultación de información Tipos de datos abstractos Secuencia temporal de operaciones Recolección de basura Dominios de protección Concurrencia
La superficie de contacto es mala
1) Ocultación de información El área superficial aumenta con el número de nombres que son visibles en la interfaz entre el proveedor y el consumidor. Esto incluye los nombres de elementos de datos (nombres de los miembros de las estructuras), nombres de tipos de datos (nombres de estructuras), y nombres de funciones. Los sistemas de empaquetamiento del software que reducen el número de nombres que tiene que aprender y utilizar correctamente el consumidor hacen que disminuya el área superficial.
La superficie de contacto es mala
2) tipos de datos abstractos La dependencia de tipos entre estos nombres incrementa el área superficial de las interfaces. Por ejemplo, el requisito de que una función admita dos argumentos, uno de tipo complejo y otro real, incrementa el número de cosas que el consumidor debe hacer bien para utilizar con éxito esta función.
La superficie de contacto es mala
3) Secuencia temporal de operaciones Todo requisito de que el consumidor lleve a cabo las operaciones en un orden concreto incrementa el área superficial. Por ejemplo, el requisito consistente en que el consumidor sepa que el objeto debe ser reservado primero, iniciado después y que después se accede a él, incrementa el número de cosas que debe hacer bien el consumidor, y por tanto el área superficial de la interfaz eltre el consumidor y el proveedor.
La superficie de contacto es mala
4) Recolección de basura Si el consumidor es el responsable de liberar los objetos cuando ya no se necesitan, debe ser consciente de todo el sistema par asegurarse de que cada objeto sea liberado una sola vez, y una sola, durante toda la ejecución del programa, y además debe asegurarse de que nunca acceda a él posteriormente. Esto incrementa enormemente el área superficial, puesto que obliga a todos los consumidores a comprender la totalidad de la aplicación y no solo la interfaz con sus proveedores.
La superficie de contacto es mala
5) Dominios de protección Los grandes sistemas implican procesos que corren dentro de un dominio protegido, tal como el espacio de direcciones de una computadora o un proceso de un sistema de tiempo compartido. Los procesos protegidos se comunican a través de archivos, tubos, paquetes, transacciones o mensajes. Cada comunicación entre dominios protegidos incrementa el área superficial, al exterior una nueva influencia, que es distinta de las que funcionan en el cuerpo principal del código.
La superficie de contacto es mala
6) Concurrencia Los problemas concurrentes tienen un área superficial mayor que las áreas secuenciales, porque ahora las interacciones pueden ocurrir no solo a través del espacio, sino a través del tiempo.
Palabras únicas
La programación orientada a objetos es una tecnología de empaquetado. Permite a los proveedores embalar unidades de funcionalidad genérica, de tal forma que los consumidores puedan aplicarlas a su aplicación concreta.
La funcionalidad se empaqueta de tal modo que pueda utilizarse de nuevo. La programación orientada a objetos es solamente una de entre muchas maneras de empaquetar el código. Aquí vamos a ver tres maneras de empaquetar código, usando el ejemplo de las palabras únicas.
Tecnología de empaquetado 1: Pipes y filtros de UNIX
Una de las tecnologías de empaquetado más eficientes, desde la perspectiva del programador, para empaquetar el código con vistas a su reutilización, es la que se presentó con UNIX, y que se ha extendido ahora a otros entornos de programación también. La tecnología de pipes y filtros empaqueta el código reutilizable en pequeños programas independientes, en que cada programa es una herramienta (un filtro) que hace bien una sola tarea. Los filtros se pueden conectar mediante tubos (pipes), a través de los cuales la salida de un filtro puede ser recibida como entrada del siguiente.
Tecnología de empaquetado 1: Pipes y filtros de UNIX
Esta tecnología se encuentra “en su salsa” con problemas como el presente. La tarea se puede realizar con una sola línea de código, conectando tres filtros mediante dos tubos, de la manera siguiente. tr -cs ‘A-Za-z0-9’ ‘\012’ | sort -u | wc -1 Los símbolos | denotan pipes, los demás denotan llamadas a herramientas. La primera herramienta es el programa tr, que traduce los caracteres. Con las opciones utilizadas aquí (-cs), traduce cada caracter no alfanumérico (‘A-Za-z0-9’) en un caracter de nueva línea (‘\012’), para producir un flujo de bytes en que cada palabra se encuentra en una línea distinta. Este flujo se lleva por el tubo hasta el programa de ordenamiento, que clasifica las palabras
Tecnología de empaquetado 1: Pipes y filtros de UNIX
tr -cs ‘A-Za-z0-9’ ‘\012’ | sort -u | wc -1 Aquí sort se utiliza con una opción (-u) que le hace descartar las palabras duplicadas, produciendo un nuevo flujo de bytes que solo contiene palabras sin repetir
Tecnología de empaquetado 1: Pipes y filtros de UNIX
tr -cs ‘A-Za-z0-9’ ‘\012’ | sort -u | wc -1 Estas palabras se llevan al programa wc, que cuenta las palabras sin repetir que haya en el documento.
Tecnología de empaquetado 1: Pipes y filtros de UNIX
Esta es una de las tecnologías de reutilización más potentes que se conocen en la actualidad. 1) Las herramientas pequeñas están bien encapsuladas, 2) se documentan con facilidad, 3) y no requieren una recompilación (esto es, no es necesario disponer de su código fuente) para reensamblarlas con objeto de resolver problemas en un universo abierto que su creador nunca imaginó.
Tecnología de empaquetado 1: Pipes y filtros de UNIX
Sin embargo, esta tecnología no es la panacea para empaquetar código reutilizable. Por una parte, pasar bytes por un tubo es más costoso que pasar argumentos entre dos subrutinas, y esto es por órdenes de magnitud. Luego, los tubos sólo transportan flujos de bytes, y no los datos sumamente estructurados que abundan en la construcción de sistemas. No se puede pasar un puntero o una lista enlazada a través de un tubo sin grandes dificultades.
Tecnología de empaquetado 2: Biblioteca de subrutinas
Ciertamente, la técnica de empaquetado que tiene más éxito, a juzgar por la frecuencia con que se utiliza, es la biblioteca de subrutinas y macros bien probadas y conocidas. Casi todos los lenguajes de programación proporcionan alguna forma de almacenar módulos compilados en una biblioteca, y de enlazar ese código en aplicaciones. Es el método de emplear la reutilizabilidad más usado de la industria de la programación.
Tecnología de empaquetado 2: Biblioteca de subrutinas
Si se aplicase al ejemplo de las palabras únicas, esta tecnología: a) Produciría una funcionalidad empaquetada (procedente de la biblioteca de E/S de UNIX) para abrir y cerrar archivos, para leer caracteres, y para imprimir resultados. b) Suministraría una biblioteca de funciones para tratar palabras, almacenadas como cadenas de caracteres. c) Y, desde el punto de vista del constructor de aplicaciones, el propio sistema operativo es un paquete de llamadas a funciones para leer y escribir archivos, etc. Pero esta tecnología no ofrece ninguna manera de llevar la cuenta de las palabras distintas que se han leído hasta el momento, así que sería neceario partir de cero en el desarrollo.
Tecnología de empaquetado 2: Biblioteca de subrutinas
Esto señala la mayor desventaja de las bibliotecas de subrutinas como tecnología para la reutilizabilidad. ¿Por qué no ha de ofrecer un entorno de programación tan rico como UNIX, una rutina para resolver problemas tan simples, tan ubicuos, como una búsqueda? 1) El algoritmo necesario ya está disponible. 2) El compilador de C contiene algoritmos para buscar símbolos, 3) El editor de textos contiene algoritmos para gestionar las definiciones de las macros, 4) La utilidad de búsqueda de archivos contiene algoritmos para buscar cadenas de textos, etc. Y sin embargo, para seguir adelante con este ejercicio es preciso construir otro algoritmo de búsqueda más, y partiendo de cero.
Tecnología de empaquetado 2: Biblioteca de subrutinas
La razón no es sutil, y es importantísima para todo lo que este curso va a ofrecer. La tecnología de bibliotecas de subrutinas está limitada por el hecho consistente en que cada trozo de código debe estar fuertemente acoplado con el entorno para el cual ha sido desarrollado esa porción de código (esto es, tiene que existir en un universo cerrado). El algoritmo que utiliza el compilador de C para buscar identificadores definidos no se puede sacar y volver a utilizar, porque está fuertemente acoplado a su entorno (la aplicación que es el compilador de C), y al tipo específico de datos que está preparado para buscar (la estructura de datos que se utiliza para los identificadores en el compilador de C).
Tecnología de empaquetado 2: Biblioteca de subrutinas
Por supuesto, esta limitación está compensada por el beneficio correspondiente: Una magnífica eficiencia a efectos de la máquina. Es posible, incluso, llegar a una estimación cualitativa de los beneficios obtenidos. Una tecnología de reutilización que fuese perfecta nos habría permitido utilizar aquí un algoritmo Hash, pero la tecnología de empaquetado requería que estuviese ligada al manejo de Nodos, y aquí necesitamos un algoritmo que maneje Palabras. Un algoritmo de esta naturaleza requiere de unas 100-120 líneas de código moderadamente complicado en C. Esta cifra nos permite estimar la cantidad de código que sería necesario escribir partiendo de cero, para resolver el ejercicio de las palabras únicas.
Tecnología de empaquetado 2: Biblioteca de subrutinas
Estas 100 líneas de código construyen la tabla de claves en sí, una función para calcular las claves a partir de los Nodos, la lógica que permite gestionar las colisiones, etc.
Tecnología de empaquetado 2: Biblioteca de subrutinas
Donde la tecnología de tubos y filtros resolvía el problema con una sola línea, la tecnología de biblioteca de subrutinas requiere de mas de 100, ¡una diferencia de más de 2 órdenes de magnitud! Pero, por supuesto, cabe esperar que la eficiencia a efectos de la máquina sea también diferente.
Tecnología de empaquetado 3: Software integrado
Las dos soluciones anteriores proporcionan puntos de referencia, con los cuales podemos comparar la tecnología de empaquetado acerca de la cual haremos énfasis. La tecnología de software integrado es parecida a la de los tubos y filtros, porque gran parte del trabajo se hace en el momento de la ejecución, pero difiere en que la eficiencia de máquina es mucho mayor, aunque algo menor que la técnica de biblioteca de subrutinas. Pero se parece a la técnica de la biblioteca de subrutinas en que se pueden tratar fácilmente unas estructuras de datos complicadas.
Tecnología de empaquetado 3: Software integrado
Esta solución también utiliza las bibliotecas de funciones de UNIX para gestionar archivos, leer caracteres, y comparar cadenas. Pero además introduce una biblioteca adicional, llamada IC pack, que contiene un par de software integrado, llamados ICSet (conjunto) y ICString (cadena) respectivamente. Los softwares integrados son archivos binarios, iguales a los que hay en una biblioteca de subrutinas convencional. El ICString realiza una clase de objetos que contiene matrices de caracteres, y éstos se van a utilizar para representar las palabras que haya en el documento. El ICSet realiza una clase de objetos que contiene conjuntos de otros objetos. Como los conjuntos no admiten miembros duplicados, estos objetos utilizan una tabla hash para comprobar rápidamente que cada uno de los objetos que se van agregando no sea un duplicado de algún miembro ya existente.
Tecnología de empaquetado 3: Software integrado
Estos componentes se pueden utilizar como sigue. Se crea un conjunto vacío. Para cada palabra del archivo, se crea una instancia de String que contiene los caracteres de la palabra, y se agrega una instancia al conjunto (el propio conjunto se encargará de rechazar los miembros que pudieran estar duplicados). Cuando el archivo se termina, se indica que el tamaño del conjunto es el número de palabras sin repetir que hay en el archivo. Esta descripción se puede traducir, línea por línea, a un lenguaje de programación, y se puede compilar para producir un programa ejecutable.
Tecnología de empaquetado 3: Software integrado
palabrasUnicas = [Set new]; while(getWord(buf) != EOF { currentWord=[String str:buf]; [palabrasUnicas add:currentWord]; } printf(”El numero de palabras distintas es %d\n”,[palabrasUnicas size]);
Tecnología de empaquetado 3: Software integrado
palabrasUnicas = [Set new]; /* while(getWord(buf) != EOF { /* currentWord=[String str:buf]; /* [palabrasUnicas add:currentWord]; /* } printf(”El numero de palabras distintas
palabrasUnicas es un conjunto vacio */ se lee una palabra y se pone en buf */ currentWord es un byteArray */ agrega currentWord al conjunto */ es %d\n”,[palabrasUnicas size]);
Tecnología de empaquetado 3: Software integrado
palabrasUnicas = [Set new]; /* while(getWord(buf) != EOF { /* currentWord=[String str:buf]; /* [palabrasUnicas add:currentWord]; /* } printf(”El numero de palabras distintas
palabrasUnicas es un conjunto vacio */ se lee una palabra y se pone en buf */ currentWord es un byteArray */ agrega currentWord al conjunto */ es %d\n”,[palabrasUnicas size]);
En casi todos los aspectos, este fragmento es un programa en C completamente normal. Las expresiones de asignación, el bucle while, y las funciones getWord y printf se comportan exactamente igual que en el C habitual. Es diferente de C en que contiene tres expresiones con mensaje, encerradas entre corchetes (por ejemplo [Set new]). Esyas extensiones han sido agregadas a C por Objective-C.
Tecnología de empaquetado 3: Software integrado
palabrasUnicas = [Set new]; /* while(getWord(buf) != EOF { /* currentWord=[String str:buf]; /* [palabrasUnicas add:currentWord]; /* } printf(”El numero de palabras distintas
palabrasUnicas es un conjunto vacio */ se lee una palabra y se pone en buf */ currentWord es un byteArray */ agrega currentWord al conjunto */ es %d\n”,[palabrasUnicas size]);
La primera expresión del tipo mensaje indica al objeto llamado Set que lleve a cabo la operación denominada new, y almacena el resultado en la variable local palabrasUnicas. Los objetos cuyos nombres comienzan por una letra mayúscula, como Set, se denominan objetos fábrica. Cada software integrado contiene su propio objeto fábrica, y éstos se utilizan para producir instancias de la clase de objetos que admita este software integrado. Esta línea da lugar a que la fábrica de Set produzca un nuevo conjunto (en otras palabras, una nueva instancia de la clase Set) y da a esa instancia el nombre palabrasUnicas.
Tecnología de empaquetado 3: Software integrado
palabrasUnicas = [Set new]; /* while(getWord(buf) != EOF { /* currentWord=[String str:buf]; /* [palabrasUnicas add:currentWord]; /* } printf(”El numero de palabras distintas
palabrasUnicas es un conjunto vacio */ se lee una palabra y se pone en buf */ currentWord es un byteArray */ agrega currentWord al conjunto */ es %d\n”,[palabrasUnicas size]);
La segunda línea utiliza una función normal de C, getWord(), para leer la palabra siguiente del archivo, y la almacena en una matriz de caracteres ordinaria de C, llamada buf.
Tecnología de empaquetado 3: Software integrado
palabrasUnicas = [Set new]; /* while(getWord(buf) != EOF { /* currentWord=[String str:buf]; /* [palabrasUnicas add:currentWord]; /* } printf(”El numero de palabras distintas
palabrasUnicas es un conjunto vacio */ se lee una palabra y se pone en buf */ currentWord es un byteArray */ agrega currentWord al conjunto */ es %d\n”,[palabrasUnicas size]);
La tercera línea es otra expresión del tipo mensaje, que, en esta ocasión, dice al objeto llamado String que lleve a cabo la operación llamada str:. Los dos puntos que hay en el nombre de la operación significan que la operación admite un argumento, buf. Esta sentencia ordena s String, el objeto fábrica del software integrado llamado String, que produzca una nueva instancia, la cual contendrá los caracteres que getWord haya obtenido del archivo. El identificador de este nuevo objeto se almacena en la variable local, currentWord.
Tecnología de empaquetado 3: Software integrado
palabrasUnicas = [Set new]; /* while(getWord(buf) != EOF { /* currentWord=[String str:buf]; /* [palabrasUnicas add:currentWord]; /* } printf(”El numero de palabras distintas
palabrasUnicas es un conjunto vacio */ se lee una palabra y se pone en buf */ currentWord es un byteArray */ agrega currentWord al conjunto */ es %d\n”,[palabrasUnicas size]);
La siguiente línea indica al conjunto palabrasUnicas, que lleve a cabo la operación, add:, sobre el argumento currentWord.
Tecnología de empaquetado 3: Software integrado
palabrasUnicas = [Set new]; /* while(getWord(buf) != EOF { /* currentWord=[String str:buf]; /* [palabrasUnicas add:currentWord]; /* } printf(”El numero de palabras distintas
palabrasUnicas es un conjunto vacio */ se lee una palabra y se pone en buf */ currentWord es un byteArray */ agrega currentWord al conjunto */ es %d\n”,[palabrasUnicas size]);
Por último, cuando el archivo se termina, se utiliza la función printf de C para imprimir el número de palabras distintas que hay en el documento. Este número se calcula ordenando alconjunto que informe acerca de la cantidad de miembros que posee, enviándole el mensaje size.
Tecnología de empaquetado 3: Software integrado
Esta solución implica unas 10 líneas de código (las 6 del programilla mas otras 4 más o menos de declaraciones). Esto es de un orden de magnitud menos que el número de líneas que empleaba la aproximación basada en una biblioteca de subrutinas, pero también es un orden de magnitud mayor que la aproximación de tubos y filtros (1 línea). Por otro lado, la versión realizada con la biblioteca de subrutinas es la que se ejecutará más deprisa; la versión del softwatr integrado será algo más lenta, y la versión hecha con tubos será la más lenta de las tres.
El momento de la integración y el grado de acoplamiento
La integración temprana (o anticipada) y la integración tardía son herramientas absolutamente diferentes, y sirven para tareas distintas, pero esta distinción siempre se entiende mal. Por ejemplo, consideremos el reciente entusiasmo por los lenguajes con comprobación estricta de tipos y con sobrecarga de operadores, como puede ser Ada.
El momento de la integración y el grado de acoplamiento
La integración temprana (o anticipada) y la integración tardía son herramientas absolutamente diferentes, y sirven para tareas distintas, pero esta distinción siempre se entiende mal. Por ejemplo, consideremos el reciente entusiasmo por los lenguajes con comprobación estricta de tipos y con sobrecarga de operadores, como puede ser Ada. Uno de los conceptos de diseño que subyacen a Ada es que la semántica de un cierto programa debe ser estática en el porcentaje más alto que pueda resultar práctico. La fiabilidad, por lo tanto, mejora, porque gran parte del análisis del programa se puede hacer en el momento de la compilación. Por tanto, en Ada, observaremos que el tipo básico de todo objeto está diseñado para ser estático, y que el número, el ámbito y la visibilidad de las entidades es conocida antes de la ejecución. Pero cuando abstraemos los objetos en el espacio del problema, sin embargo, encontramos que el mundo no es tan agradablemente estático.
El momento de la integración y el grado de acoplamiento
La respuesta de Ada para las situaciones de universo abierto es el tipo de acceso o variable puntero, que permite al programador construir colecciones débilmente acopladas, realizando él mismo una integración dinámica mediante sentencias condicionales. El mundo contiene colecciones de los dos tipos. Por ejemplo, consideremos el motor de un automóvil y la cajuela del automóvil.
Pheaton-VW
El momento de la integración y el grado de acoplamiento
El motor es una colección fuertemente acoplada, de universo cerrado. Al principio del ciclo vital del automóvil (ya en el momento en que es diseñado), es posible, y es deseable, saber que el motor a a constar de un bloque, unos pistones, unos segmentos y un cigüeñal.
Pheaton-VW
El momento de la integración y el grado de acoplamiento
Por otra pare, la cajuela del automóvil es un universo abierto, una colección débilmente acoplada. No es posible, ni siquiera deseable, especificar lo que va a contener la cajuela de un automóvil antes de que ese automóvil empiece a utilizarse.
Pheaton-VW
Ligadura estática y colecciones de acoplamiento fuerte
En programación, un número real es un ejemplo de colección fuertemente acoplada. Ya desde el momento del diseño, se sabe que cada número va a estar formado precisamente por una sola mantisa y un solo exponente. Este conocimiento se puede incorporar de forma inalterable a una herramienta de programación como un compilador de C, y el programador puede olvidar para siempre las características y los exponentes, y pensar únicamente en términos de conceptos de un nivel superior: los números reales y las operaciones que se pueden realizar con ellos.
Ligadura estática y colecciones de acoplamiento fuerte
El compilador traduce las notaciones de alto nivel, tales como a=b+2.3 a operaciones realizadas con mantisas y exponentes, basándose en su conocimiento de que a,b y 2.3 son del tipo numeroDeComaFlotante. Este conocimiento se emplea anticipadamente, en el momento de la compilación, y esta es la razón por la cual esta forma de traducir símbolos a código se denimona ligadura estática (ligadura temprana).
Ligadura estática y colecciones de acoplamiento fuerte
El lenguaje C también ofrece formas en que el programador puede definir también nuevos tipos de datos. Por ejemplo, un programador de gráficos puede decidir que va a definir un nuevo tipo de datos, vector, como una colección fuertemente acoplada, formada por dos enteros, que describen los puntos finales de un vector geométrico: typedef struct {int x,y;} vector;
(x,y)
Ligadura estática y colecciones de acoplamiento fuerte
Idealmente, el lenguaje permitiría tanta flexibilidad para escribir operaciones con los vectores como la que ofrece C para escribir operaciones con números de punto flotante. Por ejemplo, dado el origen de un rectángulo (su esquina superior izquierda) y su extensión (su dimensión diagonal): un programador podría querer calcular el vactor que indica su esquina inferior derecha escribiendo: vector baseDerecha=origen+extension; Esto no es posible en C, ni en otros lenguajes convencionales como Pascal, FORTRAN y COBOL. Estos lenguajes no permiten al programador cambiar el significado de los operadores incorporados, como +, así que estos operadores no se pueden aplicar a tipos de datos que se hayan agregado.
Ligadura estática y colecciones de acoplamiento fuerte
Estos lenguajes exigen que el programador descomponga manualmente cada operación en tipos primitivos y escribir: baseDerecha.x = origen.x + extension.x; baseDerecha.y = origen.y + extension.y; El proveedor de un nuevo tipo de datos, como vector, no puede ocultar a los ojos de los consumidores la realización de su nuevo tipo. Necesitan conocer la realización para conocer el tipo, porque tienen que hacer todas las ligaduras entre vectores y operaciones manualmente, cuando escriban su propio código.
Ligadura estática y colecciones de acoplamiento fuerte
La tendencia actual es hacia lenguajes que sí ofrecen esta flexibilidad, siempre y cuando todos los tipos sean conocidos en el momento de la compilación. Por ejemplo, los lenguajes como Ada permiten que se definan los vectores como nuevo tipo de datos con +, que es una operación válida para este tipo. Cuando se compila la expresión: baseDerecha = origen + extension; el compilador nota que tanto origen como extension son vectores, y determina que es preciso emplear un significado especial de +; se trata del que ha definido el proveedor de ese tipo. Estos lenguajes ofrecen la ligadura dinámica de una forma más potente que C, porque el significado de operadores predefinidos, como +, se puede cambiar para tipos nuevos que se hayan definido.
Ligadura estática y colecciones de acoplamiento fuerte
Esta aproximación puede resolver algunas de las dificultades que se encontraban cuando se aplicaban las bibliotecas de subrutinas al problema de las palabras únicas. El problema consistía en definir un nuevo tipo de elementos que se iban a almacenar en el conjunto Set. La solución de Ada para esta clase de problemas se denomina paquete genérico; esto es, un paquete de código con un parámetro del momento de la compilación que especifica el tipo de datos que debe ser gestionado por el conjunto. La aplicación palabras únicas se resolvería escribiendo una sentencia en el código del consumidor, que indicase al compilador que compilara un Set creado para manejar Strings
Ligadura dinámica y colecciones de acoplamiento débil
Los lenguajes de ligadura estática son perfectos para construir colecciones fuertemente acopladas, como los vectores y rectángulos. Se les puede llevar, incluso, a gestionar casos más difíciles, como el del ejemplo de las palabrasUnicas. Por supuesto, recompilar el código de un proveedor cada vez que un consumidor proporciona un nuevo tipo de datos, puede ser un problema y no sólo por el tiempo que se necesita para compilar estos conjuntos hechos a la medida, sino también por la memoria que se necesita para almacenarlos. Un tema más fundamental es que el proveedor debe confiar en que el compilador protegerá aquellos intereses privados que puedan contener esos recursos. Resulta difícil pensar que pudiera desarrollarse un mercado comercial de paquetes genéricos si los proveedores tuvieran que confiar en que el compilador protegiera sus intereses privados en código fuente.
Ligadura dinámica y colecciones de acoplamiento débil
El hecho de que los lenguajes ligados estáticamente son sumamente inadecuados para construir colecciones débilmente acopladas tiene una difusión menor; los problemas de universo abierto son más parecidos a la cajuela de un automóvil que a su motor.
Pheaton-VW
Ligadura dinámica y colecciones de acoplamiento débil
Las colecciones débilmente acopladas abundan en aplicaciones como la automatización de oficinas, en montajes orientados a la información como Escritorio, Sobre, Carpeta, Clip, Buzón y Archivero. Un escritorio, o un buzón, son colecciones débilmente acopladas, porque no es posible, ni deseable fijar el tipo de objetos que contienen, en un momento anterior a aquel en que se esté utilizando el escritorio.
Ligadura dinámica y colecciones de acoplamiento débil
Cuando se conocen todos los tipos de datos en el momento en que se compila el código, la aproximación de universo cerrado de la ligadura estática funciona. En caso contrario, es preciso realizar la ligadura dinámica, y punto.
Ligadura dinámica y colecciones de acoplamiento débil
Cuando se conocen todos los tipos de datos en el momento en que se compila el código, la aproximación de universo cerrado de la ligadura estática funciona. En caso contrario, es preciso realizar la ligadura dinámica, y punto. No hay posibilidad de eleccion entre ligadura estática y dinámica, porque la ligadura dinámica es intrínseca a la misma escencia de una colección débilmente acoplada. Sin embargo, existen opciones a la hora de emplear uno u otro método para realizar la ligadura dinámica, puesto que la ligadura dinámica se puede hacer manualmente, mediante sentencias condicionales escritas por el programador, o bien automáticamente mediante el lenguaje de programación y el entorno de ejecución.
Ligadura dinámica y colecciones de acoplamiento débil
Calendario Carpeta Sobre Buzon MientrasEstabasFuera La ligadura la hace cada consumidor item = nextItemMailBox(); ListaPrecios switch(item->type){ case CUENTAGASTOS: expOp(); La ligadura la hace cada proveedor case LISTAPRECIOS: priceOp(); case MIENTRASESTABASFUERA case SOBRE case CALENDARIO Item = nextItemMailBox(); case CARPETA(item doThis); default; }
CuentasGastos
Los tipos de objetos que hay en un proyecto de automatización de oficinas en forma de carpetas o archivadores, cada una de las cuales representa el trabajo de un programador distinto. La carpeta buzón está abierta, para mostrar las dos opciones del programador del buzón a la hora de realizar la ligadura dinámica.
Ligadura dinámica y colecciones de acoplamiento débil
Calendario Carpeta Sobre Buzon MientrasEstabasFuera La ligadura la hace cada consumidor item = nextItemMailBox(); ListaPrecios switch(item->type){ case CUENTAGASTOS: expOp(); La ligadura la hace cada proveedor case LISTAPRECIOS: priceOp(); case MIENTRASESTABASFUERA case SOBRE case CALENDARIO Item = nextItemMailBox(); case CARPETA(item doThis); default; }
CuentasGastos
Los tipos de objetos que hay en un proyecto de automatización de oficinas en forma de carpetas o archivadores, cada una de las cuales representa el trabajo de un programador distinto. La carpeta buzón está abierta, para mostrar las dos opciones del programador del buzón a la hora de realizar la ligadura dinámica.
El fragmento de la izquierda muestra la aproximacion manual, hay una sentencia switch que determina de qué tipo de objeto se trata, y que pasa correctamente el control a la operación (una funcion de C) que manipula correctamente ese tipo de objeto
Ligadura dinámica y colecciones de acoplamiento débil
Calendario Carpeta Sobre Buzon MientrasEstabasFuera La ligadura la hace cada consumidor item = nextItemMailBox(); ListaPrecios switch(item->type){ case CUENTAGASTOS: expOp(); La ligadura la hace cada proveedor case LISTAPRECIOS: priceOp(); case MIENTRASESTABASFUERA case SOBRE case CALENDARIO Item = nextItemMailBox(); case CARPETA(item doThis); default; }
CuentasGastos
El tipo de datos esta representado mediante un campo que hay dentro de todos los objetos, representados mediante estructuras en C: struct Memorandum { int tipo; ...}; struct NotaMientrasEstabasFuera { int tipo; ...}; struct Carpeta { int tipo; ...};
El fragmento de la izquierda muestra la aproximacion manual, hay una sentencia switch que determina de qué tipo de objeto se trata, y que pasa correctamente el control a la operación (una funcion de C) que manipula correctamente ese tipo de objeto
Ligadura dinámica y colecciones de acoplamiento débil
Calendario Carpeta Sobre Buzon MientrasEstabasFuera La ligadura la hace cada consumidor item = nextItemMailBox(); ListaPrecios switch(item->type){ case CUENTAGASTOS: expOp(); La ligadura la hace cada proveedor case LISTAPRECIOS: priceOp(); case MIENTRASESTABASFUERA case SOBRE case CALENDARIO Item = nextItemMailBox(); case CARPETA(item doThis); default; }
CuentasGastos
El fragmento que hay a la derecha muestra la otra aproximación, en la cual el lenguaje de programación proporciona automáticamente la ligadura dinámica.
Ligadura dinámica y colecciones de acoplamiento débil
Calendario Carpeta Sobre Buzon MientrasEstabasFuera La ligadura la hace cada consumidor item = nextItemMailBox(); ListaPrecios switch(item->type){ case CUENTAGASTOS: expOp(); La ligadura la hace cada proveedor case LISTAPRECIOS: priceOp(); case MIENTRASESTABASFUERA case SOBRE case CALENDARIO Item = nextItemMailBox(); case CARPETA(item doThis); default; }
CuentasGastos
El desarrollador del buzón, que es consumidor de los otros seis tipos de datos de este sistema, especifíca lo que tiene que hacer cada objeto, escribiendo la expresión de mensaje [item doThis], y es cuestión del objeto decidir cómo se debe realizar la orden doThis para esa clase de objeto.
El fragmento que hay a la derecha muestra la otra aproximación, en la cual el lenguaje de programación proporciona automáticamente la ligadura dinámica.
Ligadura dinámica y colecciones de acoplamiento débil
La diferencia entre estas dos aproximaciones es crucial para deslindar las responsabilidades del proveedor y del consumidos. El desarrollador del buzón es consumidor de una funcionalidad que se le proporciona en otra parte de este proyecto. La primera aproximación requiere que él, el consumidor, asuma la responsabilidad de determinar cuál de los datos del proveedor es el que se encuentra en el buzón, y también debe encargarse de seleccionar aquella subrutina del proveedor que sea correcta para ese tipo de datos. Esto se denomina ligadura por parte del consumidor. Por el contrario, la otra aproximación pone esta responsabilidad en el lugar que le corresponde, esto es, en manos del proveedor.
Ligadura dinámica y colecciones de acoplamiento débil
El problema de la ligadura por parte del consumidor puede apreciarse en las etiquetas de caso que hay en la sentencia switch. Estas etiquetas enumeran explícitamente los tipos de datos que puede manejar el buzón; por tanto, es preciso cambiar el código del buzón cada vez que se agrega algún tipo nuevo en cualquier parte del sistema.
La ligadura la hace cada consumidor item = nextItemMailBox(); switch(item->type){ case CUENTAGASTOS: expOp(); case LISTAPRECIOS: priceOp(); case MIENTRASESTABASFUERA ... case SOBRE ... case CALENDARIO ... case CARPETA ... default; }
Se ha infiltrado algo desagradable, a través de la barrera que debería aislar a los proveedores y a los consumidores. Este buzón resulta inútil en cualquier otra aplicación, porque las etiquetas de casos determinan explícitamente que sólo funcionará bien para esos seis tipos.
Ligadura dinámica y colecciones de acoplamiento débil
Uno de los efectos de la ligadura por parte del usuario es la falta de reutilizabilidad. Obliga a escribir el buzón de una forma que evita que pueda ser utilizado otra vez en otras aplicaciones. La ligadura por parte del usuario conduce también a una falta de maleabilidad. Cada vez que se agrega un nuevo objeto al sistema, es preciso modificar el fuente del buzón.
Ligadura dinámica y colecciones de acoplamiento débil
Por otro lado, la ligadura por parte del proveeror incrementa las posibilidades de que el buzón pueda ser utilizado en otras aplicaciones, porque ya no se mencionan los tipos que podrían cambiar en una nueva aplicación. Y el sistema se vuelve maleable, porque se le pueden agregar nuevos tipos de datos posteriormente, sin afectar al código que ya funciona.
Ligadura dinámica y colecciones de acoplamiento débil
Este beneficio se denomina encapsulamiento. Es la primera y principal de las contribuciones que proporciona el estilo de POO con ligadura dinámica. Un segundo beneficio, llamado herencia, se hace posible cuando el encapsulamiento está disponible. La herencia es una técnica para definir nuevos tipos de datos, describiendo los aspectos en los que cada tipo difiere de algún tipo que existiera con anterioridad.
Implicaciones de los softwares integrados
Lo atractivo de esto es que la industria del Software podría aprovechar algunos de los beneficios que trajo el chip de silicio (CI) a la industria del hardware: La capacidad de los proveedores para suministrar unidades fuertemente encapsuladas de funcionalidad, que están especializadas en aquello para lo que han sido diseñadas, pero que son independientes de cualquier aplicación concreta.
Implicaciones de los softwares integrados
El circuito integrado de silicio es la unidad de reutilizabilidad del hardware que ha contribuido de forma más sobresaliente al auge de productividad del hardware. ¿Podría el concepto de software integrado hacer lo mismo a efectos del software?
Gordon Moore, presidente de Intel Corp., predijo una vez (abril 1965) que el número de componentes que hay en un CI seguiría duplicándose anualmente. La predicción se conoce ahora como la Ley de Moore, y ha sido una regla los últimos años.
Implicaciones de los softwares integrados
Implicaciones de los softwares integrados
Una de sus muchas implicaciones es que, mientras la productividad en el desarrollo del software se ha ido incrementando de manera aritmética, la productividad del hardware ha ido creciendo de manera geométrica. Ciertamente la productividad en el desarrollo de software aumentó mucho desde ensamblador al FORTRAN, de este al C y luego a Lisp
Implicaciones de los softwares integrados
Pero ciertamente, no ha mejorado un millón de veces, que es lo que implicaría la Ley de Moore.
Los objetos están fuertemente acoplados, y, por tanto, son relativamente independientes de su entorno. Pueden ser diseñados, desarrollados, probados y documentados como unidades independientes, sin conocimiento de ninguna aplicación particular, y después se pueden almacenar para ser distribuidos y reutilizados en muchas circunstancias diferentes. Un módulo reutilizable tiene un valor por sí mismo, mientras que el código que depende de una aplicación no tiene valor más allá del que tenga esa aplicación como un todo.
Implicaciones de los softwares integrados
Se puede probar y documentar con unos estándares mucho más estrictos que lo que se ha hecho en el pasado. imagínese lo que esto podría hacer a efectos de fiabilidad del software, o para acallar la objeción habitual: “Tardo menos en desarrollarlo que en atender el código de otra persona”.
Implicaciones de los softwares integrados
Los diseñadores de placas reutilizan de manera habitual el trabajo de los diseñadores de circuitos, que reutilizan el esfuerzo de otros trabajadores de niveles todavía más bajos: fabricantes de obleas, constructores de máscaras, talleres de impresión. Los canales de comunicación (las revistas especializadas) permiten a los proveedores comunicarse con clientes, y los consumidores tienen catálogos, pruebas de rendimiento, y comparaciones para seleccionar entre múltiples proveedores. La impresionante vitalidad del mercado de componentes hardware reutilizables es legendaria.
Implicaciones de los softwares integrados
Una objeción grave, y posiblemente fatal, es que el software se puede copiar (esto es “piratear”) con tanta facilidad, que prácticamente se elimina el incentivo de invertir en construir software para que pueda ser reutilizado.
Implicaciones de los softwares integrados
El esfuerzo necesario para documentar un módulo de tal manera que los demás puedan utilizarlo realmente es considerable, y podría, incluso, exceder al esfuerzo necesario para construir el módulo partiendo de cero.
Implicaciones de los softwares integrados
Reutilizar el código implica confiar en que el código funcione correctamente. Aunque la encapsulación y la herencia son realmente una ayuda en este sentido, no elimina la posibilidad de que las aplicaciones fallen cuando cambie el software del cual dependen
Implicaciones de los softwares integrados
Subyaciendo a estos problemas, y también a otros, está el hecho indudable de que la programación es verdaderamente muy distinta de otros tipos de fabricación de objetos físicos. La idea de las partes intercambiables de Eli Whitney sólo puede reproducirse aproximadamente en una computadora, en la que las oportunidades de interacciones imprevistas son mucho mayores que en medios físicos, como el hierro.
Contacto:
[email protected] [email protected] Abdiel E. Cáceres González Centro de Investigación y de Estudios Avanzados - IPN México D.F., México. 2004