Seguimos con el ejemplo donde aplicabamos el patrón de diseño Observer a un código que realizaba la autenticación de usuarios. En este ejemplo vimos que la manera de desacoplar los componentes era mediante el uso de este patrón.
En el ejemplo, los componentes observadores reciben la información necesaria para realizar su labor cuando reciben la notificación junto con esta en forma de parámetros de entrada del método update. Cuando junto con la notificación, los observadores reciben la información de estado del sujeto, aquella que necesitan para llevar a cabo su tarea, a esta forma de comunicación de la información se la denomina «Push», dado que el sujeto «empuja» la información de su estado junto con la propia notificación.
El mayor inconveniente que puede tener el método push, tal y como lo hemos implementado, para hacer llegar la información de estado desde el sujeto a los observadores que la requieran es que no es flexible ante el cambio. Si las especificaciones futuras hicieran cambiar la información de estado, por ejemplo, ampliandola para informar adicionalmente sobre el password introducido, número de intentos de autenticación realizados, etc. ello implicaria modificar la firma del método update() en la interfaz Observer, lo que provocaría la rotura del código de todos los componentes observadores actualmente implementados, puesto que la firma de la implementación de sus métodos update() ya no coincidiría con la firma tal como quedara redefinida en la interfaz.
Método Pull
Ante esto, existe la posibilidad de no comunicar la información de estado del sujeto junto con la notificación, sino que la notificación simplemente sirva para indicar al observador que ahora es el momento de pedirle la información de estado al sujeto porque acaba de realizar la acción. Es entonces el observador, quien mediante una referencia al sujeto, invocará los métodos necesarios para recuperar la información de estado del sujeto. El observador «estira» de la información por eso el método se denomina Pull.
En el método Pull el sujeto debe de proporcionar una interfaz (una serie de métodos) para que los observadores puedan obtener la información de estado que necesitan. En el ejemplo, esto mismo es llevado a cabo mediante la creación de dos «getters» getUser() y isSuccess() de ambito público. El observador, mediante un referencia al sujeto, puede invocar estos métodos y «estirar» de la información.
El observador necesita contar con una referencia al sujeto al cual está observando que pueda untilizar dentro de su implementación del método update(). Existen varias aproximaciones para que el observador cuente con esa referencia. Podemos bien pasársela en el constructor, o bien crear un setter para establecerla, o como última posiblidad que se reciba como un parámetro del propio método update() así: void update(Subject source). Hacerlo en el constructor es muy rígido porque implica que el obervador no puede cambiar de sujeto durante toda su vida. Hacerlo mediante un setter si permite cambiar de sujeto, pero como ya veremos más adelante, implica que vamos a observar un solo sujeto a la vez. Por último, si decidimos que la referencia al sujeto nos llegue como parámetro de entrada en el método update, podremos registrar el componente para que observe varios sujetos al mismo tiempo ya que gracias a este parámetro se podrá identificar que instacia de sujeto concretamente está notificando. En el ejemplo solo existe un objeto sujeto, la instancia de AuthenticationManager, por tanto tiene sentido definir un setter.
Este setter (implementado de una forma perfecta y realista) deberia comprobar si el componente ya está actualmente observando otro sujeto y desregistrarse de su lista de observadores y a continuación registrarse para observar el nuevo sujeto.
public void setSubject(Subject subject) {
if(this.subject != null) this.subject.unregister(this);
this.subject = subject;
subject.register(this);
}
Dentro del método update el inconveniente que se nos presenta es que no podemos obtener la información de estado del sujeto a no ser que convirtamos con éxito la referencia de tipo Subject más abstracta a una más concreta del tipo concreto del sujeto AuthenticationManager.
Para asegurarnos de que el downcasting tiene exito podemos comprobar si la referencia almacenada dentro el campo subject es de tipo AutheticationManager haciendo uso del operador instanceof de java.
if (subject instanceof AuthenticationManager) {
AuthenticationManager authMng = (AuthenticationManager) subject;
//Aqui código que recupera informacion de estado del sujeto concreto
}
Este es el mayor inconveniente que presenta el método pull, a parte de obligar al observador a solicitar la información de estado que necesita, este debe conocer el tipo concreto del sujeto o tratar de averiguarlo, con lo cual estará acoplado con una implementación concreta, rompiendo el principio de diseño de programar para una interfaz y no para una implementación.
Método Push
Por otra parte tenemos en método push, que ya hemos comentado basa su estrategia en propagar la información de estado junto con la notificación. El problema como hemos apuntado es que la información de estado en si puede ser motivo de cambio en futuras ampliaciones, lo que conlleva cambios en la firma del método update. No obstante podemos solventar este problema si encapsulamos toda la información de estado que vamos a propagar en una instancia de un objeto diseñado precisamente para encapsular toda esta información de estado. Con esto, la firma del método update solamente necesita contar con un parámetro de este tipo. En el ejemplo este papel lo desempeña la interfaz NotificationInfo.
public interface NotificationInfo {
String getUser();
boolean isSuccess();
}
Normalmente y en previsión de que un observador puede observar más de una instancia de tipo sujeto al mismo tiempo, se establece otro parámetro en la firma del método update de la interfaz Observer para, en caso de estar observando varios sujetos, poder identificar de que sujeto se trata, en el caso de que debamos interaccionar con él. Quedando, por tanto, la interfaz Observer de la siguiente manera:
public interface Observer {
void update(Subject source, NotificationInfo state);
}
El código que implementa el método update en una clase observador puede acceder a la información de estado, en el ejemplo el usuario y el resultado de la validación mediante el parámetro state.
En definitiva, si en lugar de colocar cada pieza de información asociada como un parámetro independiente en la lista de parámetros del método update los encapsulamos en un nuevo tipo de objeto que cuente con una interfaz para acceder a todos ellos, no será necesario modificar nada en el código que ya tuvieramos desarrollado si posteriormente hubiera que añadir más información de estado, como sucedia con el método pull. Pero, al contrario que en el método pull, el observador no está obligado a conocer y trabajar con referencias del tipo concreto de sujeto. Por este motivo, casi todos los sistemas utilizan esta aproximación basada en push más un objeto que encapsula la información de estado.
Aqui teneis el video donde podeis ver el desarrollo de ambos ejemplos.