Programación Orientada a Objetos en Scala

Clases y objetos

En Scala, las clases y los objetos son fundamentales para la programación orientada a objetos. Las clases son plantillas para crear objetos, mientras que los objetos son instancias únicas de una clase. Los objetos pueden contener métodos, variables y otros tipos de definiciones.

				
					// Definición de una clase en Scala
class Persona(var nombre: String, var edad: Int) {
  
  // Método dentro de la clase Persona
  def saludar(): Unit = {
    println(s"Hola, mi nombre es $nombre y tengo $edad años.")
  }
}

// Creación de objetos (instancias) de la clase Persona
val persona1 = new Persona("Juan", 30)
val persona2 = new Persona("María", 25)

// Llamada al método saludar() de cada objeto
persona1.saludar()
persona2.saludar()

				
			

Explicación del Código

  • Definición de Clase (class):

    • En Scala, una clase se define con la palabra clave class. En el ejemplo, Persona es una clase que tiene dos variables miembro (nombre y edad) y un método (saludar()).
  • Variables Miembro (var):

    • Las variables miembro (nombre y edad en este caso) son accesibles y modificables desde fuera de la clase.
  • Método (def):

    • El método saludar() dentro de la clase Persona imprime un mensaje utilizando las variables miembro nombre y edad.
  • Creación de Objetos:

    • Los objetos (instancias) de una clase se crean utilizando la palabra clave new, seguida del nombre de la clase y los parámetros necesarios para inicializar las variables miembro.
  • Llamada a Métodos:

    • Los métodos definidos en una clase pueden ser invocados en cada objeto creado. En el ejemplo, persona1.saludar() y persona2.saludar() llaman al método saludar() para cada objeto Persona.

Constructores y miembros de clase

En Scala, los constructores son métodos especiales utilizados para inicializar objetos de una clase. Pueden ser primarios (definidos junto con la declaración de la clase) o auxiliares (definidos dentro de la misma clase). Los miembros de clase son variables y métodos que pertenecen a cada instancia de la clase y pueden ser públicos, privados o protegidos.

				
					// Definición de una clase con constructor primario
class Persona(var nombre: String, var edad: Int) {
  
  // Variable miembro privada
  private var direccion: String = ""
  
  // Método público para establecer la dirección
  def setDireccion(nuevaDireccion: String): Unit = {
    direccion = nuevaDireccion
  }
  
  // Método público para obtener la dirección
  def getDireccion(): String = {
    direccion
  }
  
  // Método auxiliar (segundo constructor)
  def this(nombre: String) {
    this(nombre, 0)  // Llama al constructor primario
  }
}

// Creación de objetos (instancias) de la clase Persona
val persona1 = new Persona("Juan", 30)
val persona2 = new Persona("María")

// Acceso a variables miembro y métodos de clase
persona1.setDireccion("Calle Principal, 123")
println(s"${persona1.nombre} vive en ${persona1.getDireccion()}")
println(s"${persona2.nombre} tiene ${persona2.edad} años.")

				
			

Explicación del Código

  • Constructor Primario:

    • En Scala, el constructor primario se define junto con la declaración de la clase (class Persona(var nombre: String, var edad: Int) en este caso). Permite inicializar las variables miembro nombre y edad al crear un objeto Persona.
  • Variables Miembro:

    • nombre y edad son variables miembro públicas de la clase Persona, accesibles desde fuera de la clase.
  • Variable Miembro Privada:

    • direccion es una variable miembro privada de la clase Persona. Se accede y modifica a través de métodos públicos (setDireccion y getDireccion).
  • Método Auxiliar:

    • def this(nombre: String) es un método auxiliar dentro de la clase Persona. Permite crear objetos Persona sin especificar la edad, llamando al constructor primario con valores predeterminados (0 para edad).
  • Creación de Objetos:

    • Los objetos (persona1 y persona2) se crean usando los constructores definidos. persona1 se inicializa con nombre y edad, mientras que persona2 usa el constructor auxiliar solo con nombre.
  • Acceso a Métodos y Variables Miembro:

    • Se demuestra cómo acceder y modificar variables miembro (nombre, edad, direccion) y cómo llamar métodos (setDireccion, getDireccion) en objetos Persona.

Herencia y polimorfismo

En Scala, la herencia permite a una clase (subclase o clase derivada) heredar atributos y métodos de otra clase (superclase o clase base). El polimorfismo permite tratar objetos de distintas clases de manera uniforme a través de una interfaz común.

				
					// Definición de la clase base (superclase)
class Animal(var nombre: String) {
  
  // Método de la clase base
  def hacerSonido(): Unit = {
    println("Haciendo sonido genérico...")
  }
}

// Definición de una subclase que hereda de Animal
class Perro(nombre: String) extends Animal(nombre) {
  
  // Método sobreescrito de la clase base
  override def hacerSonido(): Unit = {
    println("Guau guau!")
  }
  
  // Método adicional de la subclase
  def perseguirCola(): Unit = {
    println("Persiguiendo la cola...")
  }
}

// Función para utilizar polimorfismo
def hacerSonidoAnimal(animal: Animal): Unit = {
  animal.hacerSonido()
}

// Creación de objetos y uso de polimorfismo
val animalGenerico = new Animal("Animal")
val miPerro = new Perro("Firulais")

hacerSonidoAnimal(animalGenerico)  // Llama al método de la superclase
hacerSonidoAnimal(miPerro)         // Llama al método sobreescrito de la subclase

