Programación Orientada a Objetos (3)

Table of Contents

Referencias

  • Programming in Scala, Martin Odersky:
    • Capítulo 12: Traits
    • Capítulo 30: Actors and Concurrency

Índice

  • Object
  • Traits
  • Actores

Objetos Singleton para crear aplicaciones

  • Hasta el momento, en Scala hemos trabajado directamente en el intérprete o creando scripts (que no han dejado de ser expresiones evaluadas).
  • Para ejecutar un programa en Scala, se debe definir un objeto singleton que contenga el método main.
  • El método main toma un Array[String] como argumento y es de tipo Unit
  • Ejemplo, creamos el fichero Formas.scala (el nombre del fichero debe ser el mismo que el nombre de la aplicación, es decir, del objeto singleton):
    abstract class Figura {
      var nombre: String
      var coordSupIzq : (Int,Int)
      var coordInfDer : (Int,Int)
      def print(): Unit
      def setCoords(c1:(Int,Int), c2:(Int,Int)) = {
        coordSupIzq = c1
        coordInfDer = c2
      }
    }
    
    class Rectangulo(name:String) extends Figura {
      var nombre = name
      var coordSupIzq = (0,0)
      var coordInfDer = (0,0)
      def print():Unit = {
        println("Coord 1: " + coordSupIzq)
        println("Coord 2: " + coordInfDer)
      }
    }
    
    object Formas {
      def main (args: Array[String])= {
         val rec = new Rectangulo("rec1")
         rec.setCoords((5,5),(98,77))
         rec.print
      }  
    }
    
  • Para ejecutarlo:
    > scalac Formas.scala
    > scala Formas
    /Coord 1: (5,5)/
    /Coord 2: (98,77)/
    

El trait Application

  • Scala proporciona un trait llamado scala.Application. Este trait tiene declarado el método main, por lo que si lo extiende un objeto singleton, se puede usar como una aplicación de Scala directamente: se puede compilar y usar como cualquier otra aplicación donde se haya definido el método main dentro de un objeto singleton. Ejemplo:
    object Formas extends Application {
         val rec = new Rectangulo("rec1")
         rec.setCoords((5,5),(98,77))
         rec.print
    }
    
  • Compilación y ejecución:
    > scalac Formas.scala
    > scala Formas
    Coord 1: (5,5)
    Coord 2: (98,77)
    

Traits

  • Un trait encapsula definiciones de métodos y campos, que se pueden reutilizar mezclándolos dentro de clases. A diferencia de la herencia, donde una clase sólo puede heredar de una superclase, una clase puede mezclar cualquier número de traits.
  • Se podría considerar que un trait es como una interfaz en Java con métodos concretos. Pero además los traits pueden declarar campos y mantener estado. Un trait define un nuevo tipo. Se consideran como interfaces de Java enriquecidas.
  • Se definen como una clase sustituyendo la palabra clave class por trait.
    trait Rectangular {
       def coordSupIzq: (Int,Int)
       def coordInfDer: (Int,Int)
       def izq = coordSupIzq._1
       def der = coordInfDer._2
       def ancho = der - izq 
    }
    
  • Para que una clase utilice (mezcle) un trait, se puede poner de dos formas:
  • Si se quiere mezclar un trait en un clase que explícitamente extiende una superclase, se usa extends para indicar la superclase y se utiliza with para mezclar en el trait.
    class Rectangulo extends Figura with Rectangular {
     ...
    }
    
  • Si se utiliza extends, significa que la clase hereda implícitamente de la superclase AnyRef y la mezcla con el trait, heredando sus métodos:
    class Rectangulo extends Rectangular {
     ...
    }
    

Ejemplo completo de figuras geométricas

abstract class Figura {
  val nombre: String
  def print(): Unit
  def DrawBoundingBox():Unit
}

trait Rectangular {
   def coordSupIzq: (Int,Int)
   def coordInfDer: (Int,Int)
   def izq = coordSupIzq._1
   def der = coordInfDer._2
   def ancho = der - izq 
}

