Programación orientada a objetos

A la hora de hacer un programa, siempre es una buena idea empezar pensando qué problema resolverá. Los objetos nos permiten una estructura del código basada en el problema que se solucionará, de modo que podemos emplear el tiempo en pensar en el problema en lugar de empantanarnos en la mecánica de la escritura de código. Al usar objetos correctamente, el código a escribir será intuitivo, y fácil de leer y modificar.

Clases y métodos

Pongamos como ejemplo la navegación de un GPS. La clase que emplearemos se llamará Navigator.

Se introduciría nuestra localización actual mediante el método SetCurrentLocation() , la cual se le pasaría como un parámetro tipo string. Del mismo modo, empleando SetDestination() , se introduciría la localización objetivo. Para obtener la ruta entre las dos localizaciones se llamaría al método GetRoute() , que calcularía la ruta y la devolvería como un string. Para evitar pasar por algún lugar en concreto en nuestra ruta se emplearía ModifyRouteToAvoid() , el cual modificaría nuestra ruta original; a continuación obtendríamos la nueva ruta usando de nuevo GetRoute() .

NavigatorVS

class Navigator {
public void SetCurrentLocation(string locationName) { ... }
public void SetDestination(string destinationName) { ... }
public void ModifyRouteToAvoid(string streetName) { ... }
public string GetRoute() { ... }
}

Algunos métodos devuelven algún valor al ejecutarse, dicho valor será de un tipo en concreto. Por ejemplo, en el método GetRoute() se devuelve un valor de tipo string al finalizar su ejecución.

La sentencia return le dice al método que finalice inmediatamente su ejecución. Si el método no devuelve ningún valor (void), la sentencia return no necesitará ningún valor («return;«) y no es necesario incluirla dentro del método. Si por el contrario el método debe devolver un valor, entonces tiene que usarse return.

public int MultiplyTwoNumbers(int firstNumber, int secondNumber) {
int result = firstNumber * secondNumber;
return result;
}

Este método multiplica los dos números que se le pasan como argumentos (tipo int) y devuelve el resultado (tipo int). Si llamamos al método podremos asignarle una variable tipo int que se igualará con el valor result devuelto.

int myResult = MultiplyTwoNumbers(3, 5);

En resumen, una clase tiene métodos que contienen sentencias que realizan acciones. Los métodos pueden devolver un valor de un tipo en concreto, en cuyo caso deben incluir la sentencia return junto a una variable del tipo del valor devuelto por el método. En cuanto la sentencia return se ejecuta, el programa regresará de vuelta al punto donde ejecutó el método. Aunque el método no devuelva ningún valor, puede usarse return igualmente para finalizar el método en cualquier punto.

A modo de ejemplo, se creará un script llamado Talker.cs que contendrá la clase Talker. En esta clase incluiremos un método BlahBlahBlah( ) al cual se le pasará un string y un int. El método repetirá el string el número de veces indicado por la variable int añadiendo un salto de línea al final de cada repetición y almacenándolo todo en un nuevo string. Este nuevo string será asignado a un texto mostrado por pantalla en Unity mediante una interfaz 2D. El script estará asociado al texto en cuestión. El método devolverá la longitud del nuevo string. Al final se añadirá al texto una línea indicando la longitud del mensaje (sin incluir esta línea). El número máximo de veces que se replicará el string estará limitado a 10, para cualquier número mayor se tomará como valor 10. Los valores de los parámetros se introducirán desde Inspector.

Hello!

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class Talker : MonoBehaviour
{
[SerializeField] private Text text;
public int nTimes;
public string say;
int length;
string lengthMessage;

int BlahBlahBlah(string thingToSay, int numberOfTimes)
{
string finalString = "";
for (int count = 0; count < numberOfTimes; count++)
{
finalString = finalString + thingToSay + "\n";
}
text.text = finalString;
return finalString.Length;
}

void Update()
{
if (nTimes > 10)
{
nTimes = 10;
}
length = BlahBlahBlah(say, nTimes);
lengthMessage = "\n" + "La longitud del mensaje es " + length.ToString();
text.text = text.text + lengthMessage;
}
}

Código y estructura

A la hora de escribir el código, es importante que sea fácil de entender. Es útil añadir comentarios, pero más útil es elegir nombres intuitivos para los métodos, clases y variables.

