ATENCIÓN: si soportas cualquier tipo de ideología/religión a la que el siguiente texto le pueda resultar molesto, reemplaza, a tu gusto, tantos términos como desees hasta que te encuentres ante un contenido coherente con tus principios/creencias. No pretendo molestar a nadie.

RMS fanboys: Java ya no es malo.

AVISO: probablemente lo que voy a contar no lo haya inventado yo. No soy tan listo.

Qué delicado es el mundo de las relaciones. Lewis era un apuesto y joven campeón de Fórmula 1, vivía feliz y enamorado de la que entonces era su novia, Nicole, una no-famosa cantante de un no-famoso grupo pop (algo bastante común entre los campeones de F1 de aquellos años). Lewis tenía un problema: había tres chicas que estaban locamente enamoradas de él, ellas eran Katie, Price y Jordan, unas jóvenes muy atentas: las observadoras se hacían llamar. Tal era su obsesión por el joven inglés, que hicieron lo imposible hasta conseguir el número de teléfono de Lewis para poder llamarle y saber, en todo momento, si su estado con Nicole había cambiado.

Al principio recibía una llamada al día de Katie, Price y Jordan. Pero según pasaba el tiempo la cantidad de llamadas que tenía que contestar diariamente era mayor, aquello no parecía estar bien: su estado seguía siendo el mismo pero como buen caballero inglés que él era no se veía capaz de dejar de contestar a aquellas llamadas. Por otra parte a Nicole no le hacía mucha gracia lo de aquellas tres arpías.

Lewis era un tipo listo, un tipo con cabeza (que no con cabezón). Aquello tenía que cambiar, él no podía despreciar a aquellas maravillosas mujeres, además si el día de mañana su estado cambiaba quería que ellas fuesen las primeras en saberlo, pero por otra parte no podía soportar aquella ingente cantidad de llamadas. Entonces decidió que lo mejor era llegar a un acuerdo con Katie, Price y Jordan para, sin tener que ser ellas las que llamasen continuamente, poder avisarlas cuando su estado cambiase: era tan sencillo como tener su número y mandar un mensaje cuando su estado cambiase. Las ventajas eran claras: las observadoras podrían enterarse, Lewis no tendría que dejar de atender a Nicole para atender a llamadas intranscendentes, llamadas que iban a recibir la misma respuesta.

Katie, Price y Jordan estaban de acuerdo, pero a cambio le impusieron a Lewis una condición: podrían avisarle en cualquier momento para decirle que ya no estaban interesadas en él y que, por tanto, no hacía falta que fuesen notificadas de su cambio de estado. Lewis, chico listo, estaba de acuerdo, pero como no le gustaba desaprovechar ninguna posibilidad decidió que además cualquier chica debería poder suscribirse a su sistema de notificación.

Al principio todas seguían interesadas, Lewis era un sujeto deseado e interesaba conocer sus cambios de estado. Lewis se casó y todas las observadoras fueron avisadas, entonces Jordan decidió que ya no estaba interesada en Lewis y así se lo hizo saber. El grupo de las famosas observadoras ahora sólo estaba formado por Katie y Price.

Y era cuando ella, Nicole, tenía que decirle a Lewis algo así: I don’t need a man to make it happen / I get off being free / I don’t need a man to make me feel good / I get off doing my thing / I don’t need a ring around my finger / To make me feel complete / So let me break it down / I can get off when you ain’t around / Oh!. Su estado había cambiado, nadie le iba a llamar porque así lo había acordado con las observadoras, pero Lewis era un chico listo y como había acordado un método para avisar a las observadoras sólo tenía que ejecutarlo para que Katie y Price supiesen que su estado había cambiado. Katie y Price podían llamar a Lewis para conocer su estado puesto que sabían que este había cambiado.

Lo más increíble de esta historia no es que esté casi-basada en personajes reales, es el hecho de que todo esto sea un patrón de diseño: observer, en este caso siguiendo un protocolo de pull, sólo se notifica que el estado ha cambiado y los suscriptores se tiene que encargar de obtener el nuevo estado. Si se hubiese escogido un protocolo de push el estado podría haber sido pasado a través del método de aviso.

El acuerdo:

public interface SujetoDeseado {
	public void interesarse(Observadora o);
	public void desinteresarse(Observadora o);
	public void avisarObservadoras();
}
public interface Observadora {
	public void avisar();
}

El bueno y afable de Lewis:

