Reality is a perception. Perceptions are not always based on facts, and are strongly influenced by illusions. Inquisitiveness is hence indispensable

Thursday, May 20, 2010

GWT-GXT Building message-event framework/bus

I decided to go with the simplest event model, based on message broad-cast. The fundamental principle is to have a central universal listener and receiver, all interested parties would hang-around this guy.


|--------|
| |
Sender1 -------\ | | |------ Listener 1
Sender2 --------\-----| Event- |------|------ Listener 2
Sender3 --------/-----| Bus |------|------ Listener 3
Sender4 -------/ | | |------ Listener 4
| |
|--------|


Sender


package org.myxyz.listeners;

//import com.google.gwt.event.shared.HandlerManager;
//import com.google.gwt.event.shared.HandlerRegistration;

import org.myxyz.custom.X10HandlerManager;
import org.myxyz.custom.X10HandlerRegistration;

public class MessageSender{

/**
*
*/
private static final long serialVersionUID = -382336739840212254L;

private MessageExchange exchange = MessageExchange.getMessageExchange();
private X10HandlerManager sender = null;

public MessageSender() {
sender = new X10HandlerManager(this);
X10HandlerRegistration handlerRegistration = sender.addHandler(MessageEvent.getType(), exchange);
exchange.registerSender(this, handlerRegistration);
}

/**
* Fire an event.
*/
public void fireEvent(MessageEvent event) {
sender.fireEvent(event);
}

}



MessageListener


package org.myxyz.listeners;

import com.google.gwt.event.shared.EventHandler;

public abstract class MessageListener<T> implements EventHandler{

private MessageExchange exchange = MessageExchange.getMessageExchange();
private T source = null;

public MessageListener(){

}

public MessageListener(T source){
exchange.addHandler(this);
this.source = source;
}

public T getSource(){
return source;
}

public abstract void messageRecieved(MessageEvent pEvent);

}



MessageEvent


package org.myxyz.listeners;

import org.myxyz.custom.X10GwtEvent;

//import com.google.gwt.event.shared.GwtEvent;

@SuppressWarnings("unchecked")
public class MessageEvent extends X10GwtEvent<MessageListener> {

private static final X10GwtEvent.Type<MessageListener> TYPE = new X10GwtEvent.Type<MessageListener>();
private String eventType = null;
private Object newValue = null;
private Object oldValue = null;
private Object source = null;

public static Type<MessageListener> getType() {
return TYPE;
}

public MessageEvent(String eventType) {
super();
this.eventType = eventType;
}

public MessageEvent(String eventType, Object oldValue, Object newValue) {
super();
this.eventType = eventType;
this.oldValue = oldValue;
this.newValue = newValue;
}

public MessageEvent(String eventType, Object oldValue, Object newValue, Object source) {
super();
this.eventType = eventType;
this.oldValue = oldValue;
this.newValue = newValue;
this.source = source;
}

@Override
protected void dispatch(MessageListener handler) {
handler.messageRecieved(this);
}

@Override
public Type<MessageListener> getAssociatedType() {
return TYPE;
}

public String getEventType() {
return eventType;
}

public Object getNewValue() {
return newValue;
}

public Object getOldValue() {
return oldValue;
}

public Object getSource() {
return source;
}
}



MessageExchange



package org.myxyz.listeners;

import java.util.HashMap;
import java.util.Iterator;

import com.google.gwt.event.shared.EventHandler;
import org.myxyz.custom.X10HandlerManager;
import org.myxyz.custom.X10HandlerRegistration;

