I have the ubiquitous "validate and save" scenario, wherein the Save()
method performs various validity checks and proceeds to save the data. The solution works just fine, but I am interested to know how elegantly and correctly this code can be refactored.
First, the domain:
public class Car
{
public int Id { get; set; }
public decimal Length { get; set; }
public int Years { get; set; }
private List<IValidationRule<Car>> _validationList;
public Car()
{
this._validationList = new List<IValidationRule<Car>>();
this._validationList.Add(new ValidateCarId());
this._validationList.Add(new ValidateCarLength());
}
public void Save()
{
// This commented code will also work!
//_validationList.TrueForAll(x => x.Validate(this));
foreach (var item in _validationList)
{
item.Validate(this);
}
// Proceed to Save the data
Console.WriteLine("Done!");
}
}
Validation interface and validation provider classes:
interface IValidationRule<T>
{
bool Validate(T t);
}
public class ValidateCarId : IValidationRule<Car>
{
public bool Validate(Car t)
{
if (t.Id <= 0)
{
throw new IdNotFoundException("This Id is not available!");
}
else
{
return true;
}
}
}
public class ValidateCarLength : IValidationRule<Car>
{
public bool Validate(Car t)
{
if (t.Length <= 1000m)
{
throw new LengthInvalidException("Length must be more than 1000");
}
else
{
return true;
}
}
}
public class ValidateCarYear : IValidationRule<Car>
{
public bool Validate(Car t)
{
if (t.Years <= 0 || t.Years >= 2)
{
throw new YearInvalidException("Year invalid!");
}
else
{
return true;
}
}
}
Custom exceptions:
public class IdNotFoundException : Exception
{
public IdNotFoundException() { }
public IdNotFoundException(string message) : base(message) { }
public IdNotFoundException(string message, Exception inner) : base(message, inner) { }
protected IdNotFoundException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base(info, context) { }
}
[Serializable]
public class LengthInvalidException : Exception
{
public LengthInvalidException() { }
public LengthInvalidException(string message) : base(message) { }
public LengthInvalidException(string message, Exception inner) : base(message, inner) { }
protected LengthInvalidException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base(info, context) { }
}
[Serializable]
public class YearInvalidException : Exception
{
public YearInvalidException() { }
public YearInvalidException(string message) : base(message) { }
public YearInvalidException(string message, Exception inner) : base(message, inner) { }
protected YearInvalidException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base(info, context) { }
}
And finally, the client:
class Program
{
static void Main(string[] args)
{
Car vw = new Car { Id = -2, Length = 102, Years = 25 };
try
{
vw.Save();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.WriteLine("Passed!");
}
}
PS: I had to provide an else
block in Validate(Cat t)
for _validationList.TrueForAll(x => x.Validate(this));
to work. I felt this can be superfluous. How does it compare with foreach (var item in _validationList)
?