import java.util.ArrayList;

public class Lewis implements SujetoDeseado {

	public enum Estado {
		SOLTERO, ENNOVIADO, CASADO
	};

	private ArrayList<Observadora> observadoras;
	private Estado estado;

	public Lewis() {
		this.observadoras = new ArrayList<Observadora>();
		this.estado = Estado.ENNOVIADO;
	}

	@Override
	public void avisarObservadoras() {
		for (Observadora o : this.observadoras) {
			o.avisar();
		}

	}

	@Override
	public void desinteresarse(Observadora o) {
		this.observadoras.remove(o);
	}

	@Override
	public void interesarse(Observadora o) {
		this.observadoras.add(o);
	}

	public Estado getEstado() {
		return this.estado;
	}

	public void estadoCambiado() {
		this.avisarObservadoras();
	}

	public void setEstado(Lewis.Estado estado) {
		this.estado = estado;
		this.estadoCambiado();
	}

}

Katie:

public class Katie implements Observadora {

	private SujetoDeseado lewis;

	public Katie(SujetoDeseado lewis) {
		this.lewis = lewis;
		lewis.interesarse(this);
	}

	public void loDelAmor() {
		System.out
				.println("Katie: A mi si que me vas a poder quitar todo!");
	}

	@Override
	public void avisar() {
		if (lewis instanceof Lewis) {
			Lewis l = (Lewis) lewis;
			switch (l.getEstado()) {
			case CASADO:
				this.esoNoVaADurar();
				break;
			case ENNOVIADO:
				this.loDeHacerseLaIndiferente();
				break;
			case SOLTERO:
				this.loDelAmor();
			}
		}
	}

	private void loDeHacerseLaIndiferente() {
		System.out.println("Katie: me da igual, sé que me quiere a mi.");
	}

	private void esoNoVaADurar() {
		System.out.println("Katie: esa relación no tiene futuro.");
	}

}

Price:

public class Price implements Observadora {

	private SujetoDeseado lewis;

	public Price(SujetoDeseado lewis) {
		this.lewis = lewis;
		lewis.interesarse(this);
	}

	public void loDelAmor() {
		System.out.println("Price: Lewis que voy sin bragafaja!");
	}

	@Override
	public void avisar() {
		if (lewis instanceof Lewis) {
			Lewis l = (Lewis) lewis;
			switch (l.getEstado()) {
			case CASADO:
				this.mePonenLosCasados();
				break;
			case ENNOVIADO:
				this.loDeTirarLosTrastos();
				break;
			case SOLTERO:
				this.loDelAmor();
			}
		}
	}

	private void mePonenLosCasados() {
		System.out.println("Price: Ains como me ponen los casados!");
	}

	private void loDeTirarLosTrastos() {
		System.out.println("Price: Tu padre podría vivir con nosotros.");
	}

}

Y Jordan:

public class Jordan implements Observadora {

	private SujetoDeseado lewis;

	public Jordan(SujetoDeseado lewis) {
		this.lewis = lewis;
		lewis.interesarse(this);
	}

	public void loDelAmor() {
		System.out.println("Jordan: Vente pacá' y mira que escotazo!");
	}

	@Override
	public void avisar() {
		if (lewis instanceof Lewis) {
			Lewis l = (Lewis) lewis;
			switch (l.getEstado()) {
			case CASADO:
				this.loDeBuscarseOtro();
				break;
			case ENNOVIADO:
				this.loDeTirarLosTrastos();
				break;
			case SOLTERO:
				this.loDelAmor();
			}

		}
	}

	private void loDeTirarLosTrastos() {
		System.out.println("Jordan: Ains que guapeton es mi Hamilton!");
	}

	private void loDeBuscarseOtro() {
		System.out.println("Jordan: Que le den, yo me voy con Eddie Irvine");
	}

}

La historia de amores y desamores:

public class Main {
	public static void main(String[] args) {
		// Katie, Price y Jordan llegan a un acuerdo para que Lewis
		// pueda mandar un aviso cuando su estado cambie.
		Lewis lewis = new Lewis();
		// Todas decicen suscribirse... Lewis es tan MONO.
		new Katie(lewis);
		new Price(lewis);
		Jordan jordan = new Jordan(lewis);
		// Lo primero que hace Lewis es avisar que está ennoviado con Nicole.
		lewis.setEstado(Lewis.Estado.ENNOVIADO);
		// Luego decide casarse, su estado cambia por tanto avisa.
		// Jordan ya le avisó que el día que su estado fuese el de casado se
		// desinteresaría.
		lewis.setEstado(Lewis.Estado.CASADO);
		// Nicole le canta eso de I don't need a man...
		// Pero ya sólo Katie y Price siguen interesadas en él, así que son las
		// únicas que se enteran de su nuevo estado
		lewis.desinteresarse(jordan);
		lewis.setEstado(Lewis.Estado.SOLTERO);
	}
}