@SuppressWarnings("unchecked")
public class MessageExchange extends MessageListener {

/**
*
*/
private static final long serialVersionUID = 1623583201346713682L;
private static final MessageExchange singleton = new MessageExchange();

private X10HandlerManager dispatcher = new X10HandlerManager(this);
private HashMap<MessageSender,X10HandlerRegistration> registeredSenders = new HashMap<MessageSender,X10HandlerRegistration>(10);
private HashMap<MessageListener,X10HandlerRegistration> registeredListeners = new HashMap<MessageListener,X10HandlerRegistration>(10);

public static MessageExchange getMessageExchange(){
return singleton;
}

private MessageExchange() {
}

public void messageRecieved(MessageEvent pEvent) {
dispatcher.fireEvent(pEvent);
}

public synchronized <H extends EventHandler> X10HandlerRegistration addHandler(MessageListener handler) {
X10HandlerRegistration handlerRegistration = registeredListeners.get(handler);
if(handlerRegistration == null) {
handlerRegistration = dispatcher.addHandler(MessageEvent.getType(), handler);
registeredListeners.put(handler, handlerRegistration);
}
return handlerRegistration;
}

public int getHandlerCount() {
return dispatcher.getHandlerCount(MessageEvent.getType());
}

public synchronized <H extends EventHandler> void removeHandler(MessageListener handler) {
if(registeredListeners.get(handler) != null){
registeredListeners.get(handler).removeHandler();
registeredListeners.remove(handler);
}
}

public synchronized void registerSender(MessageSender sender, X10HandlerRegistration handlerRegistration){
if(handlerRegistration != null) {
registeredSenders.put(sender, handlerRegistration);
}
}

public synchronized void unRegisterSender(MessageSender sender){
X10HandlerRegistration handlerRegistration = registeredSenders.get(sender);
if(handlerRegistration != null) {
handlerRegistration.removeHandler();
registeredSenders.remove(sender);
}
}

@Override
protected void finalize() throws Throwable {
for (Iterator<X10HandlerRegistration> iterator = registeredListeners.values().iterator(); iterator.hasNext();) {
X10HandlerRegistration handlerRegistration = iterator.next();
handlerRegistration.removeHandler();
}
for (Iterator<X10HandlerRegistration> iterator = registeredSenders.values().iterator(); iterator.hasNext();) {
X10HandlerRegistration handlerRegistration = iterator.next();
handlerRegistration.removeHandler();
}
}

}

GXT-GWT fixing the transmission

Why reinvent the wheel



Simply said, the wheel is ok, the transmission is bad; the chassis is not strong enough. So I am starting with it! Sorry for the uber-sized post. The following three files are actually provided by GWT framework and I had to extend them to fix one issue with the code. I have created a custom package, which contains these files. My event-driven framework is centered around this building block.

Finally, this code is governed by apache licence. (Not me, Google says so). Please respect it, even though it is no rocket science ;)

Customized code from GWT HandlerManager


@SuppressWarnings("unchecked")
private <H extends EventHandler> List<H> handleQueuedAddsAndRemoves() {
List<H> handlers = new ArrayList<H>(10);
if (deferredDeltas != null) {
try {
for (AddOrRemoveCommand c : deferredDeltas) {
EventHandler newHander = c.execute();
/*Here was a problem, that exists no more*/
if(newHander != null){
handlers.add((H) newHander);
}
}
} finally {
deferredDeltas = null;
}
}
return handlers;
}



The complete set of modified files


Extended HandlerManager :: X10HandlerManager