Podríamos haber acortado los nombres de la variables. Por ejemplo, numberOfTimes podría llamarse nT para acortar el código que escribimos, pero, ¿podría alguien que ve el código por primera vez saber a qué se refiere esa variable? A simple vista sería imposible, sin embargo, numberOfTimes nos indicará solo con leerlo que la información que contiene hacer referencia al número de veces que algo debe suceder. Más importante que hacer un código compacto es hacer un código fácilmente entendible. Del mismo modo podríamos haber puesto el código del método BlahBlahBlah() dentro de Update() directamente, pero creando el método sabemos que esa parte del código hará una cosa en concreto y nuestro programa quedará mejor estructurado.

Podemos hacer nuestro código más fácil de leer y escribir pensando en el problema que se supone que debe solucionar. Si escogemos nombres que tengan sentido para alguien que entiende el problema, el código será más fácil de descifrar y desarrollar.

También es útil realizar un diagrama de la clase que vamos a crear (anteriormente se ha mostrado un diagrama de la clase Navigator con sus métodos). De este modo, podemos diseñar qué métodos tendrá nuestra clase y qué función cumplirán para tener más claro el cómo proceder. Sabremos de una manera general cual será la estructura de la clase antes de empezar con su creación. En un post anterior se expica con detalle cómo crear estos diagramas. Ahora simplemente se mostrará un ejemplo:

class CandyController {CandyController
public void DoMaintenanceTests() {
...
if (IsNougatTooHot() == true) {
DoCICSVentProcedure();
}
...
}
public void DoCICSVentProcedure() ...
public boolean IsNougatTooHot() ...
}

Objetos

Volviendo al ejemplo del GPS, podríamos crear dos nuevas clases que fuesen exactamente iguales a Navigator para tener así un total de tres posibles rutas. Las tres clases serán idénticas, por lo que se comportarán del mismo modo y si queremos modificar una tendremos que modificar las tres. Además, replicar el mismo código varias veces no es muy eficiente.

Cuando queramos trabajar con varias cosas similares (como tener varias rutas) usaremos los objetos en C#. De este modo sólo tendremos que programar una clase y la usaremos tantas veces como queramos sin necesidad de replicar el código en nuevas clases del mismo tipo.

Para crear un nuevo objeto únicamente tendremos que usar la palabra clave new y el nombre de la clase. Al usar new para crear un objeto, se reservará memoria para él y se almacenará en dicha memoria. La parte de la memoria donde se almacenan los objetos se conoce como heap. A medida que se crean objetos se reserva la memoria correspondiente en heap y se rellena con esos objetos.

Navigator navigator1 = new Navigator();
navigator1.SetDestination("Fifth Ave & Penn Ave");
string route;
route = navigator1.GetRoute();

El objeto que creamos tendrá los mismos métodos que la clase a la que corresponde. La clase vendría a ser como los planos de los objetos que puedes crear a partir de ella. Pueden crearse todos los objetos que queramos.

Navigator_objects

Cuando creamos un objeto de una clase, se le llama instancia de esa clase. Todos tendrán sus propios métodos y propiedades, pero se comportarán del mismo modo. Si quisiéramos crear tres rutas en nuestro GPS, podríamos crear tres objetos. Asignarle a cada uno la misma localización origen y destino y añadir un lugar a evitar en cada uno de los objetos. Cada objeto tendría así una ruta distinta, pero habiendo empleado los mismos métodos. Y lo más importante, sólo es necesario escribir el código de la clase Navigator una única vez.

Cuando añadimos a un método la palabra clave static, nos permite usar sus métodos sin necesidad de crear una instancia. De modo que si tuviesemos

