Forms often have complex logic applied to them. In general, the logic breaks down into the following categories: validation, persistence, and feedback.
A Form Object can encapsulate all associated logic into a single object, keeping it focused, isolated, and easy to test. If you have a signup form that creates an account, an associated Form Object could handle the following logic:
- Make sure all fields that are required are present
- Make sure all values are valid (password is long enough, username isn’t taken, etc.)
- Send the form to the server so the user is saved to the database
- Provide success or error feedback to the user
Placing model validations on the Form Object instead of on a centralized model is perhaps counter-intuitive, since you may have to repeat these validations across multiple Form Objects that affect the same model. However, localizing validations to the form increases code readability (if you’re working on a form, you can just look at the Form Object) but also avoids cluttering your model with logic that is only used in form operations. Form Objects also give you fine-tuned control over the validations in their specific context and do not force you to guard against all scenarios on the model.
In fact, if we think about it on a higher level, we are off-loading the concept of a Model to the Form Objects and treating our "Model" objects like Data Access Objects (DAOs). If this is to be true, there has to be a bond of trust between the Model and the Form Object that what is being sent to the model is pure (i.e. well-formed, valid, complete). From an application architecture standpoint, this can be a really nice design pattern.
So, let's take a look at two examples. One of them demonstrating a full Form Object that covers all form operations and another demonstrating a Validation Object—an object describing validations that can be sequenced with other components.