CEIBO

Miércoles 19/marzo/2008

Ejemplo: Un examen en línea con Smalltalk Web Toolkit (SWT)

Filed under: Programación — Hernan Galante @ 4:08 pm

Introducción

En este framework, basado en la arquitectura Comet, tenemos dos clases principales para hacer un aplicativo, SWTClientApplication y SWTServerApplication. Para comenzar vamos a realizar un pequeño ejemplo sobre lo que sería una aplicación de Exámenes en línea. Donde se muestra una pregunta a la vez, y varias posibles soluciones. Al final de contestar las preguntas da el puntaje obtenido. Este tutorial se dividirá en dos partes, en la primer parte haremos este aplicativo con un modelo en el servidor más inactivo y el cliente web con más mucha más actividad y funcionalidad. En la segunda parte, haremos uso del MVC distribuido que ofrece el SWT, donde mostraremos como mejorar considerablemente la cantidad de mensajes y la forma ineficiente con respecto a la primer parte.

Ejemplo: Exámenes en línea

El modelo

Para comenzar vamos a desarrollar un modelo simple del aplicativo. Un examen se compone de una pregunta y múltiples respuestas, donde cada respuesta puede ser la más acertada para responder o simplemente es incorrecta. Por estándar vamos a tener cinco respuestas por cada pregunta, y cada respuesta tiene un valor. Para hacer los cálculos más simples, vamos a poner un rango de puntuación entre 0 y 10 puntos.

La respuesta es un objeto simple que tiene la respuesta que contiene con su puntaje asociado, y una pregunta es un objeto con una pregunta y una colección de respuestas.

Para ello, vamos a crear un workspace con las expresiones que queremos que funcionen:

| question answers |

answers := OrderedCollection new.

answers

add: (SWTAnswerModel answer: ‘Object’ score: 0);

add: (SWTAnswerModel answer: ‘Collection’ score: 0);

add: (SWTAnswerModel answer: ‘Set’ score: 10);

add: (SWTAnswerModel answer: ‘Bag’ score: 0);

add: (SWTAnswerModel answer: ‘SWTModel’ score: 0);

yourself.

question := SWTQuestionModel question: ‘¿De quién hereda Dictionary?’ answers: answers.

Como vemos en workspace anterior ya tenemos un pequeño borrador de como sería nuestro modelo. Ahora, cada vez que un usuario responde vamos a agregarle que la pregunta sepa la respuesta que eligió el usuario, para ello, agregamos una variable de instancia que nos permita recordar que respondió a esa pregunta. La variable la vamos a denominar answerChoosed.

| question answers |

answers := OrderedCollection new.

answers

add: (SWTAnswerModel answer: ‘Object’ score: 0);

add: (SWTAnswerModel answer: ‘Collection’ score: 0);

add: (SWTAnswerModel answer: ‘Set’ score: 10);

add: (SWTAnswerModel answer: ‘Bag’ score: 0);

add: (SWTAnswerModel answer: ‘SWTModel’ score: 0);

yourself.

question := SWTQuestionModel question: ‘¿De quien hereda Dictionary?’ answers: answers.

question answerChoosed: (answers at: 3).

Si el usuario no sabe contestar la pregunta, pondremos una respuesta por defecto, que valdrá cero puntos, y es No sabe y/o No contesta. Esto lo vamos a hacer en el getter de #answerChoosed

SWTQuestionModel>>answerChoosed

” Returns the answer choosed by the user to the receiver.”

answerChoosed isNil ifTrue: [ self answerChoosed: SWTAnswerModel notAnswered ].

^answerChoosed

Ahora bien, en nuestro modelo ya tenemos preguntas, posibles respuestas y la respuesta que se elige. Esto funciona para solo una pregunta, pero un examen, consta de muchas preguntas, y dado, que generalmente una certificación, tiene al menos unas 40 preguntas, vamos a modelar nuestro objeto examen. Este objeto tiene una colección de preguntas, y sabe calcular el puntaje final. Vamos a denominar SWTExamModel.