class Rectangulo(val nombre:String, var coordSupIzq:(Int,Int), var coordInfDer:(Int,Int)) 
 extends Figura with Rectangular {
  def print():Unit = {
    println("Rectangulo: "+ nombre)
    println("Coord 1: " + coordSupIzq)
    println("Coord 2: " + coordInfDer)
  }
  def DrawBoundingBox():Unit = {
     print
     println("BoundingBox: ") 
     println("Punto Sup Izq: " + coordSupIzq)
     println("Punto Inf Der: " + coordInfDer)
  }
}

class Circulo(val nombre:String, var centro:(Int,Int), var radio:Int) extends Figura {
  def print():Unit = {
    println("Circulo: " + nombre)
    println("Centro: " + centro)
    println("Radio: " + radio)
  }
  def DrawBoundingBox():Unit = {
     print
     println("BoundingBox: ")
     println("Punto Sup Izq:" + (centro._1-radio, centro._2-radio))
     println("Punto Inf Der: "+ (centro._1+radio, centro._2+radio))
  }
}

object Geom extends Application {
     val rec = new Rectangulo("rec1",(5,5),(45,67))
     val cir = new Circulo("cir1",(20,20),12)
     rec.izq
     rec.der
     rec.ancho
     rec.DrawBoundingBox
     cir.DrawBoundingBox
}
  • Lo grabamos en el fichero Geom.scala y lo probamos:
    >scalac Geom.scala
    >scala Geom
    Rectangulo: rec1
    Coord 1: (5,5)
    Coord 2: (45,67)
    BoundingBox: 
    Punto Sup Izq: (5,5)
    Punto Inf Der: (45,67)
    Circulo: cir1
    Centro: (20,20)
    Radio: 12
    BoundingBox: 
    Punto Sup Izq:(8,8)
    Punto Inf Der: (32,32)
    

Ejemplo queue

import scala.collection.mutable.ListBuffer

abstract class IntQueue {
  def get(): Int
  def put(x: Int)
}

class BasicIntQueue extends IntQueue {
  private val buf = new ListBuffer[Int]
  def get() = buf.remove(0)
  def put(x:Int) = buf += x
}
  • Lo probamos:
    scala> val cola = new BasicIntQueue
    scala> cola.put(10)
    scala> cola.put(20)
    scala> cola.get()
    res2: Int = 10
    scala> cola.get()
    res3: Int = 20
    

Ejemplo queue con traits

  • Ahora vamos a usar traits para modificar su comportamiento (vamos a hacer modificaciones apilables):
    trait Doubling extends IntQueue {
      abstract override def put(x:Int) = super.put(2 * x)
    }
    
  • El trait Doubling hace dos cosas muy interesantes:
  • Declara como superclase IntQueue. Esta declaración hace que el trait sólo pueda ser mezclado en una clase que también extienda IntQueue.
  • El trait tiene una llamada super a un método declarado abstracto. En una clase normal sería ilegal, pero es posible en un trait; los modificadores abstract override (sólo admitidos en los métodos de los traits) indican que el trait se debe mezclar en alguna clase que tenga la definición concreta del método.
    class MiCola extends BasicIntQueue with Doubling
    
  • Lo probamos:
    val cola = new MiCola
    cola.put(10)
    cola.get()
    /Int = 20/
    
  • Fíjate que la clase MiCola no define código nuevo, sólo mezcla una clase con un trait. En esta situación, se podría hacer el new directamente:
    val cola = new BasicIntQueue with Doubling
    cola.put(10)
    cola.get()
    /res2: Int = 20/
    
  • Añadimos dos nuevos traits:
    trait Incrementing extends IntQueue {
      abstract override def put(x:Int) = super.put(x + 1)
    }
    trait Filtering extends IntQueue {
      abstract override def put(x:Int) = if (x >= 0) super.put(x)
    }
    
  • Ahora, podemos elegir aquellas modificaciones que queramos para una determinada cola. Por ejemplo, definimos una cola que filtra números negativos y añade uno a todos los números que almacena:
    val cola = new BasicIntQueue with Incrementing with Filtering
    cola.put(-1)
    cola.put(0)
    cola.put(1)
    cola.get()
    /res2: Int = 1/
    cola.get()
    /res3: Int = 2/
    