// Acceso a métodos específicos de la subclase
miPerro.perseguirCola()

				
			

Explicación del Código

  • Herencia:

    • En Scala, la herencia se define usando la palabra clave extends. class Perro(nombre: String) extends Animal(nombre) indica que Perro es una subclase de Animal, heredando todos los métodos y atributos de la clase base.
  • Método Sobrescrito (override):

    • hacerSonido() es un método de la clase base Animal, que es sobreescrito en la subclase Perro para proporcionar un comportamiento específico para los perros ("Guau guau!").
  • Polimorfismo:

    • hacerSonidoAnimal(animal: Animal): Unit es una función que utiliza polimorfismo. Puede recibir tanto objetos de la clase base (Animal) como de sus subclases (Perro). Dependiendo del tipo de objeto pasado, se ejecutará el método correspondiente (hacerSonido() de Animal o Perro).
  • Acceso a Métodos Específicos de la Subclase:

    • Aunque hacerSonidoAnimal trata a todos los animales de manera uniforme, se puede acceder a métodos específicos de la subclase (perseguirCola() en Perro) cuando se tiene una referencia específica al objeto (miPerro).

Traits y mixins

En Scala, los traits son como interfaces en otros lenguajes de programación, pero pueden contener implementaciones de métodos. Los mixins son clases que mezclan (o combinan) comportamientos de varios traits para reutilizar código en múltiples clases sin herencia múltiple.

Código Completo

				
					// Definición de un trait con métodos abstractos y concretos
trait Cantante {
  
  // Método abstracto
  def cantar(): Unit
  
  // Método concreto
  def presentarse(): Unit = {
    println("Hola, soy un cantante.")
  }
}

// Definición de otro trait
trait Bailarin {
  
  // Método abstracto
  def bailar(): Unit
  
  // Método concreto
  def moverse(): Unit = {
    println("Moviendo el cuerpo.")
  }
}

// Clase que mezcla (o combina) los traits Cantante y Bailarin
class Artista extends Cantante with Bailarin {
  
  // Implementación del método abstracto de Cantante
  def cantar(): Unit = {
    println("La la la...")
  }
  
  // Implementación del método abstracto de Bailarin
  def bailar(): Unit = {
    println("Bailando salsa.")
  }
}

// Creación de un objeto de la clase Artista
val artista = new Artista

// Llamada a métodos proporcionados por los traits
artista.presentarse()
artista.cantar()
artista.moverse()
artista.bailar()

				
			

Explicación del Código

  • Traits en Scala:

    • Los traits son similares a las interfaces en otros lenguajes, pero pueden contener métodos concretos además de métodos abstractos. En el ejemplo, Cantante y Bailarin son traits que definen métodos abstractos (cantar() y bailar()) y métodos concretos (presentarse() y moverse()).
  • Mixins:

    • Artista es una clase que mezcla los traits Cantante y Bailarin usando la palabra clave with. Esto permite que Artista herede y combine comportamientos de múltiples traits sin herencia múltiple directa.
  • Implementación de Métodos Abstractos:

    • En Artista, se deben implementar todos los métodos abstractos definidos en los traits (cantar() y bailar()).
  • Llamadas a Métodos:

    • Se demuestra cómo se puede acceder y utilizar métodos proporcionados por los traits (presentarse(), cantar(), moverse() y bailar()) a través de un objeto Artista.

Case classes y pattern matching

En Scala, las case classes son clases diseñadas específicamente para almacenar datos inmutables. Son ideales para modelar datos simples y no mutables. El pattern matching es una característica poderosa que permite concordar (match) patrones de datos y ejecutar acciones basadas en esos patrones de forma concisa y expresiva.

				
					// Definición de una case class
case class Persona(nombre: String, edad: Int)

// Función que utiliza pattern matching
def verificarEdad(persona: Persona): String = {
  persona match {
    case Persona(n, e) if e < 18 => s"$n es menor de edad."
    case Persona(n, e) if e >= 18 && e < 65 => s"$n es adulto."
    case Persona(n, e) => s"$n es adulto mayor."
  }
}

// Creación de objetos (instancias) de la case class Persona
val persona1 = Persona("Juan", 25)
val persona2 = Persona("María", 10)
val persona3 = Persona("Pedro", 70)

// Aplicación del pattern matching a cada objeto
val mensaje1 = verificarEdad(persona1)
val mensaje2 = verificarEdad(persona2)
val mensaje3 = verificarEdad(persona3)

// Impresión de los resultados
println(mensaje1)
println(mensaje2)
println(mensaje3)

				
			

Explicación del Código

  • Case Classes en Scala:

    • Las case classes son declaradas con la palabra clave case class. Automáticamente proporcionan métodos para la creación de objetos, comparación estructural y acceso a sus campos.
  • Pattern Matching:

    • verificarEdad es una función que utiliza pattern matching sobre el argumento persona. Permite concordar patrones basados en la estructura y valores de la case class Persona. Los patrones case Persona(n, e) if ... permiten concordar diferentes casos basados en la edad de la persona (e).
  • Creación de Objetos y Aplicación del Pattern Matching:

    • Se crean varios objetos Persona y se aplica la función verificarEdad a cada uno de ellos. Dependiendo de la edad de cada persona, se devuelve un mensaje correspondiente.
  • Impresión de Resultados:

    • Los mensajes resultantes de la aplicación del pattern matching se imprimen en la consola.