Para calcular el puntaje final lo hacemos así:

En el examen

SWTExamModel>>score

” Returns the score of the receiver.”

^self questions inject: 0 into: [ :finalScore :question | question score + finalScore ]

En la pregunta

SWTQuestionModel>>score

” Returns the score of the receiver.”

^self answerChoosed score

Hasta aquí, ya tenemos un modelo funcional, tenemos un examen, que se compone de preguntas, a su vez, las preguntas tiene posibles respuestas, y el usuario por cada pregunta elige su respuesta. El objeto respuesta sabe que puntaje tiene. Entonces el examen solo delega en sus preguntas saber los puntajes obtenidos, y estas a su vez, usan de colaborador a nuestras respuestas.

Nuestro examen aún le falta algo más, si ya respondí una pregunta, tengo que esperar la siguiente, así que vamos a decirle al examen, que nos de una pregunta más, para hacerlo más interesante aún, que sea al azar, y lógicamente, como es azar, no quiero responder más de una vez una pregunta, por lo tanto, el examen deberá quitar las que ya se respondieron. Para ello, agregamos este comportamiento a examen. Vamos a tener variables de instancia a questions, que son las preguntas totales, y otra colección con las preguntas ya usadas, questionsAnswered.

SWTExamModel>>questionsNotAnswered

” Returns the collection of questions not answered yet of the receiver.”

| newQuestions |

newQuestions := self questions copy.

newQuestions removeAll: self questionsAnswered.

^newQuestions

Obtener una nueva pregunta:

SWTExamModel>>newQuestion

” Returns a new question for the exam.”

| newQuestion randomIndex maximumInteger |

randomIndex := Random seed: 2345678901.

[ maximumInteger := self questionsNotAnswered size.

maximumInteger = 0 ifTrue: [ ^nil ].

newQuestion := self questionsNotAnswered at: (randomIndex nextInt: maximumInteger).

self questionsAnswered includes: newQuestion] whileTrue: [].

self currentQuestion: newQuestion.

self questionsAnswered add: self currentQuestion.

^self currentQuestion

Para probar lo que hemos hecho hasta el momento, vamos a hacer un workspace donde tengamos un examen con 3 preguntas y las contestaremos. Por ultimo, evaluaremos el puntaje final.

| exam questions answers questionToAnswer |

questions := OrderedCollection new.

answers := OrderedCollection new.

answers

add: (SWTAnswerModel answer: ‘Object’ score: 0);

add: (SWTAnswerModel answer: ‘Collection’ score: 0);

add: (SWTAnswerModel answer: ‘Set’ score: 10);

add: (SWTAnswerModel answer: ‘Bag’ score: 0);

add: (SWTAnswerModel answer: ‘SWTModel’ score: 0);

yourself.

questions add: (SWTQuestionModel question: ‘¿De quien hereda Dictionary?’ answers: answers).

answers := OrderedCollection new.

answers

add: (SWTAnswerModel answer: ‘un objeto’ score: 0);

add: (SWTAnswerModel answer: ‘una clase’ score: 0);

add: (SWTAnswerModel answer: ‘una variable global’ score: 10);

add: (SWTAnswerModel answer: ‘El nombre del producto’ score: 0);

add: (SWTAnswerModel answer: ‘una nueva distro de Linux’ score: 0);

yourself.

questions add: (SWTQuestionModel question: ‘Que es Smalltalk?’ answers: answers).

answers := OrderedCollection new.

answers

add: (SWTAnswerModel answer: ‘la clase del 3′ score: 0);

add: (SWTAnswerModel answer: ‘la clase Class’ score: 0);

add: (SWTAnswerModel answer: ‘Integer class’ score: 10);

add: (SWTAnswerModel answer: ‘un error’ score: 0);