Mixins

  • El orden de los mixins (cuando un trait se mezcla con una clase, se le puede llamar mixin) es significante. Van de derecha a izquierda. Cuando se llama a un método de una clase con mixins, se llama primero al método del trait más a la derecha. Si ese método a su vez llama a super, invocará al método del siguiente trait a su izquierda.
  • En el ejemplo anterior, primero se invoca al put de Filtering, de forma que se eliminan los enteros negativos y se añade uno a aquellos que no se han eliminado. Si invertimos el orden primero se incrementarán los enteros y entonces los enteros que todavía son negativos se descartarán:
    val cola = new BasicIntQueue with Filtering with Incrementing
    cola.put(-1)
    cola.put(0)
    cola.put(1)
    cola.get()
    /Int = 0/
    cola.get()
    /res3: Int = 1/
    cola.get()
    /res3: Int = 2/
    

Concurrencia en Java

  • Concurrencia: sucede cuando distintos procesos se ejecutan simultáneamente y pueden interactuar unos con otros.
  • El modelo de Java para manejo de concurrencia está basado en threads (hilos de ejecución), mediante el bloqueo de datos compartidos.
  • Para usar este modelo, el programador decide qué datos se compartirán por los threads y los marca como synchronized
  • El mecanismo de bloqueo asegura que sólo un thread en un momento determinado entra en las secciones sincronizadas y tiene acceso a los datos compartidos.
  • Complicado crear aplicaciones robustas multi-thread.

Actores y paso de mensajes

  • Scala proporciona una alternativa basada en un modelo de paso de mensajes, más fácil de programar ya que no hay datos compartidos (y por tanto no tenemos el problema de los interbloqueos ni de las condiciones de carrera).
  • Un actor es una entidad similar a un thread que dispone de un buzón para recibir mensajes.
  • Para implementar un actor, se debe extender la clase scala.actors.Actor e implementar el método act.

Ejemplo básico de un actor

import scala.actors._

class SillyActor extends Actor {
  def act() {
    for (i <- 1 to 5) {
      println("Estoy actuando!")
      Thread.sleep(1000)
    }
  }
} 
  • Activamos un actor invocando al método start proporcionado por la clase Actor
  • Se activa un hilo (thread) que se ejecuta en paralelo al shell
    scala> val actor = new SillyActor
    scala> actor.start()
    scala> Estoy actuando!
    Estoy actuando!
    Estoy actuando!
    Estoy actuando!
    Estoy actuando!
    
  • La ejecución de los actores es independiente de unos a otros también. Ejemplo:
    import scala.actors._
    
    class SeriousActor extends Actor {
      def act() {
        for (i <- 1 to 5) {
          println("Ser o no ser.")
          Thread.sleep(1000)
        }
      }
    } 
    
  • Ejecutamos actores de ambas clases al mismo tiempo:
    var a = new SillyActor
    var b = new SeriousActor
    
    a.start; b.start
    scala> Estoy actuando!
    Ser o no ser.
    Estoy actuando!
    Ser o no ser.
    Estoy actuando!
    Ser o no ser.
    Estoy actuando!
    Ser o no ser.
    Estoy actuando!
    Ser o no ser.
    

Creando actores con actor

  • Se pueden crear objetos actores sin haber definido ninguna clase con la función actor a la que se le pasa el código a ejecutar:
    import scala.actors.Actor._
    
    val seriousActor2 = actor {
      for (i <- 1 to 5) {
        println("Esa es la cuestión.")
        Thread.sleep(1000)
      }
    }
    