class Talker
{
static int BlahBlahBlah(string thingToSay, int numberOfTimes)

...

podríamos llamar al método sin necesidad de crear una instancia de la clase Talker

Talker.BlahBlahBlah("Hello hello hello", 5);

Se comportarán igual los métodos de instancias y los de clase (static).

Se puede marcar toda la clase con static, de manera que todos sus métodos tendrán que  serlo también. Si intentamos añadir un método no estático no compilará.

En el ejemplo del GPS no podemos hacer static los métodos con los que trabaja, ya que cada objeto tiene que tener los datos correspondientes a su propia ruta.

Fields

Los métodos nos indican lo que un objeto puede hacer, sin embargo lo que el objeto sabe está almacenado en los campos (fields). Si tomamos de nuevo el ejemplo de Navigator, cada uno de sus objetos sabrá la dirección de destino cuando usemos SetDestination(). Lo mismo para la posición actual y la ruta, única en cada instancia. Esta información se almacena en los campos de la clase.

Navigator_fields (1)

Si el primer objeto se llamase navigator1, podríamos acceder a la información de sus campos o incluso modificarlos (siempre que fuesen públicos) del siguiente modo:

Navigator navigator1 = new Navigator();
navigator1.currentLocation = "Calle falsa 123"; // Para cambiar su valor.
string destino = navigator1.destination; // Para obtener su valor.

Construyendo una clase

Es hora de aplicar todo lo aprendido hasta ahora. Crearemos una clase Guy y crearemos dos instancias (Joe y Bob). La clase constará de dos campos: Name (nombre del objeto) y Cash (efectivo del objeto). Tendrá además dos métodos: GiveCash() (al llamarlo hará que el objeto de parte de su dinero) y ReceiveCash() (para recibir dinero). Como veis todos los nombres empleados (clase, campos, métodos) son intuitivos para el problema que afrontaremos.

Cuando creemos las dos instancias, los objetos serán almacenados en heap. A continuación se asignarán los valores de los campos  NameCash de cada objeto (cada uno tendrá los suyos propios).

Los métodos seguirán esta forma:

bob.ReceiveCash(25);

Esto haría que el Cash de Bob aumente en  25.

El diagrama de la clase será tal que así:

Guy

El código de la clase será el siguiente:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class Guy : MonoBehaviour
{
public string Name; // Estos son los campos de la clase
public int Cash;
public int GiveCash(int amount) // GiveCash() recibirá como parámetro la cantidad que se dará
{
if (amount <= Cash && amount > 0) // Se dará el dinero siempre que sea mayor que 0 y
{                                 // menor que la cantidad total que posea el sujeto.
Cash -= amount;
return amount;
}
else   // Y si no, se mostrará por consola que no se dispone de esa cantidad.
{
Debug.Log("I don’t have enough cash to give you " + amount + ", " + Name + " says...");
return 0;
}
}
public int ReceiveCash(int amount) // ReceiveCash() recibirá como parámetro la cantidad que se recibirá
{
if (amount > 0) // La cantidad recibida debe ser mayor que 0.
{
Cash += amount;
return amount;
}
else   // Y si no, se mostrará por consola que no es una cantidad aceptable.
{
Debug.Log(amount + " isn’t an amount I’ll take" + ", " + Name + " says...");
return 0;
}
}
}

Ahora para trabajar con los dos objetos de la clase Guy, crearemos la clase JoeAndBob que asociaremos al objeto vacío Joe&Bob. En ella crearemos las dos instancias de los objetos (Joe y Bob) e inicializaremos los valores de Cash de Joe a 50 y los de Bob a 100, además de sus campos Name. Añadiremos además int bank, que indicará los fondos de un banco, y se inicializará a 100. Crearemos una interfaz en Unity que mostrará la cantidad de dinero de Joe, Bob y el banco. También habrá dos botones. El primero hará que el banco le de a Joe 10€, llamando a la función GiveToJoe(), y el segundo hará que Bob le de al banco 5€, llamando a la función ReceiveFromBob(). En ambos casos se comprobará que la cantidad sea correcta.

givetojoe

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class JoeAndBob : MonoBehaviour
{
Guy joe = new Guy();
Guy bob = new Guy();
int bank;
public Text jo;
public Text bo;
public Text ba;

void Start()
{
joe.Name = "Joe";
joe.Cash = 50;
bob.Name = "Bob";
bob.Cash = 100;
bank = 100;
}


void Update()
{
jo.text = "Joe has " + joe.Cash + "€.";
bo.text = "Bob has " + bob.Cash + "€.";
ba.text = "The bank has " + bank + "€.";
}

public void GiveToJoe(int a)
{
if (bank >= a && a > 0)
{
bank -= a;
joe.ReceiveCash(a);
}
}


public void ReceiveFromBob(int b)
{
if (bob.Cash >= b && b > 0)
{
bank += b;
bob.GiveCash(b);
}
}
}

A la hora de inicializar objetos podría incluirse el valor de los campos del siguiente modo:

Guy joe = new Guy() { Cash = 50, Name = "Joe" };

De este modo se ahorrarían un par de líneas de código.

Deja un comentario