[J2ME] Nokia anuncia la prÛxima generaciÛn de la plataforma Java para la serie 60
Nokia ha anunciado que la prÛxima generaciÛn de dispositivos de la serie 60 implementar· la configuraciÛn CDC ( Connected Device Configuration )
« Mayo 2005 | Inicio | Julio 2005 »
Nokia ha anunciado que la prÛxima generaciÛn de dispositivos de la serie 60 implementar· la configuraciÛn CDC ( Connected Device Configuration )
Hay dos cosas que nos gustan particularmente en design-nation: los dados, y las aplicaciones para Mac OSX. AsÌ que hemos desarrollado una pequeÒa aplicaciÛn ( lo has adivinado, un dado ) con la finalidad de enseÒar aunque sea por encima cÛmo es el proceso de desarrollo en el Mac. Lo haremos utilizando XCode y escribiendo cÛdigo en Objective-C
En todo caso, como casi cualquier otro tutorial sobre XCode que se pueda escribir, el nuestro queda oscurecido por el que hay en el Apple Developer Connection. LÈelo!
Pero antes, echa un vistazo al resultado final:
Antes de empezar a picar cÛdigo como locos, parÈmonos a pensar cÛmo vamos a implementar la aplicaciÛn. Generaremos un n˙mero entre 1 y 6, y despuÈs cargaremos un png que contendr· la cara del dado que corresponda a ese n˙mero y lo mostraremos en el interfaz.
Siempre es bueno desarrollar cualquier aplicaciÛn siguiendo el paradigma MVC, pero en este caso, veremos cÛmo XCode y el Interface Builder hacen esa implementaciÛn f·cil e intuitiva.
Bien, empecemos. en primer lugar, abre XCode ( si no lo encuentras, est· escondido en la carpeta /Developer/Applications ). DespuÈs selecciona File-> New Project, y cuando te pregunte, elige "Cocoa Application". Entonces, escribe el nombre y elige la carpeta en disco donde crear los archivos del proyecto. Tras hacer eso, se abrir· la ventana del proyecto ( "Project Window" )
No nos vamos a meter a explicar quÈ son todos los archivos y carpetas que se han creado. Adem·s, hay varios tutoriales ( incluso la documentaciÛn de XCode ) que lo explican. Por tanto, pasemos directamente a trabajar en el interface.
øPero cÛmo?. Nuestra aplicaciÛn estar· basada en el MVC. eso significa que el interfaz de usuario ( la vista ) ser· totalmente independiente de la lÛgica de la aplicaciÛn ( el modelo ). Y XCode y el Interface Builder har·n un excelente papel a la hora de facilitar esa implementaciÛn.
Para empezar, por tanto, haz doble click en el archivo MainMenu.nib. De ese modo se abrir· el Interface Builder.
El Interface Builder es una herramienta visual que permite, como su nombre indica, construir el interfaz de la aplicaciÛn. Pero no sÛlo eso, sino que nos asegura que, si seguimos las guÌas que nos proporciona, cumpliremos con las User Interface Guidelines de Apple. Veamos cÛmo.
En primer lugar, adapta el tamaÒo de la ventana del programa a tus necesidades. Para hacerlo, puedes arrastrar la esquina inferior derecha, o abrir el Inspector, seleccionar Size, e introducir los valores a mano. En nuestro ejemplo, hemos seleccionado un ancho de 240 y un alto de 280. En la secciÛn de Attributes se pueden cambiar el nombre de la ventana, fondo, y otras propiedades.
Ahora, aÒade los elementos de interfaz que vamos a necesitar. Akade un NSImageView ( de la pestaÒa Cocoa-Containers ) y un Button ( de la pestaÒa Cocoa-controls ).
Arrastra la instancia del NSImageView, y suÈltala sobre la ventana. Mientras la arrastras, ver·s cÛmo el Interface Builder dibuja unas lÌneas que ayudan a posicionarlo. Seguir esas guÌas asegurar· que se est·n cumpliendo las User Interface Guidelines de Apple.
A continuaciÛn aÒade el botÛn al interfaz. Arrastra el botÛn de la paleta a la ventana de la aplicaciÛn ( recuerda, deja que las guÌas te guÌen ), y tras soltarlo, mientras a˙n est· seleccionado, presiona Option y muÈvelo usando los cursores. Ver·s cÛmo el Interface Builder muestra la distancia entre el botÛn y el elemento del interfaz que estÈ debajo del cursor del ratÛn. Por tanto, coloca el cursor sobre el NSImage View, y centra el botÛn respecto a Èl.
Ahora, asigna el texto del botÛn, haciendo doble click sobre Èl, y tras hacerlo, vete a la secciÛn de Attributes del Info panel, y selecciona el atajo de teclado para el botÛn ( en este caso, Enter ).
Para asegurarnos que el botÛn tendr· el foco cuando se arranque la aplicaciÛn, setearemos el initialFirstresponder. Para ello, hay que arrastrar ( con Control presionado ) una lÌnea de conexiÛn desde la instancia de window en la ventana MainMenu.nib, hasta el botÛn. Una vez hecho, hay que seleccionar en el Info panel initialFirstResponder, y hacer click en "Connect"
El interfaz ya casi est· terminado. SÛlo falta conectarlo con el modelo de la aplicaciÛn, lo que haremos a travÈs del controlador.
Pero antes de hacerlo, vamos a ver r·pidamente quÈ son los "outlets" y los "actions". Los outlets son variables de instancia de una clase que apuntan a otro objeto. Dicho de otra forma, un outlet es una referencia que un objeto mantiene a otro objeto distinto. Sin embargo, una acciÛn ( action ) es similar a un mÈtodo p˙clico de una clase. Mejor dicho, es la forma de encapsular el envÌo de un mensaje a un target determinado.
En todo caso, el Interface Builder facilita el proceso de crear outlets y actions
En primer lugar, tendremos que crear la clase que haga el papel de controlador. Para ello, en el panel Classes de la ventana MainMenu.nib, hay que hacer click, en el panel m·s a la izquierda, en NSObject. A continuaciÛn, debe presionarse Return. Entonces, hay que nombrar la nueva clase como DiceController. De ese modo, hemos creado una subclase de NSObject ( la clase base de toda la jerarquÌa ).
Ahora, definiremos los outlets para esa clase. DiceController es el controlador, por lo que tendr· un outlet al NSImageView del interfaz, y otro al modelo ( una clase que no hemos creado a˙n ). Por tanto, selecciona "DiceController", y en el Info Panel, selecciona Attributes. A continuaciÛn aseg˙rate que el panel Outlets est· seleccionado, y haz click en add. Llama el outlet imageView, y asÌgnalo el tipo NSImageView ( un poco de tipado no viene mal ).
Ahora aÒade otro outlet, que ser· el que apunta al modelo, y ll·malo dice.
Para crear la acciÛn que ser· ejecutada cuando se haga click en el botÛn, selecciona el panel de acciones ( en el Info Panel ), haz click en add y da a la acciÛn el siguiente nombre: roll.
Ya hemos descrito el controlador, incluyendo sus outlets y actions. Ahora es el momento de conectar este controlador con el interfaz. Para hacerlo, en primer lugar habr· que crear una instancia de la clase DiceController, seleccionando la clae en la ventana MainMenu.nib, y a continuaciÛn seleccionando Instantiate en el men˙ Classes. La nueva instancia aparecer· en el panel de instancias. Para conectarla con el interfaz, Control-Drag una lÌnea desde la instancia de la clase hasta el NSIMageView del interfaz. Cuando sueltes el botÛn, selecciona el outlet que estar· asignado a esa conexiÛn ( en nuestro ejemplo inageView ), y haz click en connect. Para conectar el botÛn en la vista con el controlador, Control-Drag desde el botÛn hasta la instancia de DiceController. Al soltar el botÛn, selecciona roll en la columna de las acciones y haz click en connect.
Para crear el modelo ( Dice ), define una nueva subclase de NSObject y crea una instancia de esa clase. A continuaciÛn, crea una conexiÛn de outlet entre DiceController y Dice. °Y ya est· todo conectado!
Una vez que hemos concluido con el trabajo en el interfaz, es el momento de que XCode genere el cÛdigo necesario. Para ello, selecciona una de las dos clases en la ventana MainMenu.nib, y selecciona Create classes en el men˙ Classes. A continuaciÛn haz lo mismo con la otra clase.
Mira el cÛdigo generado. Ver·s que hay un mÈtodo sin implementar en DiceController.m. ImplementÈmoslo.
@implementation DiceController
- (IBAction)roll:(id)sender
{
[ imageView setImage: [ dice image ] ];
}
@end
El cÛdigo es muy sencillo. Simplemente, setearemos en contenido de imageView ( que es uno de los outlets de esta clase ), con una imagen que obtendremos del modelo ( el otro outlet ).
Por tanto, ahora concluiremos la implementaciÛn del modelo. Esta clase tendr· un mÈtodo que generar· un n˙mero aleatorio entre 1 y 6, y cargar·, dependiendo de ese valor, un png diferente de disco. DespuÈs le pasar· la imagen cargada al controlador.
Por tanto, el constructor del modelo ser·:
-( id ) init
{
self = [ super init ];
image = nil;
srand( time( NULL ) );
return self;
}
Para evitar agujeros de memoria, se liberar·n los recursos al liberar la clase:
- ( void ) dealloc
{
[ image release ];
[ super dealloc ];
}
Los png estar·n en el bundle de la aplicaciÛn. Por tanto, lo primero ser· obtener una referencia a ese bundle:
-( NSImage* ) image
{
NSBundle *bundle = [ NSBundle bundleForClass: [ self class ] ];
NSString *imgName = [ bundle pathForResource: [ self generateImageName ] ofType: @"png" ];
image = [ [ NSImage alloc ]
initWithContentsOfFile: imgName ];
return image;
}
El cÛdigo completo de Dice.m ser·, finalmente:
#import "Dice.h"
@implementation Dice
-( id ) init
{
self = [ super init ];
image = nil;
srand( time( NULL ) );
return self;
}
- ( void ) dealloc
{
[ image release ];
[ super dealloc ];
}
-( NSString* ) generateImageName
{
int random = ( rand( ) % 6+ 1 );
return [ NSString stringWithFormat: @"%i", random ];
}
-( NSImage* ) image
{
NSBundle *bundle = [ NSBundle bundleForClass: [ self class ] ];
NSString *imgName = [ bundle pathForResource: [ self generateImageName ] ofType: @"png" ];
image = [ [ NSImage alloc ]
initWithContentsOfFile: imgName ];
return image;
}
@end
Ya sÛlo queda compilar la aplicaciÛn. Bueno, y otro paso m·s. Habr· que empaquetarla para distribuirla. Para ello, crearemos un archivo dmg que contendr· la aplicaciÛn y un archivo de texto con las instrucciones de instalaciÛn.
Crearemos ese dmg con la Utilidad de Discos ( /Aplicaciones/Utilidades/Utilidad de discos ). Abre la utilidad de discos, crea una nueva imagen de disco, asigna el espacio de dicha imagen, y se crear· y montar·. Arrastra dentro de la imagen el bundle de la aplicaciÛn y el resto de archivos a incluir en la distribuciÛn, y °listo!
Hay que ver lo atrevida que es la ignorancia...
VÌa Error500 me acabo de enterar de que google ha publicado una versiÛn gratuita de google earth ( lo que antes era Keyhole ).
Eso est· muy bien, pero lo que realmente me ha llamado la atenciÛn es la forma en la que han descrito los requisitos del sistema para que funcione la aplicaciÛn.
Primero, lo explican por las bravas: no funciona en Mac, no funciona en pcs de escritorio de m·s de 4 aÒos, y no funciona en port·tiles de m·s de dos aÒos. Y luego, ya sÌ, las especificaciones de hardware, tal y como estamos acostumbrados a leerlas ( CPU, RAM, espacio en disco ).
Aunque a mÌ no me gusta hablar del "usuario medio", porque es un tÈrmino que implica que el que lo usa ya se coloca por encima de esos usuarios, sÌ es cierto que describir los requerimientos de hardware de esa forma hace que sean mucho m·s f·ciles de entender por los que no son tÈcnicos.
Y es que los que somos tÈcnicos no deberÌamos olvidar nunca que nuestro trabajo va dirigido normalmente a gente que no tiene porquÈ saber quÈ procesador tiene su ordenador.
No te repitas. En cuanto escribes el mismo cÛdigo m·s de una vez, est·s empezando a meterte en problemas
AquÌ tienes un ejemplo.
Estoy trabajando en una aplicaciÛn en la que voy a utilizar los componentes de UI. Es una aplicaciÛn Modelo-Vista-Controlador, por lo que la vista estar· totalmente desacoplada de la lÛgica de la aplicaciÛn
La aplicaciÛn va a tener m·s de una vista ( hay varias "pantallas" diferentes en las que tengo que mostrar los datos de diferentes formas ), por lo que he decidido que lo mejor ser· escribir una clase diferente para cada una de esas vistas.
Por tanto, cada vista attachear· un movieclip, que es el que contiene realmente la "pantalla" con los elementos interactivos ( botones, datagrid, etc ). Todas esas vistas extienden de una clase que implementa cierta funcionalidad com˙n ( emisiÛn de eventos y alguna cosilla m·s ). Por tanto, no extender·n de movieclip ( aparte de por razones un poco m·s abstractas, aunque Èste no es el momento para discutir eso ). A lo que vamos.
Adem·s, resulta ( todo son facilidades ) que por ahora no tengo los gr·ficos definitivos ( vamos, que me lo estoy pintando yo ). A˙n m·s, si al final el presupuesto da para ello, tendrÈ que desarrollar mis propios componentes.
Bien, pues una vez puestos en antecedentes, imagina que arranca la aplicaciÛn. Lo primero con lo que se va a encontrar el usuario es con una pantalla de login
Como puedes ver en la captura, a) m·s me vale que me vaya bien en esto de la programaciÛn, por que lo que es en la parte gr·fica...; b) la pantalla contiene un componente ( el botÛn ).
La clase que maneja esa pantalla ser·:
import mx.utils.Delegate;
class LoginView
{
private var timeline : MovieClip;
private var loginScreen: MovieClip;
function LoginView( tl: MovieClip )
{
this.timeline = tl;
}
public function init( )
{
this.loginScreen = this.timeline.attachMovie( "LoginScreen", "LoginScreen",
this.timeline.getNextHighestDepth( ), { _x: 0, _y: 0 } );
this.loginScreen.loginBtn.label= "Go!";
this.loginScreen.loginBtn.addEventListener( "click", Delegate.create( this, click ) );
}
private function click( )
{
trace( "click" );
}
}
Y cuando quiera que aparezca, harÈ algo como:
var loginView: LoginView = new LoginView( this );
loginView.init( );
Y el botÛn no va a funcionar. Ni siquiera va a mostrar el texto que le he asignado. Tras unos minutos de buscar en google, un poco de sentido com˙n, y un poco menos de conocimiento de la plataforma, he llegado a la conclusiÛn de que el botÛn no se inicializa a tiempo, y que tal vez deberÌa dejar pasar un frame para asignar el texto y el click.
import mx.utils.Delegate;
class View
{
private var timeline : MovieClip;
private var loginScreen: MovieClip;
function View( tl: MovieClip )
{
this.timeline = tl;
}
public function init( )
{
this.loginScreen = this.timeline.attachMovie( "LoginScreen", "LoginScreen",
this.timeline.getNextHighestDepth( ), { _x: 0, _y: 0 } );
var theView: View = this;
this.loginScreen.onEnterFrame = function( )
{
theView.initButton( );
delete this.onEnterFrame;
}
}
private function initButton( )
{
this.loginScreen.loginBtn.label= "Go!";
this.loginScreen.loginBtn.addEventListener( "click", Delegate.create( this, click ) );
}
private function click( )
{
trace( "click" );
}
}
Y funciona. Pero esta soluciÛn no huele demasiado bien ( y no sÛlo por el truqui del scope ). Voy a tener que hacer bastantes pantallas ( alrededor de una docena ), asÌ que voy a tener que repetir ese cÛdigo en todas ellas. Desde luego, podrÌa subclasificar, pasar ese cÛdigo a una clase base, pero no voy a tener el mismo n˙mero de botones en todas las pantallas, luego tampoco tendrÌa mucho sentido subclasificar para luegosobreescribir, y adem·s, sÛlo podrÌa utilizar ese cÛdigo en subclases de la clase base que creara, con lo cual no iba a tener demasiada flexibilidad, que digamos. Y adem·s, mis vistas ya extienden de otra clase base.
E incluso puede ser peor, porque es probable que tenga que cambiar esos componentes por unos mÌos ( o no, que a˙n no lo sÈ ). AsÌ que es m·s que probable que ese cÛdigo tenga que cambiar en un futuro no muy lejano
AsÌ pues, estoy repitiendo cÛdigo, y adem·s, es probable que ese cÛdigo tenga que cambiarlo en breve. øCÛmo lo puedo resolver?. Pues encapsulando ese cÛdigo dentro de su propia clase.
Para empezar, usarÈ un callback que ya he utilizado m·s veces:
class Callback
{
private var callbackObjVal: Object;
private var callbackMethodVal: String;
public function Callback( objParam: Object, methodParam: String )
{
this.callbackObjVal = objParam;
this.callbackMethodVal = methodParam;
}
public function fire( parameter: Object ): Object
{
return this.callbackObjVal[ this.callbackMethodVal ]( parameter );
}
}
La clase encargada de manejar el botÛn ser·:
class ButtonHandler
{
public static function initButton( mc: MovieClip, callback: Callback )
{
var cb: Callback = callback;
mc.onEnterFrame = function( )
{
cb.fire( );
delete this.onEnterFrame;
}
}
}
Y la utilizarÈ de la siguiente forma:
import mx.utils.Delegate;
class View
{
private var timeline : MovieClip;
private var loginScreen: MovieClip;
function View( tl: MovieClip )
{
this.timeline = tl;
}
public function init( )
{
this.loginScreen = this.timeline.attachMovie( "LoginScreen", "LoginScreen",
this.timeline.getNextHighestDepth( ), { _x: 0, _y: 0 } );
ButtonHandler.initButton( this.loginScreen, new Callback( this, "initButton" ) );
}
private function initButton( )
{
this.loginScreen.loginBtn.label= "Go!";
this.loginScreen.loginBtn.addEventListener( "click", Delegate.create( this, click ) );
}
private function click( )
{
trace( "click" );
}
}
øUna soluciÛn excesivamente compleja?. Puede, pero es la que m·s me ha satisfecho, porque es la que aporta m·s flexibilidad. Si tengo que cambiar la forma en la que manejo los botones, sÛlo tengo que cambiar la implementaciÛn de un mÈtodo en una clase. Estoy programando pensando en el interfaz, y no en la implementaciÛn.
La ComisiÛn Intersectorial de Asuntos Sociales de design-nation ha elaborado un sesudo informe sobre la manifestaciÛn que se ha convocado en Madrid para este s·bado "en defensa de la familia".
Puedes leerlo en nuestro blog off-topic:
øEn defensa de la familia?
Por cierto, como Èste es un blog sobre desarrollo de software, queremos aprovechar para indicar que esto que acabamos de hacer es lo que a veces se llama "meter una indirecciÛn".
Dicho de otra forma, si no te gusta el tema, tienes una oportunidad m·s de no hacer click en el link.
Es probable que este post no aporte nada a los desarrolladores Java m·s experimentados, pero para mÌ al menos, el artÌculo que apareciÛ en onjava.com el pasado dÌa 8 sobre Prevailer ha sido un descubrimiento.
Prevailer es un framework que proporciona una capa de prevalencia a las aplicaciones Java. Lo cual lleva a la siguiente pregunta: øquÈ es la prevalencia?. Pues es un mecanismo de persistencia pero no basado en base de datos ni en serializaciÛn de objetos de negocio en disco, sino que permite tomar screenshots del estado de los objetos en memoria en intervalos determinados, y serializar esos screenshots. Las transacciones necesarias se serializan tambiÈn a partir de objetos command.
De esa forma, se puede evitar el implementar una capa de acceso a base de datos, por ejemplo, lo que puede resultar bastante ˙til sobre todo a la hora de implementar prototipos de aplicaciones.
En todo caso, Jim Paterson, el autor del artÌculo de onjava.com lo va a explicar mejor que yo, sobre todo porque explica el proceso de implementaciÛn con un ejemplo.
En Naveg·polis han publicado un artÌculo titulado "øCÛmo motivar a los programadores?", construido a base de citas de algunos de los libros de mayor distribuciÛn en este mundo de la inform·tica ( y algunos del mundo real, como las Aventuras de Tom Sawyer ).
Una artÌculo al que yo aÒadirÌa un enlace m·s: øCÛmo saber cu·ndo la moral de la tropa est· baja? ( el tÌtulo en castellano lo he puesto yo, lo digo por si no resulta obvio ).
Ayer se congelÛ el infierno, y Apple anunciÛ el switch a Intel. Se tenga la opiniÛn que se tenga del asunto ( y yo tengo la mÌa, por supuesto ), lo cierto es que Apple ha puesto a disposiciÛn de los miembros del ADC la versiÛn 2.1 de XCode, y ha publicado un documento llamado Introduction to Universal Binary Programming Guidelines.
El mundo sigue girando...
ActualizaciÛn: XCode 2.1 release notes
En DNJ han publicado una serie de artÌculos ( algunos sÛlo para usuarios registrados ) escritos por Alex Homer en los que se resumen algunas de las novedades de la nueva versiÛn del .Net framework.
Est· desencadenado. El primer ataque ha sido lanzado. El profesor Dispar ha dado las Ûrdenes a sus huestes para dominar el mundo. En post anteriores hemos visto como el profesor Dispar ha conseguido clonar cualquier animal ( con gran predilecciÛn por ovejas y vacas ) utilizando un patrÛn prototype, ha conseguido darlas un rol din·micamente con el patrÛn extension objects, y ha repartido las Ûrdenes con un patrÛn command.
Pero como ya sabemos, el profesor Dispar est· loco, pero no es idiota. Sabe, que algo puede salir mal, que un pequeÒo detalle puede truncar sus planes de dominar el mundo. Y tambiÈn sabe que una retirada a tiempo es una victoria.
Est· desencadenado. El primer ataque ha sido lanzado. El profesor Dispar ha dado las Ûrdenes a sus huestes para dominar el mundo. En post anteriores hemos visto como el profesor Dispar ha conseguido clonar cualquier animal ( con gran predilecciÛn por ovejas y vacas ) utilizando un patrÛn prototype, ha conseguido darlas un rol din·micamente con el patrÛn extension objects, y ha repartido las Ûrdenes con un patrÛn command.
Pero como ya sabemos, el profesor Dispar est· loco, pero no es idiota. Sabe, que algo puede salir mal, que un pequeÒo detalle puede truncar sus planes de dominar el mundo. Y tambiÈn sabe que una retirada a tiempo es una victoria.
AsÌ, pues, ha equipado a sus huestes de una radioenigma de campaÒa ( es decir, una radio acompaÒada de una m·quina enigma ). Pero, ø porquÈ ha hecho esto ?. Sencillo, en el fragor de la batalla, las comunicaciones directas se hacen complicadas. Es difÌcil hacer llegar las Ûrdenes, y muchas veces no se tiene claro a quien le estamos dando esas Ûrdenes ( øser· una vaca?, ø ser· una oveja? ).Conquistar el mundo no es f·cil, de hecho muchos lo han intentado pero no lo han conseguido. El profesor Dispar, ha estudiado con detenimiento todas las intentivas anteriores para no reproducir los mismos errores. ø QuÈ pasarÌa si para dar nuevas Ûrdenes, el profesor necesitase que el sargento vaca tuviese que recorrerse la trinchera indicando una por una a todas las vacas y ovejas que se retirasen ?. Desde que le diese la orden a la primera, hasta que se la diese a la ˙ltima, pasarÌa un tiempo precioso. Adem·s, ø y si ocurre algo por el camino ?. Todos hemos jugado al f˙tbol de pequeÒos despuÈs de comer, y sabemos lo que ocurre cuando te pones a correr. Se llama flato. Si nos pasa a nosotros que tenemos un estÛmago, imaginemos las posibilidades que hay de que le pase al sargento vaca que tiene 4. Las posibilidades de que las Ûrdenes no lleguen a todos los integrantes de nuestra tropa son amplias.
Sabiendo esto, el profesor Dispar, que est· loco pero no es idiota, para poder modificar ( actualizar ) f·cil y r·pidamente el comportamiento de sus huestes, utilizar· el patrÛn Observer.
Como el profesor Dispar no es idiota ( aunque este loco y sea un genio del mal, no es idiota ), ha encontrado una malvada forma de enviar las Ûrdenes a sus tropas. Las ovejas y vacas, estar·n equipadas con una radio que sintoniza Cadena Dial ( sÌ, entendemos que es desagradable, pero dominar el mundo tiene estas cosas ). Sus tropas estar·n atentas a la radio, y cuando esta emita una canciÛn de Village People, la m·quina enigma les indicar· quÈ acciÛn deben tomar.
Dj Dispar emitiendo su programa
El cÛdigo ser· algo asÌ
public interface IObserver
{
public void update(Message info);
}
public interface ISubject
{
public void addObserver(IObserver obs);
public void removeObserver( IObserver obs );
public void notifyObserver( );
}
public class Cow implements IObserver
{
public Cow( )
{
debug("Soy una vaca muuuuuu| I am a cow Muuuu");
}
private void debug( String arg )
{
System.out.println("Vaca | Cow :-> "+arg );
}
public void update( Message msg )
{
debug("Actualizada | update ");
String kk = msg.getMsg();
debug("msgText: "+kk );
}
}
public class Sheep implements IObserver
{
public Sheep( )
{
debug("creada la oveja | a new sheep");
}
private void debug( String arg )
{
System.out.println("Oveja |Sheep :-> "+arg );
}
public void update( Message msg )
{
debug("Actualizada | update ");
String kk = msg.getMsg();
debug("msgText: "+kk );
}
}
El mensaje:
public class Message {
String msg = "";
public void setMsg( String arg )
{
this.msg = arg;
}
public String getMsg( )
{
return this.msg;
}
}
public class Profesor implements ISubject
{
private ArrayList misObservers;
private Message miMsg;
public Profesor( )
{
//System.out.println("profesor profesor");
debug("Creando al profesor | a new Profesor");
misObservers = new ArrayList();
}
public void debug( String arg )
{
System.out.println("Profesor:-> "+arg);
}
public void addObserver( IObserver obs )
{
debug("aÒadido observer | a new observer");
misObservers.add(obs);
debug("Ya tengo | I have: "+misObservers.size()+" observers ");
}
public void removeObserver( IObserver obs)
{
debug("eliminado observer | removing observer");
//int value = indexOf
misObservers.remove(misObservers.indexOf(obs));
debug("ahora me quedan | now i have : "+misObservers.size()+" observers ");
}
public void notifyObserver( )
{
debug("notificar observers | notify observer ");
miMsg = new Message( );
miMsg.setMsg("In the Navy");
for( int i = 0; i < misObservers.size(); i++ )
{
IObserver obs = ( IObserver )misObservers.get(i);
obs.update( miMsg );
}
}
}
Y para finalizar
public class ObserverSample
{
public static void main(String[] args)
{
Profesor prof = new Profesor();
Sheep oveja = new Sheep();
Cow vaca = new Cow();
Sheep oveja2 = new Sheep( );
Cow vaca2 = new Cow( );
Sheep oveja3 = new Sheep();
Cow vaca3 = new Cow();
prof.addObserver( oveja );
prof.addObserver( oveja2 );
prof.addObserver( oveja3 );
prof.addObserver( vaca );
prof.addObserver( vaca2 );
prof.addObserver( vaca3 );
prof.removeObserver(oveja);
prof.notifyObserver( );
}
}
De este modo, el profesor, puede comunicar r·pidamente con todas sus tropas. Es brillante, es genial, es maravilloso, es....ø perfecto ?. Bueno, la verdad es que est· muy bien, pero imaginemos, por un momento una situaciÛn en la que una oveja consigue infiltrarse en el alto mando enemigo, y descubre que los enemigos est·n a punto de ser derrotados, y en ese momento de extasis, la radio emite "In the Navy" indicando a las tropas que hay que retirarse. NOOOOOOOOO ( bueno, esto est· traducido, claramente la oveja dirÌa algo como BEEEEEEEEEEE ). Nuestra oveja espÌa, tiene una informaciÛn valiosÌsima, pero que no puede comunicar a su alto mando, porque la forma de transmisiÛn es ˙nicamente de tipo push, del profesor a sus tropas, no hay retorno. Que situaciÛn, la oveja necesita transmitir que el ataque debe continuar pero....
Una oveja tarareando "In the Navy"
ajajajajajajajaj, el profesor Dispar ( loco pero no tonto ) ha pensado en ello, y ha decidido implementar un patrÛn observer que permita la transmisiÛn tipo push ( del sujeto emisor a todos los sujetos receptores ) y tipo pull ( uno de los objetos receptores, puede solicitar una informaciÛn al objeto emisor para que este la envÌe a todos los dem·s ). AsÌ, nuestra oveja espÌa, estar· equipada de un n˙mero de telÈfono al que podr·n llamar y solicitar que emitan "Rasputin" de Boney M, y cuando esta canciÛn sea emitida por la radio, las ovejas y vacas que la escuchen, sabr·n que ha llegado el momento de la ofensiva final. JAJAJAJAJAJAJAJA. Para ello debemos modificar ligeramente nuestros cÛdigos anteriores.
Para empezar nuestro sujeto, implementar· un nuevo mÈtodo que hemos aÒadido a la interfaz ISubject( requestInfo ), que permitir· recibir las llamadas telefÛnicas de la oveja espÌa.Vemos tambiÈn que el constructor de las ovejas y las vacas tambiÈn ha cambiado un poco. Ahora las ovejas y vacas, almacenar·n una referencia al constructor. TambiÈn hemos modificado el cÛdigo de nuestra oveja ( suponemos que esta es la oveja espÌa en el momento en que decide que pese a lo que emite la radio, hay que atacar y pide que se emita Rasputin que es otra seÒal de ataque ).
Por tanto, la interfaz ISubject quedar· asÌ:
public interface ISubject
{
public void addObserver(IObserver obs);
public void removeObserver( IObserver obs );
public void notifyObserver( Message msg );
public void requestInfo( String arg );
}
public class Cow implements IObserver
{
public Cow( )
{
debug("Soy una vaca | I am a cow ");
}
private void debug( String arg )
{
System.out.println("Vaca | Cow :-> "+arg );
}
public void update( Message info )
{
debug("Actualizada | update ");
debug(" -> "+ info.getMsg());
}
}
public class Sheep implements IObserver
{
private ISubject profesor;
public Sheep(ISubject isub )
{
debug("creada la oveja | a new Sheep");
this.profesor = isub;
this.profesor.addObserver(this);
this.profesor.requestInfo( "Rasputin" );
}
private void debug( String arg )
{
System.out.println("Oveja | Sheep:-> "+arg );
}
public void update( Message info )
{
debug("Actualiz·ndome | update");
debug( " -> "+info.getMsg());
}
}
import java.util.ArrayList;
public class Profesor implements ISubject
{
private ArrayList misObservers;
public Profesor( )
{
//System.out.println("profesor profesor");
debug("Creando al profesor | a new profesor");
misObservers = new ArrayList();
}
public void debug( String arg )
{
System.out.println("Profesor :-> "+arg);
}
public void addObserver( IObserver obs )
{
debug("a“adido observer | a new observer");
misObservers.add(obs);
debug("Ya tengo | I have: "+misObservers.size()+" observers ");
}
public void removeObserver( IObserver obs)
{
debug("eliminado observer | removing observer");
misObservers.remove(misObservers.indexOf(obs));
debug("ahora me quedan | now i have: "+misObservers.size()+" observers ");
}
public void notifyObserver(Message msg )
{
debug("notificar observers | notify observers");
for( int i = 0; i < misObservers.size(); i++ )
{
IObserver obs = ( IObserver )misObservers.get(i);
obs.update( msg );
}
}
public void requestInfo( String arg )
{
debug("recibiendo una peticiÛn | someone has asked for ...");
Message msg = new Message( );
msg.setMsg( arg );
this.notifyObserver( msg );
}
}
Y por fin:
public class ObserverSample
{
public static void main(String[] args) {
Profesor dispar = new Profesor();
Sheep oveja = new Sheep( dispar );
}
}
Evidentemente, el profesor, que ha estudiado la API de JAVA, sabe que existe la clase java.util.Observable y la interface java.util.Observer. La primera no la ha utilizado, pues eso implicarÌa que el profesor ( el mismo ) extiende o hereda de Observable. Esto podrÌa ser posible, pero en realidad el profesor extiende de una larga extirpe de profesores diabÛlicos empeÒados en conquistar el mundo, y ni quiere ni puede extender de Observable. ( class Profesor extends ExtirpeDeProfesores, Observable -> no es posible ) Por eso ha decidido construirse su propia interface ISubject. Con respecto a la interface java.util.Observer, sÌ la podrÌa haber utilizado para que sus vacas y ovejas la implementasen en lugar de implementar IObserver, pero ya saben, los genios del mal, ellos se lo guisan y ellos se lo comen.
Los cÛdigos est·n simplificados al m·ximo simplemente para ilustrar como es el patrÛn. Una vez m·s comprobamos que el profesor tiene sus planes muy bien estudiados, no es idiota ni tonto. SÛlo ø est· loco ?.....jajajajajajajajajaja ( ya saben, risas de ultratumba con el fundido a negro y el cartel de the end )
Si quieres, puedes bajar el cÛdigo fuente aquÌ
Est· desencadenado. El primer ataque ha sido lanzado. El profesor Dispar ha dado las Ûrdenes a sus huestes para dominar el mundo. En post anteriores hemos visto como el profesor Dispar ha conseguido clonar cualquier animal ( con gran predilecciÛn por ovejas y vacas ) utilizando un patrÛn prototype, ha conseguido darlas un rol din·micamente con el patrÛn extension objects, y ha repartido las Ûrdenes con un patrÛn command.
Pero como ya sabemos, el profesor Dispar est· loco, pero no es idiota. Sabe, que algo puede salir mal, que un pequeÒo detalle puede truncar sus planes de dominar el mundo. Y tambiÈn sabe que una retirada a tiempo es una victoria.
AsÌ, pues, ha equipado a sus huestes de una radioenigma de campaÒa ( es decir, una radio acompaÒada de una m·quina enigma ). Pero, ø porquÈ ha hecho esto ?. Sencillo, en el fragor de la batalla, las comunicaciones directas se hacen complicadas. Es difÌcil hacer llegar las Ûrdenes, y muchas veces no se tiene claro a quien le estamos dando esas Ûrdenes ( øser· una vaca?, ø ser· una oveja? ).Conquistar el mundo no es f·cil, de hecho muchos lo han intentado pero no lo han conseguido. El profesor Dispar, ha estudiado con detenimiento todas las intentivas anteriores para no reproducir los mismos errores. ø QuÈ pasarÌa si para dar nuevas Ûrdenes, el profesor necesitase que el sargento vaca tuviese que recorrerse la trinchera indicando una por una a todas las vacas y ovejas que se retirasen ?. Desde que le diese la orden a la primera, hasta que se la diese a la ˙ltima, pasarÌa un tiempo precioso. Adem·s, ø y si ocurre algo por el camino ?. Todos hemos jugado al f˙tbol de pequeÒos despuÈs de comer, y sabemos lo que ocurre cuando te pones a correr. Se llama flato. Si nos pasa a nosotros que tenemos un estÛmago, imaginemos las posibilidades que hay de que le pase al sargento vaca que tiene 4. Las posibilidades de que las Ûrdenes no lleguen a todos los integrantes de nuestra tropa son amplias.
Sabiendo esto, el profesor Dispar, que est· loco pero no es idiota, para poder modificar ( actualizar ) f·cil y r·pidamente el comportamiento de sus huestes, utilizar· el patrÛn Observer.
Como el profesor Dispar no es idiota ( aunque este loco y sea un genio del mal, no es idiota ), ha encontrado una malvada forma de enviar las Ûrdenes a sus tropas. Las ovejas y vacas, estar·n equipadas con una radio que sintoniza Cadena Dial ( sÌ, entendemos que es desagradable, pero dominar el mundo tiene estas cosas ). Sus tropas estar·n atentas a la radio, y cuando esta emita una canciÛn de Village People, la m·quina enigma les indicar· quÈ acciÛn deben tomar.
Dj Dispar emitiendo su programa
El cÛdigo ser· algo asÌ
interface IObserver
{
public function update( info: Object ): Void;
}
interface ISubject
{
public function addObserver( obs: IObserver ) : Boolean;
public function removeObserver( obs: IObserver ) : Boolean;
public function notifyObserver( info: Object ) : Void;
}
class Cow implements IObserver
{
function Cow( )
{
this.init( );
}
private function debug( arg: String ): Void
{
trace( "Vaca | Cow: ->"+arg );
}
private function init( ): Void
{
debug( "init" );
}
public function update( info: Object ) : Void
{
debug( "La emisora del profesor ha emitido | song in the radio is: "+info.cancion );
debug( enigmaResult( info.cancion ) );
}
private function enigmaResult( arg: String ): String
{
debug( "soy la m·quina enigma interpretando quÈ hacer | I am the enigma machine" );
if( arg != "In the Navy" )
{
return "sigo a lo mÌo. MUUU | nothing to do MUUUUU";
}
else
{
return "cielos, llegÛ el momento. MUUUUUUUUUUU | Oh, my God, it's the signal MUUU";
}
}
}
class Sheep implements IObserver
{
function Sheep( )
{
this.init( );
}
private function debug( arg: String ): Void
{
trace( "Oveja | Sheep: ->"+arg );
}
private function init( ): Void
{
debug( "init" );
}
public function update( info: Object ) : Void
{
debug( "La emisora del profesor ha emitido | song in the radio is: "+info.cancion );
debug( enigmaResult( info.cancion ) );
}
private function enigmaResult( arg: String ): String
{
debug( "soy la m·quina enigma interpretando quÈ hacer | I am the enigma machine" );
if( arg != "In the Navy" )
{
return "sigo a lo mÌo. BEEEEE | nothing to do BEEEEEE";
}
else
{
return "cielos, llegÛ el momento. BEEEE | Oh, my God, it's the signal BEEEEEE";
}
}
}
class Profesor implements ISubject
{
private var arrayObservers: Array;
function Profesor( )
{
init( );
}
private function debug( arg: String ) : Void
{
trace("Profesor: ->"+arg);
}
private function init( ): Void
{
debug( "init" );
this.arrayObservers = new Array();
}
public function addObserver( obs: IObserver ): Boolean
{
// AquÌ comprobamos que el observer que estamos intentando aÒadir, no est· ya en el array de
// observers. En caso que estÈ, no lo aÒadiremos y retornaremos false. En caso que no estÈ,
// lo aÒadimos y retornamos true.
debug("registrando");
this.arrayObservers.push( obs );
return true;
}
public function removeObserver( obs: IObserver ) : Boolean
{
// comprobamos que el observer a eliminar est· en el array. Que no est·, retornamos false, que est·
// lo eliminamos y retornamos true
var longObservers: Number = this.arrayObservers.length;
for ( var k: Number = 0; k< longObservers; k++ )
{
var tmpObserver: IObserver = this.arrayObservers[ k ];
if( tmpObserver == obs )
{
this.arrayObservers.splice( k, 1 );
break;
}
}
return true;
}
public function notifyObserver( info: Object ): Void
{
var longObservers: Number = this.arrayObservers.length;
for ( var k: Number = 0; k
Y en el primer frame del fla
var pd: Profesor = new Profesor();
var miVaca: Cow = new Cow( );
var miOveja: Sheep = new Sheep( );
pd.addObserver( miVaca );
pd.addObserver( miOveja );
var info:Object = new Object();
info.cancion = "In the Navy";
pd.notifyObserver( info );
De este modo, el profesor, puede comunicar r·pidamente con todas sus tropas. Es brillante, es genial, es maravilloso, es....ø perfecto ?. Bueno, la verdad es que est· muy bien, pero imaginemos, por un momento una situaciÛn en la que una oveja consigue infiltrarse en el alto mando enemigo, y descubre que los enemigos est·n a punto de ser derrotados, y en ese momento de extasis, la radio emite "In the Navy" indicando a las tropas que hay que retirarse. NOOOOOOOOO ( bueno, esto est· traducido, claramente la oveja dirÌa algo como BEEEEEEEEEEE ). Nuestra oveja espÌa, tiene una informaciÛn valiosÌsima, pero que no puede comunicar a su alto mando, porque la forma de transmisiÛn es ˙nicamente de tipo push, del profesor a sus tropas, no hay retorno. Que situaciÛn, la oveja necesita transmitir que el ataque debe continuar pero....
Una oveja tarareando "In the Navy"
ajajajajajajajaj, el profesor Dispar ( loco pero no tonto ) ha pensado en ello, y ha decidido implementar un patrÛn observer que permita la transmisiÛn tipo push ( del sujeto emisor a todos los sujetos receptores ) y tipo pull ( uno de los objetos receptores, puede solicitar una informaciÛn al objeto emisor para que este la envÌe a todos los dem·s ). AsÌ, nuestra oveja espÌa, estar· equipada de un n˙mero de telÈfono al que podr·n llamar y solicitar que emitan "Rasputin" de Boney M, y cuando esta canciÛn sea emitida por la radio, las ovejas y vacas que la escuchen, sabr·n que ha llegado el momento de la ofensiva final. JAJAJAJAJAJAJAJA. Para ello debemos modificar ligeramente nuestros cÛdigos anteriores.
Para empezar nuestro sujeto, implementar· un nuevo mÈtodo que hemos aÒadido a la interfaz ISubject( requestInfo ), que permitir· recibir las llamadas telefÛnicas de la oveja espÌa.Vemos tambiÈn que el constructor de las ovejas y las vacas tambiÈn ha cambiado un poco. Ahora las ovejas y vacas, almacenar·n una referencia al constructor. TambiÈn hemos modificado el cÛdigo de nuestra oveja ( suponemos que esta es la oveja espÌa en el momento en que decide que pese a lo que emite la radio, hay que atacar y pide que se emita Rasputin que es otra seÒal de ataque ).
Por tanto, la interfaz ISubject quedar· asÌ:
interface ISubject
{
public function addObserver( obs: IObserver ) : Boolean;
public function removeObserver( obs: IObserver ) : Boolean;
public function notifyObserver( info: Object ) : Void;
public function requestInfo( arg: String ): Void
}
class Cow implements IObserver
{
private var profesor: Profesor;
function Cow( prof: Profesor )
{
this.profesor = prof;
this.profesor.addObserver(this);
this.init( );
}
private function debug( arg: String ): Void
{
trace( "Vaca | Cow: ->"+arg );
}
private function init( ): Void
{
debug( "init" );
}
public function update( info: Object ) : Void
{
debug( "La emisora del profesor ha emitido | song in the radio is: "+info.cancion );
debug( enigmaResult( info.cancion ) );
}
private function enigmaResult( arg: String ): String
{
debug( "soy la m·quina enigma interpretando quÈ hacer | I am the enigma machine" );
if( arg != "In the Navy" && arg!="Rasputin" )
{
return "sigo a lo mÌo. MUUU | nothing to do MUUUUU";
}
else
{
return "cielos, llegÛ el momento. MUUUUUUUUUUU | Oh, my God, it's the signal MUUU";
}
}
}
class Sheep implements IObserver
{
private var profesor: Profesor;
function Sheep( prof: Profesor )
{
this.profesor = prof;
this.profesor.addObserver(this);
this.init( );
}
private function debug( arg: String ): Void
{
trace( "Oveja | Sheep: ->"+arg );
}
private function init( ): Void
{
debug( "init" );
}
public function update( info: Object ) : Void
{
debug( "La emisora del profesor ha emitido | song in the radio is: "+info.cancion );
debug( "enigmaResult: "+enigmaResult( info.cancion ) );
}
private function enigmaResult( arg: String ): String
{
debug( "soy la m·quina enigma interpretando quÈ hacer | I am the enigma machine" );
if( arg != "In the Navy" && arg!="Rasputin" )
{
debug("un momento, debemos atacar ");
debug("wait a moment, we must attack");
debug("pedirÈ un ataque | I'll ask for an attack ");
askForAttack( );
return "he solicitado un ataque | i've asked for an attack";
//return "sigo a lo mÌo. BEEEEE | nothing to do BEEEEEE";
}
else
{
return "cielos, llegÛ el momento. BEEEE | Oh, my God, it's the signal BEEEEEE";
}
}
private function askForAttack( ): Void
{
this.profesor.requestInfo( "Rasputin" );
}
}
class Profesor implements ISubject
{
private var arrayObservers: Array;
function Profesor( )
{
init( );
}
private function debug( arg: String ) : Void
{
trace("Profesor: ->"+arg);
}
private function init( ): Void
{
debug( "init" );
this.arrayObservers = new Array();
}
public function addObserver( obs: IObserver ): Boolean
{
// AquÌ comprobamos que el observer que estamos intentando aÒadir, no est· ya en el array de
// observers. En caso que estÈ, no lo aÒadiremos y retornaremos false. En caso que no estÈ,
// lo aÒadimos y retornamos true.
debug("registrando");
this.arrayObservers.push( obs );
return true;
}
public function removeObserver( obs: IObserver ) : Boolean
{
// comprobamos que el observer a eliminar est· en el array. Que no est·, retornamos false, que est·
// lo eliminamos y retornamos true
var longObservers: Number = this.arrayObservers.length;
for ( var k: Number = 0; k< longObservers; k++ )
{
var tmpObserver: IObserver = this.arrayObservers[ k ];
if( tmpObserver == obs )
{
this.arrayObservers.splice( k, 1 );
break;
}
}
return true;
}
public function notifyObserver( info: Object ): Void
{
var longObservers: Number = this.arrayObservers.length;
for ( var k: Number = 0; k
Y en el primer frame del fla:
var pd: Profesor = new Profesor();
var miVaca: Cow = new Cow( pd );
var miOveja: Sheep = new Sheep( pd );
var info:Object = new Object();
info.cancion = "Any song";
pd.notifyObserver( info );
Evidentemente, los cÛdigos est·n simplificados al m·ximo simplemente para ilustrar como es el patrÛn. Una vez m·s comprobamos que el profesor tiene sus planes muy bien estudiados, no es idiota ni tonto. SÛlo ø est· loco ?.....jajajajajajajajajaja ( ya saben, risas de ultratumba con el fundido a negro y el cartel de the end )
Si quieres, puedes bajar el cÛdigo fuente aquÌ