/*
* This is customized version of HandlerManager provided by google. The purpose of this
* extension is to enable user triggered events to bypass ISSUE 101
*
* Fixed ISSUE 101: To support event handling when event handlers are added as part of event processing
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.myxyz.custom;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import org.myxyz.custom.X10GwtEvent.Type;

/**
* Manager responsible for adding handlers to event sources and firing those
* handlers on passed in events.
*/
public class X10HandlerManager {
public static int CONFIGURED_DEPTH = 5;
/**
* Interface for queued add/remove operations.
*/
private interface AddOrRemoveCommand {
/*ISSUE 101*/
public EventHandler execute();
}

/**
* Inner class used to actually contain the handlers.
*/
private static class HandlerRegistry {
private final HashMap<X10GwtEvent.Type<?>, ArrayList<?>> map = new HashMap<X10GwtEvent.Type<?>, ArrayList<?>>();

private <H extends EventHandler> void addHandler(Type<H> type, H handler) {
ArrayList<H> l = get(type);
if (l == null) {
l = new ArrayList<H>();
map.put(type, l);
}
l.add(handler);
}

private <H extends EventHandler> void fireEvent(X10GwtEvent<H> event, boolean isReverseOrder) {
Type<H> type = event.getAssociatedType();
int count = getHandlerCount(type);
if (isReverseOrder) {
for (int i = count - 1; i >= 0; i--) {
H handler = this.<H> getHandler(type, i);
event.dispatch(handler);
}
} else {
for (int i = 0; i < count; i++) {
H handler = this.<H> getHandler(type, i);
event.dispatch(handler);
}
}
}

/* ISSUE 101 */
private <T extends EventHandler> void fireForSkippedHandler(X10GwtEvent<T> event, boolean isReverseOrder, List<T> handlers) {

int count = handlers.size();
if (isReverseOrder) {
for (int i = count - 1; i >= 0; i--) {
T handler = handlers.get(i);
event.dispatch(handler);
}
} else {
for (int i = 0; i < count; i++) {
T handler = handlers.get(i);
event.dispatch(handler);
}
}
}

@SuppressWarnings("unchecked")
private <H> ArrayList<H> get(X10GwtEvent.Type<H> type) {
// This cast is safe because we control the puts.
return (ArrayList<H>) map.get(type);
}

private <H extends EventHandler> H getHandler(X10GwtEvent.Type<H> eventKey, int index) {
ArrayList<H> l = get(eventKey);
return l.get(index);
}

private int getHandlerCount(X10GwtEvent.Type<?> eventKey) {
ArrayList<?> l = map.get(eventKey);
return l == null ? 0 : l.size();
}

private boolean isEventHandled(X10GwtEvent.Type<?> eventKey) {
return map.containsKey(eventKey);
}

private <H> void removeHandler(X10GwtEvent.Type<H> eventKey, H handler) {
ArrayList<H> l = get(eventKey);
boolean result = l.remove(handler);
if (l.size() == 0) {
map.remove(eventKey);
}
assert result : "Tried to remove unknown handler: " + handler + " from " + eventKey;
}
}

private int firingDepth = 0;
private boolean isReverseOrder;

// map storing the actual handlers
private HandlerRegistry registry;

// source of the event.
private final Object source;

// Add and remove operations received during dispatch.
private List<AddOrRemoveCommand> deferredDeltas;

/**
* Creates a handler manager with the given source. Handlers will be fired
* in the order that they are added.
*
* @param source
* the event source
*/
public X10HandlerManager(Object source) {
this(source, false);
}

/**
* Creates a handler manager with the given source, specifying the order in
* which handlers are fired.
*
* @param source
* the event source
* @param fireInReverseOrder
* true to fire handlers in reverse order
*/
public X10HandlerManager(Object source, boolean fireInReverseOrder) {
registry = new HandlerRegistry();
this.source = source;
this.isReverseOrder = fireInReverseOrder;
}

/**
* Adds a handle.
*
* @param <H>
* The type of handler
* @param type
* the event type associated with this handler
* @param handler
* the handler
* @return the handler registration, can be stored in order to remove the
* handler later
*/
public <H extends EventHandler> X10HandlerRegistration addHandler(X10GwtEvent.Type<H> type, final H handler) {
assert type != null : "Cannot add a handler with a null type";
assert handler != null : "Cannot add a null handler";
if (firingDepth > 0) {
enqueueAdd(type, handler);
} else {
doAdd(type, handler);
}

return new X10HandlerRegistration(this, type, handler);
}

/**
* Fires the given event to the handlers listening to the event's type.
*
* Note, any subclass should be very careful about overriding this method,
* as adds/removes of handlers will not be safe except within this
* implementation.
*
* @param event
* the event
*/
public <H extends EventHandler> void fireEvent(X10GwtEvent<H> event) {
// If it not live we should revive it.
if (!event.isLive()) {
event.revive();
}
Object oldSource = event.getSource();
event.setSource(source);
try {
firingDepth++;

registry.fireEvent(event, isReverseOrder);

} finally {
/*ISSUE 101*/
int eventTries = 0;
do {
eventTries++;
List<H> newHandlers = handleQueuedAddsAndRemoves();
/* pass the event to newly added handlers alone */
registry.fireForSkippedHandler(event, isReverseOrder, newHandlers);
} while(eventTries < CONFIGURED_DEPTH && deferredDeltas != null);
}
if (oldSource == null) {
// This was my event, so I should kill it now that I'm done.
event.kill();
} else {
// Restoring the source for the next handler to use.
event.setSource(oldSource);
}

}

/**
* Gets the handler at the given index.
*
* @param <H>
* the event handler type
* @param index
* the index
* @param type
* the handler's event type
* @return the given handler
*/
public <H extends EventHandler> H getHandler(X10GwtEvent.Type<H> type, int index) {
assert index < getHandlerCount(type) : "handlers for " + type.getClass() + " have size: " + getHandlerCount(type)
+ " so do not have a handler at index: " + index;
return registry.getHandler(type, index);
}

/**
* Gets the number of handlers listening to the event type.
*
* @param type
* the event type
* @return the number of registered handlers
*/
public int getHandlerCount(Type<?> type) {
return registry.getHandlerCount(type);
}

/**
* Does this handler manager handle the given event type?
*
* @param e
* the event type
* @return whether the given event type is handled
*/
public boolean isEventHandled(Type<?> e) {
return registry.isEventHandled(e);
}

/**
* Removes the given handler from the specified event type. Normally,
* applications should call {@link HandlerRegistration#removeHandler()}
* instead.
*
* @param <H>
* handler type
*
* @param type
* the event type
* @param handler
* the handler
* @deprecated This method is likely to be removed along with "listener"
* interfaces in a future release. If you have a reason it
* should be retained beyond that time, please add your comments
* to GWT <a href=
* "http://code.google.com/p/google-web-toolkit/issues/detail?id=3102"
* >issue 3102</a>
*/
@Deprecated
public <H extends EventHandler> void removeHandler(X10GwtEvent.Type<H> type, final H handler) {
if (firingDepth > 0) {
enqueueRemove(type, handler);
} else {
doRemove(type, handler);
}
}

/**
* Not part of the public API, available only to allow visualization tools
* to be developed in gwt-incubator.
*
* @return a map of all handlers in this handler manager
*/
Map<X10GwtEvent.Type<?>, ArrayList<?>> createHandlerInfo() {
return registry.map;
}

private void defer(AddOrRemoveCommand command) {
if (deferredDeltas == null) {
deferredDeltas = new ArrayList<AddOrRemoveCommand>();
}
deferredDeltas.add(command);
}

private <H extends EventHandler> void doAdd(X10GwtEvent.Type<H> type, final H handler) {
registry.addHandler(type, handler);
}

private <H extends EventHandler> void doRemove(X10GwtEvent.Type<H> type, final H handler) {
registry.removeHandler(type, handler);
}

private <H extends EventHandler> void enqueueAdd(final X10GwtEvent.Type<H> type, final H handler) {
defer(new AddOrRemoveCommand() {
public EventHandler execute() {
doAdd(type, handler);
return handler;
}
});
}

private <H extends EventHandler> void enqueueRemove(final X10GwtEvent.Type<H> type, final H handler) {
defer(new AddOrRemoveCommand() {
public EventHandler execute() {
doRemove(type, handler);
return null;
}
});
}

@SuppressWarnings("unchecked")
private <H extends EventHandler> List<H> handleQueuedAddsAndRemoves() {
List<H> handlers = new ArrayList<H>(10);
if (deferredDeltas != null) {
try {
for (AddOrRemoveCommand c : deferredDeltas) {
EventHandler newHander = c.execute();
/*ISSUE 101*/
if(newHander != null){
handlers.add((H) newHander);
}
}
} finally {
deferredDeltas = null;
}
}
return handlers;
}
}