add: (SWTAnswerModel answer: ‘nil’ score: 0);

yourself.

questions add: (SWTQuestionModel question: ‘que devuelve 3 class class?’ answers: answers).

exam := SWTExamModel questions: questions.

questionToAnswer := exam newQuestion.

questionToAnswer answerChoosed: (questionToAnswer answers at: 1).

questionToAnswer := exam newQuestion.

questionToAnswer answerChoosed: (questionToAnswer answers at: 3).

questionToAnswer := exam newQuestion.

questionToAnswer answerChoosed: (questionToAnswer answers at: 3).

exam score

El resultado obtenido nos debería devolver 20 puntos. Pues la primer pregunta la contestamos incorrectamente, y las dos subsecuentes correctamente.

La interfaz web con SWT sin MVC distribuido

La interfaz de nuestro examen, será bien simple también, solo tenemos que mostrar nuestra pregunta, y las posibles respuestas para que las pueda elegir el usuario. La forma de elegir será a través de un botón por ahora. Cada vez que responde una pregunta, tenemos que continuar a la siguiente, hasta que termine y mostrar el puntaje final obtenido. Para lograr esto, vamos a modelar nuestra página a través de una clase que hereda de SWTClientApplication. La vamos a denominar SWTClientExamApplication, y desde el servidor vamos a tener a su controlador, llamado SWTServerExamApplication.

En el cliente, vamos a tener la pregunta como modelo, y en el server al examen.

SWTServerExamApplication>>exam

” Returns the exam of the receiver.”

exam isNil ifTrue: [ self initializeExam ].

^exam

SWTServerExamApplication>>initializeExam

” Initialize the exam.”

| questions answers |

questions := OrderedCollection new.

answers := OrderedCollection new.

answers

add: (SWTAnswerModel answer: ‘Object’ score: 0);

add: (SWTAnswerModel answer: ‘Collection’ score: 0);

add: (SWTAnswerModel answer: ‘Set’ score: 10);

add: (SWTAnswerModel answer: ‘Bag’ score: 0);

add: (SWTAnswerModel answer: ‘SWTModel’ score: 0);

yourself.

questions add: (SWTQuestionModel question: ‘De quien hereda Dictionary?’ answers: answers).

answers := OrderedCollection new.

answers

add: (SWTAnswerModel answer: ‘un objeto’ score: 0);

add: (SWTAnswerModel answer: ‘una clase’ score: 0);

add: (SWTAnswerModel answer: ‘una variable global’ score: 10);

add: (SWTAnswerModel answer: ‘El nombre del producto’ score: 0);

add: (SWTAnswerModel answer: ‘una nueva distro de Linux’ score: 0);

yourself.

questions add: (SWTQuestionModel question: ‘Que es Smalltalk?’ answers: answers).

answers := OrderedCollection new.

answers

add: (SWTAnswerModel answer: ‘la clase del 3′ score: 0);

add: (SWTAnswerModel answer: ‘la clase Class’ score: 0);

add: (SWTAnswerModel answer: ‘Integer class’ score: 10);

add: (SWTAnswerModel answer: ‘un error’ score: 0);

add: (SWTAnswerModel answer: ‘nil’ score: 0);

yourself.

questions add: (SWTQuestionModel question: ‘que devuelve 3 class class?’ answers: answers).

self exam: (SWTExamModel questions: questions).

SWTServerExamApplication>>newQuestion

” Returns a new question of the receiver.”

^self exam newQuestion.

Una parte muy importante, dado que el cliente, es un mini-Smalltalk, pues todo lo que estamos escribiendo en nuestra clase cliente, a la hora de funcionar, esto se traduce a Javascript. El framework posee un traductor de Smalltalk a Javascript bastante complejo, y como Smalltalk y Javascript son asimétricos, es decir, no todo el potencial del Smalltalk puede ser traducido a funcionalidad en Javascript, debemos tenerlo en cuenta a la hora de poner comportamiento en el cliente.

