There are normally various conditions that must be checked before a public method of a problem domain (PD) class can be expected to perform correctly.
These include basic validation of values supplied as parameters to
the method such as:
A good user interface (UI) dynamically enables and disables buttons
and menu options so only the operations permitted are available to the
user. For example, the Approve button is
only enabled on the loan view if the user has authority to approve the
size of loan currently being viewed.
For a user interface to be able to enable and disable operations, PD services are required that return boolean values indicating if the prevailing conditions permit an operation. The user interface does not want to call multiple validation methods catching various exceptions just to set the enabled property of a user interface component to false; the exception catching code has to be repeated wherever the operation can be invoked. This is too much work and change is not localised.
In contrast, a simple user interface may not dynamically
enable/disable buttons and menus, allowing users to invoke operations
without restriction. In this case, users do want more than simply
whether an operation was successful or not. They want a clear and
specific error message that explains why the operation failed. This
user interface, therefore, the user interface wants the PD to throw
specific exceptions; returning a boolean value is not good enough.
Obviously this contradicts the requirement for providing a simple
boolean indication to allow enabling and disabling controls in a more
sophisticated user interface.
Of course, in a non-trivial application some parts of the user
interface may be more sophisticated than others, and the PD cannot
predict where a boolean indication of failure is required and where
detailed exceptions are required.
Whether you are coding in Java, C#, or Objective-C, a simple coding convention can be adopted by PD developers that supports both of the above requirements for a little extra work.
The PD class provides a number of checkOperation()
methods that check the preconditions associated with a method. These
methods return void and throw a specific exception if one of the
pre-conditions is not satisfied. Then for each check method, the PD class also
provides a canOperation() method within a try-catch
statement. Each of these can
methods invokes the equivalent check
method. The can methods
returns false if an exception is thrown by the check method. Otherwise they return
true.
Adopting this convention can obviously lead to the precondition checks being executed several times. Where this degrades performance below an acceptable level, the checks can often be optimised by storing intermediate results in transient attributes of the PD class. For example, a check to determine if the logged in user is the owner of a particular object can be done once and result saved; for a particular session the logged in user and object owner typically do not change. If the owner can change then the method that sets the new owner can also reset the check result.
In practice, this convention is useful in:
In more complex objects, check
methods often delegate to smaller
check methods. The class developer must decide how granular to make the
can methods in such a
scenario. Since the can
methods are simple to
code, if logistics allow, the PD developer can let the UI developers
ask for can methods to be
added when they discover that they are needed.
In addition to the check and can methods, the PD class developer can choose to add checkAllOperation() methods. The check methods stop at the first sign of trouble; as soon as one of the precondition checks fails. The checkAll methods continue checking all pre-conditions, adding each exception caught to a suitable collection. After all conditions have been checked an enumeration over the collection is returned to the caller. An empty enumeration means all checks passed.
The checkAll methods
require the user interface to display a list of problems to user in a
manner somewhat analogous to the output from a compiler. Obviously
there is higher overhead in checking all conditions and these methods
should only be added where appropriate. One example of a suitable place
might be just before a complex loan, license or insurance form is
submitted for approval. This approach avoids the ever-annoying 'fix the
problem, try again, get told about another problem' cycle.
Using check and can methods provides the UI with the flexible validation services it desires, enhances UI and precondition checking consistency and saves UI developers time. The cost is a little extra work in the PD classes and the need to optimise the effects of repeating condition checks in places.
Most of the above derives from discussions on the PowerLender project at United Overseas Bank.