Extended GWTEvent :: X10GWTEvent


/*
* This is customized version of GWTEvent provided by google. The purpose of this
* extension is to enable user triggered events to bypass ISSUE 101
*
* Fixed ISSUE 101: To support event handling when event handlers are added as part of event processing
*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.myxyz.custom;

import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;

/**
* Root of all GWT events. All GWT events are considered dead and should no
* longer be accessed once the {@link X10HandlerManager} which originally fired
* the event finishes with it. That is, don't hold on to event objects outside
* of your handler methods.
*
* @param <H>
* handler type
*
*/
public abstract class X10GwtEvent<H extends EventHandler> {
/**
* Type class used to register events with the {@link X10HandlerManager}.
* <p>
* Type is parameterized by the handler type in order to make the addHandler
* method type safe.
* </p>
*
* @param <H>
* handler type
*/
public static class Type<H> {
private static int nextHashCode;
private final int index;

/**
* Constructor.
*/
public Type() {
index = ++nextHashCode;
}

// We override hash code to make it as efficient as possible.
@Override
public final int hashCode() {
return index;
}

@Override
public String toString() {
return "Event type";
}
}

private boolean dead;

private Object source;

/**
* Constructor.
*/
protected X10GwtEvent() {
}

/**
* Returns the type used to register this event. Used by handler manager to
* dispatch events to the correct handlers.
*
* @return the type
*/
public abstract Type<H> getAssociatedType();

/**
* Returns the source that last fired this event.
*
* @return object representing the source of this event
*/
public Object getSource() {
assertLive();
return source;
}

/**
* This is a method used primarily for debugging. It gives a string
* representation of the event details. This does not override the toString
* method because the compiler cannot always optimize toString out
* correctly. Event types should override as desired.
*
* @return a string representing the event's specifics.
*/
public String toDebugString() {
String name = this.getClass().getName();
name = name.substring(name.lastIndexOf(".") + 1);
return "event: " + name + ":";
}

/**
* The toString() for abstract event is overridden to avoid accidently
* including class literals in the the compiled output. Use
* {@link GwtEvent} #toDebugString to get more information about the
* event.
*/
@Override
public String toString() {
return "An event type";
}

/**
* Asserts that the event still should be accessed. All events are
* considered to be "dead" after their original handler manager finishes
* firing them. An event can be revived by calling
* {@link GwtEvent#revive()}.
*/
protected void assertLive() {
assert (!dead) : "This event has already finished being processed by its original handler manager, so you can no longer access it";
}

/**
* Should only be called by {@link X10HandlerManager}. In other words, do
* not use or call.
*
* @param handler
* handler
*/
protected abstract void dispatch(H handler);

/**
* Is the event current live?
*
* @return whether the event is live
*/
protected final boolean isLive() {
return !dead;
}

/**
* Kill the event. After the event has been killed, users cannot really on
* its values or functions being available.
*/
protected void kill() {
dead = true;
source = null;
}

/**
* Revives the event. Used when recycling event instances.
*/
protected void revive() {
dead = false;
source = null;
}

/**
* Set the source that triggered this event.
*
* @param source
* the source of this event, should only be set by a
* {@link X10HandlerManager}
*/
void setSource(Object source) {
this.source = source;
}
}