Todas las clases de nuestro modelo que sean usadas en el cliente, debemos enumerarlas en un mensaje de clase #jsClassesToInclude. Para que el traductor, genere estas clases del modelo en nuestro mini-Smalltalk en Javascript. Para nuestro ejemplo, vamos a hacer uso de las preguntas y las respuestas, por consiguiente, redefinimos este mensaje:

SWTClientExamApplication class>>jsClassesToInclude

^ {SWTQuestionModel. SWTAnswerModel }

Ahora en el cliente, haremos nuestra vista

SWTClientExamApplication>>newQuestion

” Returns the a new question of the receiver and updates the questions panel of the receiver.”

self isolatedAsynchronousRPCMethods:

[ self question: self serverSide newQuestion.

self updateQuestionsPanel ]

Ahora, lo que haremos son los widgets de la vista, esto se hace redefiniendo un método #initializeWidgets.

SWTClientExamApplication>>initializeWidgets

” Initialize the widgets of the receiver.”

| root |

root := self rootWidget.

root

addWidget: (questionsPanel := SWTPanel new).

En este método hemos definido una variable de instancia que guardara un SWTPanel, que es quien contendrá las preguntas, y a medida que el usuario las contesta, entonces, usaremos este panel para actualizar los datos en el navegador.

SWTClientExamApplication>>updateQuestionsPanel

” Update the questions panel of the receiver.”

questionsPanel clearWidgets.

self question isNil

ifTrue:

[ ^questionsPanel

addWidget: (SWTHeader contents: ‘Puntaje final: ‘ , self finalScore)].

questionsPanel

