Cursos‎ > ‎miercoles-Objetos‎ > ‎

Clase 6 - Responsabilidad y Delegacion

Hoy hablamos de:

- Diagramas de clases

    Hablamos de como dibujar una clase, con su nombre, atributos y métodos.
    Hablamos de como escribir un método de clase y una variable de clase (poniéndoles una '(C)' al lado)
    Hablamos de como relacionar las clases.  Son importantes las flechas y la cardinalidad.
    ¿Qué pasa si hay una cajita sin métodos? Quiere decir que las instancias de esa clase no entienden ningun mensaje.... Eso nos indica algo malo.

- Responsabilidad y delegación, con el ejercicio de trenes y vagones:

¿Qué objetos tienen que tener qué responsabilidad?  Necesitabamos obtener la cantidad de pasajeros para una formación determinada.  Para ello, le dimos la responsabilidad de darnos dicho número a las formaciones.  En un workspace:

formacion cantidadDePasajeros

¿Por qué a las formaciones y no a los depósitos o a la boletería?  La formaciones no hablan...

Porque nosotros estamos haciendo un modelo... La formación que programamos no necesariamente es la formación de posta.  Le damos la responsabilidad a las formaciones porque suena natural hacerlo así.  Si queremos saber la cantidad de pasajeros de una formación, se lo preguntamos a la formación.

Entonces empezamos a escribir el método que resuelva eso:

Formacion>> cantidadDePasajeros
  ^( self vagones collect: [:vagon | vagon cantidadDePasajeros] ) sum.
 
Decidimos tener toooodos los vagones en una sola colección.  Eso nos obliga a tratarlos todos polimorficamente, sean de carga o de pasajeros.

Ahora necesitamos modelar a los vagones, porque tienen que entender el mensaje cantidadDePasajeros.  Y tenemos varios tipos de vagones: de pasajeros y de carga.

VagonDePasajeros>> cantidadDePasajeros
    ^metrosDeAncho <= 2.5     ifTrue: [ metrosDeLargo * 8 ]
                            ifFalse: [ metrosDeLargo * 10 ]

VagonDeCarga>> cantidadDePasajeros
    "Un vagon de carga no lleva pasajeros, entiende este mensaje para ser polimorfico con los vagones de pasajeros"
    ^0
   
--------------
Ahora completemos el  workspace:

formacionCorta := Formacion new.
vagonPasajeros1 := VagonDePasajeros new.
vagonPasajeros1 metrosDeAncho: 2.
vagonPasajeros1 metrosDeLargo: 10.

vagonPasajeros2 := VagonDePasajeros new.
vagonPasajeros1 metrosDeAncho: 3.
vagonPasajeros1 metrosDeLargo: 10.

vagonCarga := VagonDeCarga new.

formacionCorta agregaVagon: vagonPasajeros1.
formacionCorta agregaVagon: vagonPasajeros2.
formacionCorta agregaVagon: vagonCarga.

---------------------

Hay algo feo aca... :S.  Cada vez que creamos un vagon de pasajeros, nos tenemos que acordar de agregarle sus metros de ancho y sus metros de largo!  Fua, si tenemos que crear 30... nos vamos a equivocar seguro.  Estaría reee bueno tener un objeto al que pueda decirle "che, dame un vagon de carga de 2 de ancho y 10 de largo" onda:

objetoQueMeVaAConstruirVagones dameUnNuevoVagonConAncho: 2 yLargo: 10

¿Pero qué objeto?
Y.. hasta ahora el que tenia la responsabilidad de crearme los vagones de pasajeros era la clase VagonDePasajeros.  Demosle entonces la responsabilidad nueva también!! Y así reemplazamos

vagonPasajeros1 := VagonDePasajeros dameUnNuevoVagonConAncho: 2 yLargo: 10
vagonPasajeros2 := VagonDePasajeros dameUnNuevoVagonConAncho: 3 yLargo: 10

¿Y ese método como se escribe en smalltalk, donde lo meto?
Clase VagonDePasajeros >> dameUnNuevoVagonConAncho: ancho yLargo: largo
    | vagon |
    vagon :=self new.
     vagon ancho: ancho.
    vagon largo: largo.
    ^vagon
   
Ese método que escribimos en la parte de "clase", que se va a ejecutar cuando le mandemos un mensaje a la clase, se llama método de clase.

---------------------

Ahora encaremos el segundo punto.  Saber la cantidad de vagones livianos de una formación.