Extended HandlerRegistration :: X10HandlerRegistration



/*
* This is customized version of HandlerRegistration provided by google. The purpose of this
* extension is to enable user triggered events to bypass ISSUE 101
*
* Fixed ISSUE 101: To support event handling when event handlers are added as part of event processing
*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.myxyz.custom;

import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import org.myxyz.custom.X10GwtEvent.Type;


/**
* Default implementation of {@link HandlerRegistration}.
*/
public class X10HandlerRegistration implements HandlerRegistration {

private final X10HandlerManager manager;
private final EventHandler handler;
private final Type<?> type;

/**
* Creates a new handler registration.
*
* @param <H>
* Handler type
*
* @param manager
* the handler manager
* @param type
* the event type
* @param handler
* the handler
*/
protected <H extends EventHandler> X10HandlerRegistration(X10HandlerManager manager, Type<H> type, H handler) {
this.manager = manager;
this.handler = handler;
this.type = type;
}

/**
* Removes the given handler from its manager.
*/
@SuppressWarnings( { "unchecked", "deprecation" })
// This is safe because when the elements were passed in they conformed to
// Type<H>,H.
public void removeHandler() {
manager.removeHandler((Type<EventHandler>) type, handler);
}

EventHandler getHandler() {
return handler;
}
}