addWidget: (SWTText

contents: ‘Pregunta: ‘ , (self question perform: #question));

addHorizontalRule.

(self question perform: #answers) do:

[:answer |

questionsPanel addWidget: (SWTButton caption: (answer perform: #answer)

onClick: [:event | self answerChoosed: answer])]

Un detalle que debemos notar de este #updateQuestionsPanel, es que si la pregunta es nula, es decir, ya no tenemos más preguntas para mostrar, entonces mostramos el puntaje final obtenido del usuario.

Para ello definimos el mensaje:

SWTClientExamApplication>>finalScore

” Returns the final score of the receiver.”

^self serverSide score

A la hora que se conecta el cliente con el server, vamos a hacer que este se inicialize con la primer pregunta para que la muestre, para engancharnos de este evento, redefiniremos un mensaje que nos provee el framework, el #justConnected. De esta manera nos aseguramos que solo la pregunta será nula, cuando el examen haya finalizado, dado que cuando se conecta por primera vez el cliente, se inicializará con la primer pregunta del examen.

SWTClientExamApplication>>justConnected

” On just connected the receiver.”

super justConnected.

self newQuestion.


Lo que hemos hecho es decirle, una vez que te conectás con el servidor, envíame una pregunta, e inicializamos el MainPanel, que es nuestro panel donde mostramos las preguntas.

¡Muy bien! Ahora ya tenemos nuestro examen listo ¡Es hora de probarlo!

Screenshot tutorial pregunta

Y la pantalla final del puntaje:

Screenshot puntaje final non MVC

Resumen

Este ejemplo es muy improductivo e ineficiente, porque, como se puede observar en la ultima imagen, usa demasiados mensajes al servidor, y no usa el MVC distribuido que nos proporciona el framework. En nuestra segunda sección, haremos que este mismo ejemplo, sea hecho con el MVC distribuido, mejorando considerablemente la cantidad de mensajes que usan el servidor a través de eventos.

¡La cantidad de peticiones que realizó fueron 30! En la siguiente sección vamos a explorar y refactorizar ciertos mensajes haciendo uso no solo del MVC distribuido sino también de un modelo en el servidor más activo que en la primer parte.

La interfaz web con SWT con MVC distribuido

Una vez que sabemos como podemos hacerlo funcionar, ahora, vamos a hacerlo funcionar bien.

Vamos a usar el mismo modelo, con algunas modificaciones, pero usando el Model-View-Controller distribuido que provee el framework, el cual, está optimizado para Internet, dado que un mal uso del servidor, con muchos usuarios, sería muy caótico, además de desaprovechar el framework.

Creando nuevas clases

Para empezar vamos a redefinir todo la parte web en otras clases. Las vamos a denominar SWTClientMVCExamApplication y SWTServerMVCExamApplication a las nuevas vistas con MVC.

SWTClientApplication subclass: #SWTClientMVCExamApplication

instanceVariableNames: ‘exam questionsPanel’

classVariableNames:

poolDictionaries:

category: ‘TutorialSWT-SWT’

Y el servidor

SWTServerApplication subclass: #SWTServerMVCExamApplication

instanceVariableNames: ‘exam’

classVariableNames:

poolDictionaries:

category: ‘TutorialSWT-SWT’

Modificando el modelo

A nuestro modelo anterior, tenemos que agregarle la parte escencial del MVC, que es que nos avise cuando un evento se produce en él. Las dos únicas situaciones importante en el modelo, que son de interés para nuestra vista, son cuando se gestiona una nueva pregunta (y esto es porque el usuario ya respondió la anterior o porque comienza el examen) y cuando se termina el examen. Estas son las dos situaciones donde necesitamos que nos avise el modelo, y a las cuales, la vista se suscribirá para tomar acción en ellas.

Para ello modificamos el modelo haciendo que la pregunta actual que está siendo mostrada por el sistema al usuario la guardemos en una variable de instancia del examen, cosa que antes no hacíamos, entonces nuestra clase de examen quedaría de esta manera:

SWTModel subclass: #SWTExamModel

instanceVariableNames: ‘questions questionsAnswered currentQuestion’

classVariableNames:

poolDictionaries:

category: ‘TutorialSWT-Core’

y los mensajes que modificaremos serán #newQuestion y

SWTExamModel>>newQuestion

” Returns a new question for the exam.”

| newQuestion randomIndex maximumInteger |

randomIndex := Random seed: 2345678901.

[ maximumInteger := self questionsNotAnswered size.

maximumInteger = 0 ifTrue: [ self triggerEvent: #examFinished. ^nil ].

newQuestion := self questionsNotAnswered at: (randomIndex nextInt: maximumInteger).

self questionsAnswered includes: newQuestion] whileTrue: [].

self currentQuestion: newQuestion.

self questionsAnswered add: self currentQuestion.

self triggerEvent: #newQuestion.

^self currentQuestion


Como se puede observar en el código del método, lo que hemos agregado es el uso de la variable de instancia #currentQuestion a través de sus mensajes de acceso, y hemos puesto los eventos, el primero denominado #examFinished, que se dispara cuando ya terminaron las preguntas, y el otro #newQuestion, que se dispara cuando hay una nueva pregunta disponible.

Vamos a agregar el mensaje al examen #answerChoosed:, donde se la pasa la respuesta seleccionada y genera una nueva pregunta.

SWTExamModel>>answerChoosed: anAnswer

self currentQuestion answerChoosed: anAnswer.

self newQuestion.


Se puede apreciar que ya en este nuevo modelo, levemente modificado, un circuito, donde una vez que se contesta una pregunta, se inicia otra, si se termino, avisa, y si está comenzando, con solo llamar a generar una nueva pregunta damos comienzo al examen. Con este pequeño flujo de trabajo, vamos a rediseñar nuestro Servidor y nuestro Cliente para bajar las 30 peticiones que hace el cliente al servidor.

La vista con MVC

Esta nueva vista, al momento que se conecta con el servidor, le pedirá su modelo (un exámen), registrará los eventos y luego dará comienzo al examen.

SWTClientMVCExamApplication>>justConnected

” On just connected the receiver.”

super justConnected.

self exam: self serverSide exam.

self registerEvents.

self serverSide startExam


La suscripción a los eventos del modelo

SWTClientMVCExamApplication>>registerEvents

” Register the events with the model of the receiver.”

self exam

when: #examFinished send: #showFinalScore to: self;

when: #newQuestion send: #showQuestion to: self.


Y en el lado de la clase, vamos a decirle al framework que solo vamos a pasar el examen hacia la vista como modelo.

SWTClientMVCExamApplication class>>jsClassesToInclude

^ {SWTExamModel}

En este método, cuando se produce un evento u otro, llama a dos mensajes distintos dentro de la vista: #showFinalScore y #showQuestion.

Si terminó el examen, entonces se ejecuta:

SWTClientMVCExamApplication>>showFinalScore

” Shows the final score in the questions panel of the receiver.”

self isolatedAsynchronousRPCMethods:

[ questionsPanel

clearWidgets;

addWidget: (SWTHeader contents: ‘Puntaje final: ‘ , self serverSide score printString) ]

En este método, estamos haciendo que esta operación del cliente sea ejecutada en modo batch de una sola vez contra el servidor. Esto es unas de las características que brinda el framework para reducir el tráfico de datos.

Cada vez que hay una nueva pregunta se ejecuta:

SWTClientMVCExamApplication>>showQuestion

” Shows the questions panel of the receiver.”

questionsPanel clearWidgets.

questionsPanel

addWidget: (SWTText contents: ‘Pregunta: ‘ , (self serverSide currentQuestionString));

addHorizontalRule.

(self serverSide currentQuestionAnswersString) inlineDo:

[:answerString |

questionsPanel addWidget: ((SWTButton caption: answerString)

onClick: [:event | self serverSide answerChoosed: event source caption ])]

En esta método, lo que está realizando es que cada vez que se ejecuta, pide al servidor la pregunta, las posibles respuestas y arma la web.

Armando el servidor con su respectivo examen

En el servidor, vamos a definir #startExam, que lo unico que hará es decirle al modelo que comienze el examen por medio de la generación de una nueva pregunta

SWTServerMVCExamApplication>>startExam

” Start the current exam.”

self exam newQuestion

Los dos siguientes mensajes retornan los textos necesarios para la pregunta y sus posibles respuestas

SWTServerMVCExamApplication>>currentQuestionString

” Returns the current question strings.”

^self exam currentQuestion question

El cliente enviará al servidor el siguiente mensaje cuando la respuesta se haya seleccionado:

SWTServerMVCExamApplication>>answerChoosed: anAnswerString

” Set the answer choosed from the button caption clicked.”

self exam

answerChoosed: (self exam currentQuestion answers detect: [ :answer | answer answer = anAnswerString ])

¡Muy bien! Ahora ya tenemos nuestro nuevo examen listo ¡Es hora de probarlo!

Screenshot puntaje final MVC

Como podemos analizar de ver en la imagen anterior, la cantidad de pedidos al servidor se vieron reducidos en un 60%, lo que es un gran ahorro de tráfico para cualquier tipo de aplicativo.

Resumen

Como hemos visto a través de este ejemplo, el framework permite hacer aplicativos web en forma muy sencilla, sin ningún uso otras tecnologias más que Smalltalk y usando objetos. Y como se puede comprobar, el uso de un MVC distribuido es eficaz y nos permite tener muchas más ventajas, como multiples vista (N cantidad de navegadores sobre un mismo modelo), optimización de tráfico de datos a través de la red y una actualización sin necesidad de que el cliente lo tenga que peticionar al servidor.

Dejar un comentario »

Aún no hay comentarios.

RSS feed for comments on this post. TrackBack URI

Responder

Por favor, inicia sesión con uno de estos métodos para publicar tu comentario:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Crea un blog o un sitio web gratuitos con WordPress.com.

A %d blogueros les gusta esto: