Clases en Swift

Clases en Swift

En este capítulo vamos a ver uno de los conceptos más importantes de la programación orientada a objetos: las clases. Su importancia radica en que todo programador debe diseñar un modelo de datos que soporte el comportamiento que requiere un determinado programa para resolver el problema para el cual es creado y las clases son justamente ideales para ello. En el siguiente capitulo vamos a ver las estructuras que tienen un comportamiento similar a las clases, sobre todo en Swift.

Clases – Definición

Según Wikipedia una clase es “una plantilla para la creación de objetos de datos según un modelo predefinido. Las clases se utilizan para representar entidades o conceptos, como los sustantivos en el lenguaje. Cada clase es un modelo que define un conjunto de variables -el estado, y métodos apropiados para operar con dichos datos -el comportamiento. Cada objeto creado a partir de la clase se denomina instancia de la clase”.

Como bien dice la definición, usamos a las clases para definir un concepto. Dicho concepto forma parte del problema que queremos modelar y por lo tanto, del problema que queremos resolver al crear un programa. Para ello, las clases poseen distintos componentes, entre los que se destacan las propiedades y los métodos. Podemos pensar a las propiedades como las características que poseen las clases mientras que los métodos son las acciones que pueden realizar.

Cada vez que creamos una clase, en realidad estamos creando un nuevo tipo de dato. Es por esto que se dice que este tipo de lenguajes es un lenguaje tipado, por la posibilidad que da al programador de crear nuevos tipos. Todo nombre de una clase debe, por convención, nombrarse con una mayúscula en su primer letra, mientas que las propiedades y métodos deben comenzar con minúscula.

Sintaxis de una clase

Para crear una clase se utiliza la palabra resevada class seguida del nombre de la misma:

[code lang=”Swift”]
class UnaClase {

}
[/code]

Las propiedades son variables o constantes creados dentro del ámbito de una clase y su declaración sigue todas las reglas que vimos en los capítulos anteriores.

[code lang=”Swift”]
class Rectángulo {
let lados = 4
let alto = 5
let ancho = 2
}

let figura = Rectángulo()
print(“la figura contiene \(figura.lados) lados”)

//Devuelve
//la figura contiene 4 lados
[/code]

Como se aprecia en el ejemplo, para poder instanciar una clase basta con declarar una variable o constante y hacer una asignación usando el nombre de esa clase. En este caso, se usan paréntesis sin parámetros porque no se ha especificado que reciba ninguno. La clase Rectángulo posee tres propiedades numéricas con un valor asignado al momento de la creación de la clase.

Se dice que la constante figura representa un objeto o una instancia de la clase Rectángulo. La diferencia entre una clase y un objeto (o instancia) es que la clase simplemente es una definición, un concepto. Sin embargo, al momento de crear el objeto, se asigna un espacio en memoria para poder guardar todos los elementos que este define y por lo tanto, sus propiedades y métodos pueden ser usados.

Gracias a que figura es una instancia (y no una simple definición como lo es Rectángulo), podemos acceder a su propiedad lados e imprimir el resultado dentro de una cadena con la función print.

Inicialización de una clase

Si quisiéramos que las propiedades de la clase obtengan su valor al momento de crear la instancia, debemos utilizar un inicializador (lo que en otros lenguajes orientados a objetos se lo conoce como constructor). Mediante este elemento podremos, en el ejemplo, dar un valor a las propiedades alto y ancho para crear distintos tamaños de rectángulos al momento de crear una instancia.

Se define inicialización como el proceso de preparar la instancia de una clase. En él, podemos dar valores iniciales a las propiedades de la clase como así también ejecutar código para hacer un seteo inicial. Para poder usarlo, debemos usar un inicializador que se identifica por la palabra reservada init.

[code lang=”Swift”]
class Rectángulo {
let lados = 4
var alto:Int
var ancho:Int

init(al:Int, an:Int){
alto = al
ancho = an
}
}

let figura = Rectángulo(al: 3, an: 6)
print(“La figura mide \(figura.ancho) x \(figura.alto)”)

//Devuelve:
//La figura mide 6 x 3
[/code]

En el ejemplo usamos init para pedir un valor inicial para las propiedades alto y ancho. Luego, en el cuerpo del mismo usamos esos parámetros para asignárselos a las propiedades. Como se puede observar en el print, los valores son guardados en las variables correspondientes.

Nota
En el proceso de inicialización, no solamente se puede dar valor de inicio a las variables. Por el contrario, las constantes también pueden ser inicializadas aquí teniendo en cuenta que una vez que termine el proceso de init, las mismas deben quedar todas inicializadas (no pueden haber constantes sin valor) y luego nunca más podrán variar su contenido. Si quedaran constantes sin inicializar, se obtendría un error.

 

En el caso de querer dejar una variable sin inicializar incluso cuando ya finalizó la etapa del init, esa variable deberá ser un opcional, ya que en su ciclo de vida existen etapas donde su valor es nil.

Self

Si bien el ejemplo anterior obtiene perfectamente dos valores y los mismos son usados dentro del cuerpo del init, el nombre de los parámetros no siguen los lineamientos de nomenclatura que promueve Apple. Uno de los puntos más importantes de estos lineamientos es que el nombre de las variables, parámetros, constantes o cualquier otra estructura que usemos debe dar una idea del valor que contiene o del propósito para el cual fue creado.

En este sentido, los nombres utilizados para los parámetros del ejemplo (al y an) no dicen nada, y una persona que use nuestro código (o bien nosotros mismos más adelante) puede no entender de qué se trata. Por eso, sería más conveniente darles nombres más descriptivos, como alto y ancho respectivamente.

Ahora bien, ¿cómo diferenciamos las referencias alto y ancho de los parámetros con respecto al alto y ancho de las propiedades? Es decir, si en el cuerpo del init hiciéramos un “alto + alto”, ¿estaríamos haciendo referencia a la suma de los alto de los parámetros o de las propiedades?.

Para evitar esta confusión existe la palabra reservada self. Con self hacemos referencia a los nombres de las propiedades de la clase. Por lo tanto, dentro de un init en donde el nombre de los parámetros coincide con el de las propiedades, decir self.alto hace referencia al alto de las propiedades, mientras que alto a secas referencia al parámetro.

Dicho esto, el siguiente ejemplo queda mucho más claro en cuanto a los nombres de los parámetros:

[code lang=”Swift”]
class Rectángulo {
let lados = 4
var alto:Int
var ancho:Int

init(alto:Int, ancho:Int){
self.alto = alto
self.ancho = ancho
}
}

let figura = Rectángulo(al: 3, an: 6)
print(“La figura mide \(figura.ancho) x \(figura.alto)”)

//Devuelve:
//La figura mide 6 x 3
[/code]

Métodos de una clase

Los métodos son funciones escritas dentro del cuerpo de una clase que permiten darle un comportamiento a ésta, encapsulando tareas y proveyendo de un comportamiento a la instancia.

Nota
En Swift los métodos no son un componente exclusivo de las clases sino que también se pueden dotar de métodos a las estructuras y enumeraciones, como veremos más adelante.

Para crear un método, basta con crear una función adentro de la clase:

[code lang=”Swift”]
class Rectángulo {
let lados = 4
var alto:Int
var ancho:Int

init(alto:Int, ancho:Int){
self.alto = alto
self.ancho = ancho
}

func incrementarAlto() {
alto += 1;
}
}

figura.incrementarAlto()
print(“La figura mide \(figura.ancho) x \(figura.alto)”)

//Devuelve:
//La figura mide 6 x 4
[/code]

En el ejemplo anterior, creamos un método que nos permite aumentar el alto del rectángulo en 1. Por lo tanto, al invocar a la función incrementarAlto() el valor de la propiedad Alto sube a 4.

Cabe destacar que los métodos siguen todas las reglas que vimos en el capítulo de funciones. Por consiguiente, podemos crear métodos con el mismo nombre pero distinta definición generando sobrecarga o usar etiquetas de argumento, por solo citar dos características:

[code lang=”Swift”]
class Rectángulo {
let lados = 4
var alto:Int
var ancho:Int

init(alto:Int, ancho:Int){
self.alto = alto
self.ancho = ancho
}

func incrementarAlto() {
alto += 1;
}

func incrementarAlto(en valor: Int) {
alto += valor;
}
}

let figura = Rectángulo(alto: 3, ancho: 6)
figura.incrementarAlto(en: 6)
print(“La figura mide \(figura.ancho) x \(figura.alto)”)

//Devuelve:
//La figura mide 6 x 9
[/code]

Clases – Herencia

Se trata de una propiedad que permite que una clase se cree a partir de otra, adoptando las características de la clase madre y agregando nuevas funcionalidades. Es especialmente útil cuando se elabora el diagrama de clases que se va a utilizar para resolver un problema determinado y que, consiguientemente, van a definir el comportamiento de la aplicación. Una clase puede heredar métodos, propiedades y otras características de la otra clase.

La herencia es una de las propiedades que diferencia a las clases de otros tipos, como las estructuras o las enumeraciones. Cuando una clase A hereda de B se dice que B es la súper clase y A la subclase.

Las subclases, por lo tanto, pueden hacer uso de la funcionalidad de la súper clase como si fueran definidos dentro de ella. Asimismo, la subclase puede sobreescribir las versiones de los métodos o propiedades en caso de ser necesario (proceso conocido como overriding).

Clase base

Se define clase base como aquella que no hereda de ninguna otra. Supongamos que tenemos la siguiente clase FiguraGeometrica:

[code lang=”Swift”]
class FiguraGeometrica {
var color:String
var area = 0.0
var perimetro = 0.0

init(color:String) {
self.color = color
}

func calcularArea() {
//No escribimos nada ya que cada figura tiene su propia fórmula
}

func calcularPerimetro() {
//No escribimos nada ya que cada figura tiene su propia fórmula
}

func cambiar(color:String) {
self.color = color
}
}
[/code]