Saturday, May 15, 2010

Event handling in GXT-GWT

This is the way I like to handle events. Note the ease with which the MessageSenders and MessageListeners can be "weaved" using aop/annotations. Unfortunately, I am not going the aop way for the time being.

The big picture:

Architecture diagram of a GWT-GXT application




Using a message listener




Using a message sender


The SubmitButtonListener is a button listener. I tend to separate UI events from business/semantic events. The message sender is used to fire the event. There is another reason why this separation was required, GWT EventHandler code cannot register listeners, while a event is being processed. I re-wrote the custom code and wanted to use it only for semantic events.

Wednesday, April 14, 2010

Event Bus and event handling

Event bus is a rather simple notion, that is of great aid. Think of a telephone network; to communicate between two ends, one would require a line of communication. In most programming languages, an object reference serves this purpose. Having a line of communication between any two end-points quickly becomes a problem with a complexity of O(N^2). Simple combinatorics says we need nC2 lines i.e N(N-1)/2 lines between N unique parties.

Telephone exchanges provide a rather good solution to this. Instead of creating a line between every end-point. We create a line to the exchange for every new party. So N parties would need N lines. This is an O(N) solution, with a linear ordering. How does this look in program terms? There are several solutions, but I prefer the one describe here. I prefer distinguishing MessageSenders, MessageListeners, so I typically create two classes for each. Then I need a MessageExchange, which happens to be a listener as well as a sender. All senders speak with the exchange and all listeners listen against the exchange. The exchange broadcasts any event received to all the parties.

I have ignored one finer aspect of this pattern, The message itself. Messages are typically implementation specific. In java I prefer to use PropertyEvent. This is a generic event which takes three arguments: EventName, OldValue, NewValue. This generic nature of the event suits me really well. I tried incorporating the same philosophy in GWT. The listeners are called MessageHandlers in GWT. However, the GWT message handlers have an inherent problem ( as of GWT 2.0). Whilst an event is being processed, other handlers cannot be hooked up and hence cannot receive events. I had to rewrite the contract to fix this. More on this in the next post.

Monday, March 29, 2010

Event models and Event driven programming

Traditional web applications have a sequential flow, A form which is filled and submitted to controller, and some magic before showing a html page. Rich GUI on the other hand supported event driven flow. A button clicked on form causes the controller/presenter to sense data from widgets (data-binding), then some magic and finally a view is shown to the user. If the logic of rendering is built into view it is MVC, if any intermediate intelligent helper is present it is MVP. Until the advent of AJAX, web applications could not benefit from event driven style of programming. Reason: server-side code could not listen on client side entities.

What is event driven code any way? Lets understand what is not event driven. A program is typically a list of chores. This is procedural. The program is to be thought of a continuous loop only pausing for inputs. Once an input is given the program runs and marks an internal state (session state). Then there is another input, the program again runs and updates the internal state. Some of these states are shown to the user using conditional logic. This is not important, the important point is to note how the program is processing inputs in a sequential pattern. In case you are wondering, I made the most blatant assumptions about multi-tasking abilities of the underlying process. In reality, the inputs are accepted in a different thread/process and passed on using multi-threading and multi-processing. Thus simulating the concurrent execution and support for multi-user paradigm.

