Decorator pattern
I would actually recommend wrapping ArrayList
using well-documented Decorator pattern. You simply wrap your ArrayList
with another List
implementation that delegates most of the methods but adds validation logic:
public class ValidatingListDecorator extends AbstractList<MyBusinessObject>
{
private final List<MyBusinessObject> target;
public ValidatingListDecorator(List<MyBusinessObject> target) {
this.target = target;
}
@Override
public MyBusinessObject set(int index, MyBusinessObject element)
{
validate(element);
return target.set(index, element);
}
@Override
public boolean add(MyBusinessObject o)
{
validate(o);
return target.add(o);
}
//few more to implement
}
Advantages:
- You can still access raw list without validation if you want (but you can restrict this)
- Easier to stack different validations, turn them on and off selectively.
- Promotes composition over inheritance as noted by @helios
- Improves testability
- Does not tie you to a specific
List
implementation, you can add validation to LinkedList
or Hibernate-backed persistent lists. You can even think about generic Collection
decorator to validate any collection.
Implementation notes
Despite the implementation remember there are quite a lot of methods you have to remember about while overriding: add()
, addAll()
, set()
, subList()
(?), etc.
Also your object must be immutable, otherwise the user can add/set valid object and modify it afterwards to violate the contract.
Good OO design
Finaly I wrote:
validate(element)
but consider:
element.validate()
which is a better design.
Stacking validations
As noted before if you want to stack validations, validating each proprty/apsect in a single, separate class, consider the following idiom:
public abstract class ValidatingListDecorator extends AbstractList<MyBusinessObject>
{
private final List<MyBusinessObject> target;
public ValidatingListDecorator(List<MyBusinessObject> target) {
this.target = target;
}
@Override
public MyBusinessObject set(int index, MyBusinessObject element)
{
validate(element);
return target.set(index, element);
}
protected abstract void validate(MyBusinessObject element);
}
...and few implementations:
class FooValidatingDecorator extends ValidatingListDecorator {
public FooValidatingDecorator(List<MyBusinessObject> target)
{
super(target);
}
@Override
protected void validate(MyBusinessObject element)
{
//throw if "foo" not met
}
}
class BarValidatingDecorator extends ValidatingListDecorator {
public BarValidatingDecorator(List<MyBusinessObject> target)
{
super(target);
}
@Override
protected void validate(MyBusinessObject element)
{
//throw if "bar" not met
}
}
Want to only validate foo?
List<MyBusinessObject> list = new FooValidatingDecorator(rawArrayList);
Want to validate both foo and bar?
List<MyBusinessObject> list =
new BarValidatingDecorator(new FooValidatingDecorator(rawArrayList));