Comunicación entre actores: mensajes

  • Ya hemos creado actores que se ejecutan independientemente unos de otros.
  • ¿Cómo pueden trabajar juntos? ¿Cómo se pueden comunicar sin compartir memoria y sin bloqueos?
  • Los actores se comunican enviándose mensajes.
  • Se puede enviar un mensaje usando el método !
  • Para recibir los mensajes los actores deben llamar a la función parcial receive (implementa algo similar a un match)
  • La llamada a receive deja el actor a la espera del siguiente mensaje que llega a su buzón:
    import scala.actors.Actor._
    
    val echoActor = actor {
      while(true) {
        receive {
          case msg => println("Mensaje recibido: "+ msg)
        }
      }
    }
    
    echoActor ! "hola"
    

Los mensajes son asíncronos

  • Cuando un actor envía un mensaje no se queda esperando el resultado, sino que continúa su proceso
  • Esto se denomina comunicación asíncrona
  • Una invocación a una función o a un método es síncrona: el flujo de ejecución del objeto llamador se detiene hasta que la función devuelve un resultado
  • Los mensajes se almacenan en una cola en el actor receptor
  • El receptor no interrumpe la ejecución de su proceso; sino que lee los mensajes cuando ejecuta un receive

Mensajes con tipo

  • Un actor puede esperar mensajes de un tipo en concreto, especificándolo en el tipo del case.
  • Por ejemplo, un actor que sólo espera mensajes de tipo Int:
import scala.actors.Actor._

val intActor = actor {
  receive {
      case x: Int => println("Tengo un Int: "+ x)
  }
}
  • Si recibe mensajes de otro tipo los ignora
    scala> intActor ! "hola"
    scala> intActor ! scala.math.Pi
    scala> intActor ! 12
    scala> Tengo un Int: 12
    

Ejemplo completo

  • Definimos dos actores que intercambian una serie de mensajes y luego finalizan. El primer actor envía mensajes ping al segundo actor, que como respuesta envía mensajes pong (por cada mensaje ping recibido, un mensaje pong).
  • El actor ping comienza el intercambio de mensajes. Cuando el actor ping ha enviado un determinado número de mensajes, envía un mensaje Stop al actor pong.
  • Ambos actores los definimos como subclases de Actor porque queremos pasarles argumentos a sus constructores.
  • Para terminar la ejecución de un actor invocamos a exit().
import scala.actors.Actor
import scala.actors.Actor._

class Ping(count: Int, pong: Actor) extends Actor {
  def act() {
    var pingsQuedan = count - 1
    println("Ping envía Ping")
    pong ! "Ping"
    while (true) {
      receive {
        case "Pong" =>
          if (pingsQuedan > 0) {
            println("Ping envía Ping")
            pong ! "Ping"
            pingsQuedan -= 1
          } else {
            println("Ping envía Stop")
            pong ! "Stop"
            println("Ping stops")
            exit()
          }
      }
    }
  }
}

class Pong extends Actor {
  def act() {
    while (true) {
      receive {
        case "Ping" =>
          println("Pong envía Pong")
          sender ! "Pong"
        case "Stop" =>
          println("Pong stops")
          exit()
      }
    }
  }
}

object PingPong extends Application {
  val pong = new Pong
  val ping = new Ping(5, pong)
  ping.start
  pong.start
}
  • Lo probamos:
    > scalac PingPong.scala
    > scala PingPong
    Pong: Pong
    Ping: Ping
    Pong: Pong
    Ping: Ping
    Pong: Pong
    Ping: Ping
    Pong: Pong
    Ping: Ping
    Pong: Pong
    Ping: stop
    Pong: stop
    




Lenguajes y Paradigmas de Programación
Curso 2010-2011
Departamento de Ciencia de la Computación e Inteligencia Artificial
Universidad de Alicante

Sitio web realizado con org-mode y el estilo CSS del proyecto Worg

Validate XHTML 1.0