In event driven, the program is thought to be comprised of multiple standalone modules each of which can be triggered independently. A user inputs triggers only portions of program, depending on their nature. The state is set in these portions. The user inputs assume no sequential flow. There can be multiple triggers and no assumptions are made about order of triggering. The keyboard is a good analogy, to play a tune the user selects the keys and changes the state. Most real world tasks are event-driven. Unfortunately, in CS the mind set is rather procedural.

Do you see any problems with event-driven approach? Event driven code relies on underlying sequential code. This causes unwanted side-effects. For eg: if the code itself is capable of creating events or user triggers multiple events the order in which events are raised cause a huge furore, nearly impossible to debug as they are difficult to be repeated. I once had a code which ran perfectly in debug mode when I was stepping through and just refused to yield in run mode. Mea-culpa. The solution prevent user-inputs when undesired (by locking the application). Try to queue up events if required.

Another problem, the observer-observable pattern causes a mesh. It is nearly impossible to keep track of who is listening to whom. The whereabouts of event are cryptic when visualising the big picture. Another problem, the observers need a handle/reference to observable. So there would be unwanted reference propagation. The solution, I call it message exchange. I tried to address this issue by making a bulletin analogy. Observables are only observed by the bulletin, in turn the bulletin takes the responsibility of broadcasting the events globally. Observers only listen to bulletin. The bulletin is sort of radio station or telephone exchange.

Think of this mathematical problem, if there are N houses with telephones and each of them need a line to call other, how many lines are required? N(N-1)/2 is the answer. It is O(N^2). For 10 houses it would be 45, for 12 houses it is 66. A difference of 2 and see the extra lines required. With a message exchange the increase is linear. For 10 houses we need 10 lines and for 12 houses it is 12 lines. The penalty is paid in real-world in terms of dropped calls or busy tones. Under the sufficient assumption that computers are fast, we can get away with this. However, it is always a good practice to freeze the application and thus preventing unwanted inputs. One may wonder about concurrency, in a properly designed application the freeze would be applicable for a single user (especially session state). Another advantage of message exchange is the ability to debug/analyse the application. The testability of application also improves greatly.

Sunday, March 28, 2010

Patterns to UI, MVC and MVP

To begin with the most common patterns are MVC and MVP. Model View Controller and Model View Presenter, for the few who may not know. Both of them deal with one major fact, categorising or boxing intelligence. Model represents an idea and actions that can be conducted. Controller performs the actions and decides when/where/whether or not to perform the actions. View is the camera man or the commentator who showcases the whole play. MVC unfortunately is interpreted in a diverse fashion. Presenters on other hand act like the intelligent chaps on whom view can rely. Having a separate intelligent view model changes most MVC architectures to MVP. The view model needs to provide information required for rendering widgets and hence is a composition of other view models. If the view observes view model/presenter it is called passive MVP, on the other hand if view model dictates the changes it is active MVP.

Think of it as an orchestra, model is the theme Bethoven/Mozart/Bollywood dance/Americal Idol etc. Controller is the composer/director. S/he directs/delegates to the actors. The camera just captures the show. To do that effectively, the view needs to understand what is to be shown. The camera doesn't show the whole act - the rehearsals/the makeup/the merchandise are all skipped. This knowledge is obtained from the controller. If the spectators come to a decision based on the view and trigger appropriate action. The action needs to be understood by controller. The dumb view cannot do that, so it has one more trick, a mechanism to convey intent to Controller. Note that view and controller are mostly aware of each other. This is MVC-1, Another option is to keep the view unaware of controller and model making use of observer pattern (pure MVC). Often, as it is perceived that view is a reflection of model rather than controller, the view acts as an observer on model. So changes made by controller on model are immediately shown to user.