Y el resultado de toda esta historia:

Katie: me da igual, sé que me quiere a mi.
Price: Tu padre podría vivir con nosotros.
Jordan: Ains que guapeton es mi Hamilton!
Katie: esa relación no tiene futuro.
Price: Ains como me ponen los casados!
Jordan: Que le den, yo me voy con Eddie Irvine
Katie: A mi si que me vas a poder quitar todo!
Price: Lewis que voy sin bragafaja!

Te puedes meter con la implementación todo lo que quieras, sobre todo con la visibilidad de algunos de los métodos. O por el hecho de extender java.util.Observable, pero esto ya es más discutible :-).

Y sí, probablemente debería haber aprovechado el tiempo en otra cosa, pero los domingos no suelen ser los mejores días para trabajar.



  1. Hola rau1!

    Yo habría hecho el objeto observadoras como un Set:

    private Set observadoras;

    this.observadoras = new HashSet();

    ¿Y seguro que el método desinteresarse tiene que ser así? Yo probaría:

    @Override
    public void desinteresarse(Observadora o) {
    this.observadoras.remove(o);
    }

    Saludos!
    J

  2. No pensé que fueras a poner todo el código, de verdad que no lo pensé…

  3. @Jorge:

    Siempre quise decir esta frase: me alegra que me hagas esas preguntas.

    1) Sobre porque elegí un ArrayList y no, por ejemplo, un HashSet:

    Lewis como buen caballero quiere avisar primero a la chica que antes se interesó por él. Además quiere avisar de la forma más rápida posible a todas las chicas. El precio que tiene que pagar es el de que una chica se pueda apuntar dos veces. Pero si discriminase el poder apuntarse en función del número de teléfono, imagina que dos chicas comparten un mismo número de teléfono: sólo una podría suscribirse.

    Lo que dice el Javadoc sobre List:
    - An ordered collection (…)
    Y sobre ArrayList:
    - The (…) iterator and listIterator operations run in constant time.

    Ahora lo que dice Javadoc sobre Set:
    - A collection that contains no duplicate elements. (…)
    Y sobre HashSet:
    - This class offers constant time performance for the basic operations (add, remove, contains and size), assuming the hash function disperses the elements properly among the buckets. Iterating over this set requires time proportional to the sum of the HashSet instance’s size (the number of elements) plus the “capacity” of the backing HashMap instance (the number of buckets). Thus, it’s very important not to set the initial capacity too high (or the load factor too low) if iteration performance is important.

    Sobre lo de compartir número de teléfono, me refiero a que … las colisiones ocurren.
    Y es que las observadoas se han pasado por la piedra eso de que se deben sobre-escribir todos los métodos no-finales de la clase Object, y más cuando el método add de la interfaz Set reza lo siguiente:
    - Adds the specified element to this set if it is not already present (optional operation). More formally, adds the specified element, o, to this set if this set contains no element e such that (o==null ? e==null : o.equals(e)). If this set already contains the specified element, the call leaves this set unchanged and returns false. In combination with the restriction on constructors, this ensures that sets never contain duplicate elements.
    Así que si no hemos sobre-escrito los métodos hashCode y equals(Object o) no podemos confiar en un Set, ni en nada que vaya a necesitar calcular el hashCode de nuestros objetos.

    2) Sobre el método remove:
    Se me fue la pinza! :P Realmente quería hacer:

    @Override
    public void desinteresarse(Observadora o) {
    // Sometimes Java sucks!
    int i = this.observadoras.indexOf(o);
    if (i >= 0)
    this.observadoras.remove(i);

    }

    Pero supongo que entonces fue cuando mi amigo Eclipse me dijo… estúpido, también puedes usar remove(Object), no te acuerdas, o qué?. De todas formas son dos búsquedas lineales por el precio de una :-O. Así que lo mejor es hacer lo que has dicho tú (lo cambio).
    Además si se supiese que los métodos add(Object) y remove(Object) [o remove(i)] iban a ser usados constantemente sería mejor usar una LinkedList, porque cuando se inserta o se elimina (salvo al final) de un ArrayList se deben desplazar todos los objetos que este contiene desde el punto de inserción.

  4. Me alegro de que te alegres de que haga esa pregunta :p

    Aceptamos List como almacenamiento de los observadores, pero yo evitaría que se suscribiese dos veces la misma observadora. Se puede comprobar con contains antes de insertarlo, aunque entonces haría falta un bloque synchronized (o decir que no es thread safe ;).

    Ah, y también faltaría un synchronized en el remove que querías hacer con el indexOf(i) (que era directamente lo que había leido yo, he tenido que comprobar en el google reader cómo estaba antes…).

    Saludos!
    J

  5. @arpia49: pobre inocente!

    @Jorge con contains(Object o) estás igual de expuesto a posibles colisiones, ya que las observadoras no sobre-escriben el método equals(Object o). Y extraigo de nuevo del javadoc de la interface List:
    - boolean contains(Object o)
    Returns true if this list contains the specified element. More formally, returns true if and only if this list contains at least one element e such that (o==null ? e==null : o.equals(e)).
    .

    Es un tema interesante el de los contratos, pero de esta historia podemos hablar otro día ;-). Al fin y al cabo a Lewis no creo que le suponga mucha pasta mandar duplicar el mismo SMS a la misma persona.

  6. Pero el equals de la clase Object devuelve true si (y solo si) los objetos son exactamente los mismos:

    The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).

    Y por otro lado el remove tendría el mismo problema que indicas del contains:

    Removes a single instance of the specified element from this list, if it is present (optional operation). More formally, removes an element e such that (o==null ? e==null : o.equals(e)), if the list contains one or more such elements.

    ¿Vas a escribir algo parecido para más patrones de diseño? ;)

    Saludos!
    J

  7. :-) Tienes razón, pero desde que la interfaz observadora puede ser implementada por cualquiera no puedes confiar en cómo va a funcionar su método equals(Object o).

    Un ejemplo:

    Una chica nueva y mala en la ciudad:

    public class FakeJordan extends Katie {
    	public FakeJordan(SujetoDeseado lewis) {
    		super(lewis);
    	}
    
    	@Override
    	public boolean equals(Object o) {
    		return (o instanceof Katie);
    	}
    
    	@Override
    	public void loDelAmor() {
    		System.out.println("FakeJordan: se la he jugado a Katie!");
    	}
    }
    

    Un nuevo escenario:

    	public static void main(String[] args) {
    		Lewis lewis = new Lewis();
    		Katie katie = new Katie(lewis);
    		Price price = new Price(lewis);
    		FakeJordan fakeJordan = new FakeJordan(lewis);
    		lewis.desinteresarse(fakeJordan);
    		lewis.setEstado(Lewis.Estado.SOLTERO);
    	}
    

    OUTPUT:

    Price: Lewis que voy sin bragafaja!
    FakeJordan: se la he jugado a Katie!
    

    Cuando el OUTPUT esperado debería ser:

    Katie: A mi si que me vas a poder quitar todos los botones!
    Price: Lewis que voy sin bragafaja!
    

    Pero lo dicho, esa es otra guerra en la que otro día podemos batallar.
    Necesitaré otro domingo lluvioso, agujetoso y sin carreras, pero quizás algún día escriba algo más ;-).

  8. Si hola… me gustaría saber que le paso a Lewis.

    Gracias.

  9. Eso pasaría igual con todas las maneras de hacerlo que hemos ido viendo.

    ¿Y si hicieses el remove solo basandote en el operador ==? Recorres la lista y si encuentras exactamente ese objeto lo eliminas. Así FakeJordan no se la jugaría a Katie.

    También se podría hacer una clase abstracta en lugar de una interfaz en la que sobreescribas el métdodo equals con un final para que las subclases no lo puedan tocar, aunque puede que no sea muy recomendable.

    Saludos!
    J

  10. @Marcos: se fue a casa a llorar con su padre ;-).

    @Jorge: quizás ;-).

Dejar un comentario



About Raúl

Raúl Ochoa, a spaniard working for Tuenti in Madrid, Spain. More about me.

Subscribe to the feed

If you want to receive a notification when I update the website, you only have to add the feed to your reader, or submit your email address and I'll let you know.

Categories