6. Programación orientada a objetos - 6.1. ¿Por qué los objetos?
Curso: Programación en C# (2015), por Nacho Cabanes
6. Programación orientada a objetos
6.1. ¿Por qué los objetos?
Cuando tenemos que realizar un proyecto grande, será necesario descomponerlo en varios subprogramas, de forma que podamos repartir el trabajo entre varias personas.
Esta descomposición no debe ser arbitraria. Por ejemplo, será deseable que cada bloque tenga unas responsabilidades claras, y que cada bloque no dependa de los detalles internos de otros bloques.
Existen varias formas de descomponer un proyecto, pero posiblemente la más recomendable consiste en tratar de verlo como una serie de "objetos" que colaboran entre sí.
Una forma de "descubrir" los objetos que forman parte de un programa es partir de la descripción del problema, y subrayar los nombres con un color y los verbos con otro color.
Como ejemplo, vamos a dedicar un momento a pensar qué elementos ("objetos") hay en un juego como el clásico Space Invaders. Cuando entramos al juego, aparece una pantalla de bienvenida, que nos recuerda detalles como la cantidad de puntos que obtendremos al "destruir" cada "enemigo":
(Imagen pronto disponible)
Y cuando comenzamos una partida, veremos una pantalla como ésta, que vamos a analizar con más detalle:
(Imagen pronto disponible)
Observando la pantalla anterior, o (preferiblemente) tras jugar algunas partidas, podríamos hacer una primera descripción del juego en lenguaje natural (que posiblemente habría que refinar más adelante, pero que nos servirá como punto de partida)
Nosotros manejamos una "nave", que se puede mover a izquierda y derecha y que puede disparar. Nuestra nave se esconde detrás de "torres defensivas", que se destruyen poco a poco cuando les impactan los disparos. Nos atacan (nos disparan) "enemigos". Además, estos enemigos se mueven de lado a lado, pero no de forma independiente, sino como un "bloque". En concreto, hay tres "tipos" de enemigos, que no se diferencian en su comportamiento, pero sí en su imagen. Tanto nuestro disparo como los de los enemigos desaparecen cuando salen de la pantalla o cuando impactan con algo. Si un disparo del enemigo impacta con nosotros, perderemos una vida; si un disparo nuestro impacta con un enemigo, lo destruye. Además, en ocasiones aparece un "OVNI" en la parte superior de la pantalla, en la parte superior de la pantalla, que se mueve del lado izquierdo al lado derecho y nos permite obtener puntuación extra si le impactamos con un disparo. Igualmente, hay un "marcador", que muestra la puntuación actual (que se irá incrementando) y el récord (mejor puntuación hasta el momento). El marcador se reinicia al comienzo de cada partida. Antes de cada "partida", pasamos por una pantalla de "bienvenida", que muestra una animación que nos informa de cuántos puntos obtenemos al destruir cada tipo de enemigo.
A partir de esa descripción, podemos buscar los nombres (puede ayudar si los subrayamos con un rotulador de marcar), que indicarán los objetos en los que podemos descomponer el problema, y los verbos (con otro rotulador de marcar, en otro color), que indicarán las acciones que puede realizar cada uno de esos objetos.
De la descripción subrayada de este juego concreto podemos extraer los siguientes objetos y las siguientes acciones:
- Nave : mover izquierda, mover derecha, disparar, perder vida
- Torre defensiva: destruir (un fragmento, en ciertas coordenadas)
- Enemigos : mover, disparar, desaparecer
- Ovni : mover
- Bloque (formado por enemigos) : mover
- Disparo : mover, desaparecer
- Marcador : mostrar, reiniciar, incrementar puntos
- Partida (contiene nave, enemigos, torres, ovni)
- Juego (formado por bienvenida y partida)
(En general, esta descomposición no tiene por qué ser única, distintos programadores o analistas pueden llegar a soluciones parcialmente distintas).
Esa serie de objetos, con sus relaciones y sus acciones, se puede expresar mediante un "diagramas de clases", que en nuestro caso podría ser así (simplificado):
(Imagen pronto disponible)
El nombre "diagrama de clases" se debe a que se llama "clase" a un conjunto de objetos que tienen una serie de características comunes. Por ejemplo, un Honda Civic Type-R con matrícula 0001-AAA sería un objeto concreto perteneciente a la clase "Coche".
Algunos de los detalles que se pueden leer de ese diagrama (y que deberían parecerse bastante a la descripción inicial) son:
- El objeto principal de nuestro proyecto se llama "Juego" (el diagrama típicamente se leerá de arriba a abajo).
- El juego contiene una "Bienvenida" y una "Partida" (ese relación de que un objeto "contiene" a otros se indica mediante un rombo en el extremo de la línea que une ambas clases, junto a la clase "contenedora").
- La "Bienvenida" se puede "Lanzar" al comienzo del juego.
- Si así lo elige el jugador, se puede "Lanzar" una "Partida".
- En una partida participan una "Nave", cuatro "Torres" defensivas, un "BloqueDeEnemigos" formado por varios "Enemigos" (que, a su vez, podrían ser de tres tipos distintos, pero no afinaremos tanto por ahora) y un "Ovni".
- El "Ovni" se puede "Mover".
- Una "TorreDefensiva" se puede "Destruir" poco a poco, a partir de un impacto en ciertas coordenadas x,y.
- El "BloqueDeEnemigos" se puede "Mover".
- Cada "Enemigo" individual se puede mover a la derecha, a la izquierda, puede disparar o puede desaparecer (cuando un disparo le acierte).
- El "Disparo" se puede "Mover" y puede "Desaparecer".
- Nuestra "Nave" se puede mover a la derecha, a la izquierda y puede disparar.
- Tanto la "Nave" como las "Torres", los "Enemigos" y el "Ovni" son tipos concretos de "Sprite" (esa relación entre un objeto más genérico y uno más específico se indica con las puntas de flecha, que señalan al objeto más genérico).
- Un "Sprite" es una figura gráfica de las que aparecen en el juego. Cada sprite tendrá detalles (que llamaremos "atributos") como una "imagen" y una posición, dada por sus coordenadas "x" e "y". Será capaz de hacer operaciones (que llamaremos "métodos") como "dibujarse" (aparecer en pantalla) o "moverse" a una nueva posición. Cuando toda esta estructura de clases se convierte en un programa, los atributos se representarán variables, mientras que los "métodos" serán funciones. Los subtipos de sprite "heredarán" las características de esta clase. Por ejemplo, como un Sprite tiene una coordenada X y una Y, también lo tendrá el OVNI, que es una subclase de Sprite. De igual modo, la nave se podrá "Dibujar" en pantalla, porque también es una subclase de Sprite.
- El propio juego también tendrá métodos adicionales, relacionados con la lógica del juego, como "ComprobarTeclas" (para ver qué teclas ha pulsado el usuario), "MoverElementos" (para actualizar el movimiento de los elementos que deban moverse por ellos mismos, como los enemigos o el OVNI), "ComprobarColisiones" (para ver si dos elementos chocan, como un disparo y un enemigo, y actualizar el estado del juego según corresponda), o "DibujarElementos" (para mostrar en pantalla todos los elementos actualizados).
Faltan detalles, pero no es un mal punto de partida. A partir de este diagrama podríamos crear un "esqueleto" de programa que compilase correctamente pero aún "no hiciese nada", y entonces comenzaríamos a repartir trabajo: una persona se podría encargar de crear la pantalla de bienvenida, otra de la lógica del juego, otra del movimiento del bloque de enemigos, otra de las peculiaridades de cada tipo de enemigo, otra del OVNI...
Nosotros no vamos a hacer proyectos tan grandes (al menos, no todavía), pero sí empezaremos a crear proyectos sencillos en los que colaboren varias clases, que permitan sentar las bases para proyectos más complejos, y también entender algunas peculiaridades de los temas que veremos a continuación, como el manejo de ficheros en C#.
Como curiosidad, cabe mencionar que en los proyectos grandes es habitual usar herramientas gráficas que nos ayuden a visualizar las clases y las relaciones que existen entre ellas, como hemos hecho para el Space Invaders. También se puede dibujar directamente en papel para aclararnos las ideas, pero el empleo de herramientas informáticas tiene varias ventajas adicionales:
- Podemos "pinchar y arrastrar" para recolocar las clases, y así conseguir más legibilidad o dejar hueco para nuevas clases que hayamos descubierto que también deberían ser parte del proyecto.
- Algunas de estas herramientas gráficas permiten generar automáticamente un esqueleto del programa, que nosotros rellenaremos después con los detalles de la lógica del programa.
La metodología más extendida actualmente para diseñar estos objetos y sus interacciones (además de otras muchas cosas) se conoce como UML (Unified Modelling Language, lenguaje de modelado unificado). El estándar UML propone distintos tipos de diagramas para representar los posibles "casos de uso" de una aplicación, la secuencia de acciones que se debe seguir, las clases que la van a integrar (que es lo que a nosotros nos interesa en este momento), etc. Disponemos de herramientas gratuitas como ArgoUML, multiplataforma, que permite crear distintos tipos de diagramas UML y que permite generar el código correspondiente al esqueleto de nuestras clases, o Dia, también multiplataforma, pero de uso más genérico (para crear diagramas de cualquier tipo) y que no permite generar código por ella misma pero sí con la ayuda de Dia2code.
En los próximos ejemplos partiremos de una única clase en C#, para ampliar posteriormente esa estructura e ir creando proyectos más complejos.
Actualizado el: 28-01-2015 14:16