The most important facts about MVC now. However, there is an additional overhead in MVC. The view is not a simple reflection of model. View is a bells and whistles representation. Like putting up a high-light color for invalid values or showing a tabular list. Where does this logic go? In traditional MVC, the answer is open for interpretation. Some use another layer of objects say a view model that listens on model and computes the logic required by the dumb view. This can be orchestrated by the controller where view model is another controller(pure MVC) or can be a direct observer on model, thus acting as a view model (MVC-1). Both of them can be called variants of Presentation model (as both address the grievances of view).

MVP is similar but different, Model View Presenter skips the controller part. MVC mostly deals with full-fledged views and doesn't care about widgets. MVP emphasies on widgets and view constituents. In the later view is not a single entity, but a structure of entities. The rational for MVP comes from the observation that, in UI models there is little need for controller arbitration. There is a new guy called presentation model who is understood by views and who manipulates the models. How is it different from Controller. They are similar remember, the controller doesn't speak of widgets, controller speaks of forms and content to be shown. Presenter speaks to widgets. Controller orchestrates the symphony between view and model. Presenter orchestrates only view and observes the model. Most often you realise that view and controller are coupled to form a presenter. MVP is what most of us desire as the explicit flow dictation by controller is too cumbersome (if I am allowed to say so).

An analogy for MVP, think of a refrigerator with automatic defrost feature. The user puts water into the frost shelf. The fridge senses the frost levels time to time (observes) and turns on/off the power. In sense the fridge is a presentor model. If it were MVC, the user puts water in the freezer and explictly states the time (to avoid frost). Checks the frost level and adjusts the cooling level. The interactions are explicit, the fridge doesn't sense changes, the user senses them and in turn becomes the controller.

In your application, you can detect the pattern used (MVC or MVP) by looking for the intelligence built into the code. In case of MVP the presenters are fully automated and pass information to widgets. In case of MVC, the rendering intelligence is pushed to view.

Coffee cup analogy to code/design aesthetics

Code aesthetics are often understated, good developers copy, great developers don't care and greater ones steal. Larry Wall once said: "easy things are easy and hard things are possible". Nothing else in the world gives a better description when it comes to requirement realisation. Most of us have read design patterns and have understood code etiquette. Some great programmers don't, they get away with it owing to their greatness, but the code monster catches up. It is said that if smart code is written, then you need to be 200% smarter to debug it. The essence of people centered development can thus be simplified as writing simple code which other educated peers can easily grasp.

There is often as debate on whether HTML/XML are to be construed as programming languages. To start the discussion they are not turing complete, and one of them is a mark up language and other is a data descriptor. What is turing complete, in simple words any language that can simulate condition branching (if-else) and goto (evil JMP) are turing complete. They in essence can read any arbitrary input and act upon it. HTML and XML don't qualify in this regards. What about GUI? GUI is just an abstract realisation of underlying program. Imagine a coffee cup, the coffee is the essence, the cup is the interface. It is not coffee, however it is a coffee cup when it holds coffee. Without coffee it is any other cup. Same with GUI, gui becomes the app just as coffee-cup is synonymous with coffee.

An application can do several things; a coffee cup can only serve one task, provide access to coffee. Keep this in mind. Most of us do this, and complain about nasty the application is :(. It is the cup that is spoiling the taste not the coffee par say. The cup might be leaking or cracking or lacking an handle, this spoils the whole coffee experience. So the greatness of coffee cannot be appreciated without a good cup. The cup need not be aesthetic, it is not required to be a masterpiece, it needs to do one thing that matters the most.

Popular Posts

Labels

About Me

Well for a start, I dont' want to!. Yes I am reclusive, no I am not secretive; Candid? Yes; Aspergers? No :). My friends call me an enthusiast, my boss calls me purist, I call myself an explorer, to summarise; just an inquisitive child who didnt'learn to take things for granted. For the sake of living, I work as a S/W engineer. If you dont' know what it means, turn back right now.