¿A quien le damos esa responsabilidad? Y... suena lógico dársela a la formación, porque es ella quien conoce a los vagones.  Esta bueno ver como ahora la responsabilidad surgió a partir de las responsabilidades que ya tenían nuestros objetos.  Como una formación ya conoce a sus vagones, entonces le damos la responsabilidad de filtrar los livianos a ella.  Así también respetamos el encapsulamiento, haciendo que la única responsable de trabajar con la coleccion de vagones de una formación sea esa misma formación.

Ahora aca tenemos 2 variantes:

1)
Formacion>> cantidadVagonesLivianos
    ^( vagones select:[:v | v pesoMaximo <= 2500 ] ) size.

2)
Formacion>> cantidadVagonesLivianos
    ^( vagones select:[:v | v esLiviano ] ) size.
   
Recalcamos ambas versiones, porque es necesario contar que la primera tiene varios problemas:  no respeta en encapsulamiento, está tomando responsabilidades que le pertenecen a los vagones, me impide usar polimorfismo (si tuviera vagones que calculan la liviandad de distinta manera), y fomenta la repetición de código.

En cambio, en la segunda es más sencilla definir la liviandad distinta para cada tipo de vagón, sin que nos interese como se decide eso.
Entonces:

Version 1) :(
Version 2) :D

Ahora pasemos a escribir el método esLiviano en los vagones:

VagonPasajeros>>esLiviano
    ^self pesoMaximo <= 2500
   
    >>pesoMaximo
        ^self cantidadTotalPasajeros * 80
   
VagonCarga>>esLiviano
    ^self pesoMaximo <= 2500
   
    >>pesoMaximo
        ^self cargaMaxima + 160
       
PEROOOOOO, ¡¡¡Estamos repitiendo código!!!  Lamentablemente, les vamos a pedir que compren eso hasta la semana que viene.  Ahí vamos a resolver este problema con una herramienta nueva.  Aunque si quieren, pueden pensar como se resolvía ese problema con el object browser, con clones y prototipos (porque lo que vamos a ver la semana que viene tiene una mecánica parecida).


Ahora que tenemos resuelto el punto 2, encontramos que hay un par de números mágicos.  "Mágicos" porque no sabemos de donde provienen.  Por ejemplo el 2500, el 160, el 80.  Ok, en el enunciado lo dice, pero no en el código...  Podemos buscar de reemplazarlos por algo más feliz.

Podríamos usar una variable que contenga esos valores.  Por ejemplo:

- limiteDeLiviandad para el 2500
- pesoPersona para el 80
- el 160 podríamos reemplazarlo por (pesoPersona * 2)

Pero si son variables de instancia, me debería ocupar de cargarlas en cada instancia, cosa que puede ser engorroso...
Antes, cuando teníamos código repétido, lo subíamos a la clase.  ¿Por qué ahora que tenemos variables con valores repetidos no lo subimos a la clase también?

Ese concepto de tener una variable en la clase, vamos a llamarlo variable de clase, y tiene la particularidad que es una variable compartida por todas las instancias.
¿Quién tiene la responsabilidad de setear esa variable? La clase... que lo haga una instancia es medio chancho.  Acuerdensé del encapsulamiento.  Con las clases también juega.  ¿Y como lo hace una clase?  A través de un método de clase :).

Cosas a tener en cuenta:
    -si asigno la variable de clase, cambia la referencia para todas las instancias.
    -las variables de clase empiezan en mayúsculas.
    -las instancias pueden acceder directamente a las variables de su clase
   
Entonces el código queda:

VagonPasajeros(vc LimiteLiviandad, PesoPersona)
    >>esLiviano
        ^self pesoMaximo <= LimiteLiviandad
   
    >>pesoMaximo
        ^self cantidadTotalPasajeros * PesoPersona
   
VagonCarga(vc LimiteLiviandad, PesoPersona, CantidadGuardas)
    >>esLiviano
        ^self pesoMaximo <= LimiteLiviandad
   
    >>pesoMaximo
        ^self cargaMaxima + self pesoGuardas
   
    >>pesoGuardas
        ^PesoPersona * CantidadGuardas

- Tarea:

Tienen que traer hecho en máquina e impreso para la clase que viene (dentro de 3 semanas) en smalltalk el siguiente ejercicio.  El mismo tiene que contener:
  - Código
  - Workspace
  - Diagrama de clases (pueden hacerlo en Visio o en alguna otra herramienta como ArgoUML, StarUML o yuml)
Comments