En el ejemplo, la clase FiguraGeometrica define 3 propiedades: color, área y perímetro. El color es asignado en el init mientras que los otros dos son iniciados en 0. Por otro lado, se definen 3 métodos, los cuales dos de ellos no tienen código en su interior debido a que no se disponen de los datos suficientes para poder hacer el cálculo, ya que cada figura geométrica posee su propia fórmula para calcular estas propiedades. Mientras tanto, lo único que sí sabemos a esta altura es que si se desea cambiar el color, simplemente recibiendo un nuevo color ya es suficiente para hacer el cambio.

La primera conclusión que sacamos es que en la clase base debemos incluir aquellas propiedades y métodos que son comunes en todas las clases heredadas.

Al momento de crear la clase FiguraGeometrica definimos todas las características comunes para una figura geométrica arbitraria. Para darle más comportamiento y un contexto más rico, es necesario crear clases más específicas que hereden de ella.

Nota
Para representar colores en una aplicación real se utilizan otras clases en lugar de String, pero para efectos del ejemplo vamos a usarlos como si solo se trataran de cadena de caracteres.

Subclases

Como ya dijimos, al crear una subclase se genera una nueva clase basada en otra, de la cual se heredan sus características. Estas pueden ser usadas como si fueran propias, pueden ser modificadas e incluso pueden agregarse nuevas características.

Para indicar que una subclase hereda de otra, se lo debe indicar al momento de definirla, usando : y el nombre de la súper clase:

[code lang=”Swift”]
class subClase: superClase {

}
[/code]

Podemos tomar la clase Rectángulo usada en los ejemplos anteriores y hacer que herede de FiguraGeometrica:

[code lang=”Swift”]
class Rectángulo:FiguraGeometrica {
let lados = 4
var alto:Int
var ancho:Int

init(color:String, alto:Int, ancho:Int){
self.alto = alto
self.ancho = ancho
super.init(color: color)
}

func incrementarAlto() {
alto += 1;
}

func incrementarAlto(en valor: Int) {
alto += valor;
}
}
[/code]

Un punto a tener en cuenta es que la súper clase tiene un inicializador distinto al de la subclase (espera un color mientras que la subclase espera un alto y un ancho). Por lo tanto, en el init de la clase Rectángulo se debe invocar al init de la súper clase como última sentencia, utilizando la palabra reservada super.

[code lang=”Swift”]
let rectangulo = Rectángulo(color: “Azul”, alto: 9, ancho: 4)
print(“El rectángulo mide \(rectangulo.ancho) x \(rectangulo.alto) y es de color \(rectangulo.color)”)

//Devuelve:
//El rectángulo mide 4 x 9 y es de color Azul
[/code]

Como se aprecia en el ejemplo, la variable rectángulo es una instancia de la clase Rectángulo y si bien la propiedad color pertenece a la súper clase, se utiliza como si hubiera sido definido dentro de la sub clase.

De la misma manera, se pueden crear otras subclases adicionales:

[code lang=”Swift”]
class Círculo:FiguraGeometrica {
var radio: Double

init(color: String, radio:Double) {
self.radio = radio
super.init(color: color)
}
}
[/code]

Nota
Una clase puede tener n cantidad de subclases, pero una subclase solo puede tener una súper clase.

 

Override

En el proceso de override se sobrescriben las funcionalidades de los métodos (o mismo de las propiedades) para proveer una implementación alternativa en la subclase.

[code lang=”Swift”]
class Rectángulo:FiguraGeometrica {
let lados = 4
var alto:Int
var ancho:Int

init(color:String, alto:Int, ancho:Int){
self.alto = alto
self.ancho = ancho
super.init(color: color)
}

func incrementarAlto() {
alto += 1;
}

func incrementarAlto(en valor: Int) {
alto += valor;
}

override func calcularArea() {
area = Double(alto) * Double(ancho)
}

override func calcularPerimetro() {
perimetro = 2 * Double(alto) + 2 * Double(ancho)
}
}

class Círculo:FiguraGeometrica {
var radio: Double

init(color: String, radio:Double) {
self.radio = radio
super.init(color: color)
}

override func calcularArea() {
area = Double.pi * radio * radio
}

override func calcularPerimetro() {
perimetro = 2 * Double.pi * radio
}
}
[/code]

Tanto la clase Rectángulo como Círculo ahora tienen sus propias implementaciones de los métodos calcularArea() y calcularPerimetro(), los cuales difieren entre sí ya que sus fórmulas son distintas. Para sobrescribir la versión de la súper clase se usa la palabra reservada override.

[code lang=”Swift”]
let círculo = Círculo(color: “verde”, radio: 6.43)
círculo.calcularArea()
círculo.calcularPerimetro()
print(“El area del círculo es \(círculo.area) y el perímetro es \(círculo.perimetro)”)

//Devuelve:
//El area del círculo es 129.888834103405 y el perímetro es 40.4008815251647
[/code]

Descarga el archivo playground.
Siguiente capítulo: Estructuras.

Si quieres aprender más sobre que es iOS sigue este enlace con una guía definitiva sobre el mejor Sistema Operativo para móviles.
¿Te ha gustado este artículo?
Clases en Swift Overall rating: ★★★★★ 5 based on 6 reviews
5 1

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *