As I see it you have 2 choices.
- You stop invalid cars being constructed, in which case the rules for a valid car need to be part of the car class
- You use a factory to create the cars and the factory delegates to a
CarValidator
to validate the cars after construction. This means validation is separate (which seems to be something you value) but means that cars could be built in an invalid state. This might be ok if validation is contextual and your cars are reused and 'valid' means something different in one place or another.
Which of these is most applicable I think that fundamentally it comes down to what is 'valid' in your situation and whether validity is universally applicable. The truth probably means a bit of both.
For example it might be that having an empty model is invalid for all cars, but that some model colour combinations are invalid at the moment. In this case your cars constructor would throw an exception if someone tried to create a car which had an empty string for the model, as this is never valid.
Your validator might check that the car is valid after construction to ensure that the model/colour combination you have specified exists at the moment. These rules might change and so would be more applicable to external validation (you may have different acceptable model in certain countries or different validations rules based on some other factors). These can live in your CarValidator
class(es).
Any fundamental invariants of your object that parts of your program will rely on (like the fact that the model is not empty, should be validated and enforced by the object itself, as these are not 'business rules' per-se but invariants of the system.
I'd likely go with:
public class CarFactory
{
private ICarValidator validator;
public CarFactory(ICarValidator validator){ this.validator=validator;}
public Car Create(string model)
{
Car car = new Car(model);
if (validator.IsValid(car))
return car;
throw new InvalidCarException(car);
}
}
public Car
{
private string model;
public Car(string model)
{
if (model=="")
throw new EmptyModelException();
this.model=model;
}
}
Which gives you separation of you invariants (enforced by Car
) from your business logic (encapsulated in CarValidator
) and disallows your users from getting hold of invalid car instances at all.