Onion, part 3
Women can multitask
No matter how many times you might be told "women can multi-task!" it's just not true, humans can only do one thing at a time. I don’t doubt for a second that my wife's brain can keep track of multiple subjects much better than my single task brain, but at any one point her brain is only concentrating on a single task!It's exactly the same for software. People may wish to deviate from their current task in order to fulfil some adhoc requirement, but that task is an interruption, it does not occur in parallel to what they were doing before. Once that interruption is over the user of your software wants to pick up where they left off. This is what a process driven (or "task oriented") approach to writing software is about.
Process driven work flow
A process in this context is a single task performed within an application in order to achieve a specific goal. The goal may be just about anything such as "Delete customer", "Print invoice" etc. The process may involve only a single step- Are you sure you wish to delete this customer?
- Select invoice
- Select action
- Are you sure you want re-print this invoice?
During its operation the process may call open the functionality of one or more other processes and act accoring to whether the process completed successfully or was cancelled, after the spawned process has terminated control should be handed back to the original process.
To achieve this we will require some kind of first-on-last-off (FILO) stack of processes. When a process is executed it is added to the stack and becomes the "active" process, when a process is completed or cancelled it is removed from the stack; at this point the re-activated process is informed why it has been re-activated, just in case it needs to act according to how the child process it executed came to be removed from the stack, whether it completed or was cancelled.
Reusable UI
My new (cheap) DVD recorder with hard-disk is pretty good, but you might be surprised with the bit that really impressed me! If I click the Browse button I get a list of contents on the hard-disk and pressing Enter will play the file. If I click the Copy (to DVD) button I see exactly the same UI, however, when I press the Enter button it will copy the file to a DVD instead of playing it.You might not think this is very impressive, and maybe I'm just weird, but it is the first real-life example of reusable UI I have seen outside of computer software, in fact it is the first example I can recall seeing at all!
We've all heard of patterns in source code. The thing is, there are also patterns in GUI too. Using the example above the pattern is "Select a recording". This GUI asks the user for a specific type of information (which recording they wish to work with), and this input is used by two separate processes, let's say the "PlayRecordingProcess" and the "CopyRecordingProcess". What happens to the recording selected depends completely on what the process is doing.
Interfaces
I decided that instead of writing an ECO framework I would instead write the framework as a set of interfaces and then implement the classes however I want, my first (and only?) implementation will be an ECO one because the state diagrams save me from having to write a lot of code. I also decided that I would avoid properties in these interfaces, using methods instead. This is so that frameworks such as ECO which persist properties to a database wont attempt to persist the values returned by these interfaces.IProcessStack
The requirements of this interface are- GetActiveProcess : IProcess - Gets the IProcess at the top of the stack, the active process
- GetProcessCount : Integer - Returns the number of processes on the stack
- GetProcess(Index : Integer) : IProcess - Returns the IProcess at the given index
- GetProcessParameters : IProcessParameters - Gets a reference to an object that has properties which should be set by the user (explained below)
- ActiveProcessChanged (event) - Triggered whenever the currently active process changes
- ProcessParametersChanged (event) - Triggered whenever the ProcessParameters property changes, so that the GUI may react by displaying the currect controls to edit its properties
Initially I struggled with this interface. The class that implements it has an ExecuteProcess method. Originally this method accepted an IProcess parameter, but I didn't like that because an ECO implementation would require the Process to be an ECO object in order to have an association to it, and a remoting implementation would require this Process to be something else. If I asserted this restriction at runtime I would not get strong compile time checking.
Then I had a bit of a eureka moment! I was trying to make the interface a generic representation of the class, and this is not what an interface is for! An interface defines "how do I talk to this class" rather than "this is what this class is". We will never need to execute a process in a generic way, the interface is only there so that we can develop a generic GUI or multiple GUIs. Only the application layer itself will actually need to execute processes, this will be done by signals, processes, and ultimately once at the very start of the application when the main process is executed. So, dilemma over, on I go...
IProcess
This interface is very simple- GetProcessStack : IProcessStack - Used to access the process stack
- GetProcessParameters : IProcessParameters - The ProcessStack will use GetActiveProcess.GetProcessParameters to determine what to present to the user
- GetInstanceId : String - Used as a unique identifier for this instance. This can be used in a GUI to identify which target a signal should be sent to
- Activate(ProcessActivationReason) - This method will be called by the ProcessStack whenever the process becomes the active process. The reasons for activation are ProcessExecuted, ActiveProcessCancelled, ActiveProcessCompleted
IProcessParameters
As promised earlier I will explain IProcessParameters. A process may pass through various internal states before reaching its final state and finishing. In some cases the process may be able to obtain all of the information it requires from automated sources (a database, config file, etc) and therefore require nothing from the user. It is more likely however that the user will be required to provide instructions or data at various points.Whenever this situation occurs the Process will update its ProcessParameters reference and execute the ProcessStack.ProcessParametersChanged event. For example
public class AuthenticateUserParameters : IProcessParameters
{
private string userName;
public string UserName
{
get { return userName; }
set { userName = value; }
}
private string password;
public string Password
{
get { return password; }
set { password = value; }
}
Guid IProcessParameters.GetTypeUniqueIdentifier()
{
return new Guid("DEADBEEF-4F89-11D3-9A0C-0305E82C3301");
}
}
When a process needs to authenticate a user it will need the user to enter a username and password. The process would update its GetProcessParameters reference and then trigger the ProcessParametersChanged event.
At this point the UI layer, which has subscribed to this event, will get the GUID that identifies the type of the parameters, this is just a unique ID that says "I need the user to fill out AuthenticateUserParameters". The UI can then find a suitable control to present to the user and databind it to this object.
Once the user has typed in their username and password they would click a UI element that has been created to represent a signal "OK" or "Login" or something like that. This signal will then be sent to process and this will trigger either an internal state change within the process, or an exception explaining that the username or password is incorrect.
Just like with my clever DVD HDD recorder the UI has absolutely no idea why it is being used, all it knows is that it has been asked for certain information and should present this request in a format acceptible to the user. This UI may therefore be reused throughout the application. The application may ask the user to log in initially, and then at a later time the user may exceed some authority level at which point the same ProcessParameters would be used to capture the username and password of the user’s manager before being allowed to continue.
UI patterns appear all over the place. Here are some examples:
- Prompt for a username and password
- Ask the user to confirm or cancel an action. "Are you sure?" Yes/No
- Select an item from a list
- Enter a list of numbers in a grid, such as stock quantities during a stock check, or a purchase order
Summary
This one has been pretty conceptual really. What we have now is a clear separation between database, business objects, application layer, and user interface. This separation not only encourages clearly focused code, but also encourages reuse of both processes and composite UI controls.Using this separation approach also allows us to have multiple UI's without having to implement any logic in the UI (except for code to enable, disable controls etc). In addition this clear separation would allow us to place the layers on different computers. We might have a single database, a farm of application servers, and each client connecting via either the Internet or directly through smart(ish) UI applications, or even a combination of both!
Comments