Add a layer or two of abstraction.
Add an interface to wrap the constrained implementations together; something like:
public interface IValidatingRepository
{
public string Key {get; }
public IValidationService ValidationService {get; }
public ICustomerRepository Repository {get; }
//personally, I would prefer to have this operation
// return bool or bool? or give some kind of feedback
//Also, this could be genericized to TDto dto vs CustomerDTO customer
void Save(CustomerDTO customer);
}
Implement IValidatingRepository, something like:
//this could alternately be an abstract base class,
// if you wanted to require custom subclasses for each strategy
public class ValidatingRepository : IValidatingRepository
{
//these could be backed by readonly fields vs private setters if you prefer.
public string Key {get; private set;}
public IValidationService ValidationService {get; private set;}
public ICustomerRepository Repository {get; private set;}
public ValidatingRepository(
string key,
IValidationService validationService,
ICustomerRepository repository
)
{ /* set properties / fields */}
public virtual void Save(CustomerDTO customer)
{
var validationService = ValidationService;
var repository = Repository;
if(customer == null || validationService == null || repository == null)
return; //false? throw? some kind of feedback?
//optional
//if(!Preconditions())
// return; //false?
if (validationService.Valid(customer))
repository.Save(customer); //any return value from repository.Save(...)?
//optional
//return //Postconditions(customer);?
}
//optional
protected virtual bool Preconditions(CustomerDTO customer)
{
if(customer.Type != Key)
return false;
/* any other preconditions */
return true;
}
//optional
protected virtual bool Postconditions(CustomerDTO customer)
{
/* any postconditions */
}
}
Alter the service implementation to have an instance of IValidatingRepository[] (or IEnumerable or other collection of your choice) rather than instances of IValidationService and ICustomerRepository.
Change the ctor to match[1].
public class CustomerService : ICustomerService
{
private readonly IValidatingRepository[] ValidatingRepositorySet;
public CustomerService(IValidatingRepository[] validatingRepositorySet)
{
ValidatingRepositorySet = validatingRepositorySet;
}
public void Save(CustomerDTO customer)
{
if(customer == null)
return;
var validatingRepositorySet = ValidatingRepositorySet;
if(validatingRepositorySet == null)
return;
var key = customer.Type;
var validatingRepository = validatingRepositorySet[key];
if(validatingRepository == null)
return;
validatingRepository.Save(customer);
}
Wherever you are constructing the CustomerService instance, compose together the correct validation service and repository pairs into a collection of IValidatingRepository instances and inject it into the service keyed by Customer.Type or whatever is applicable or works for your usage. If you add more service / repository pairs later no big deal. If you want to put in different / mock sets for testing you can. Etc.
EXTENDED
If your injection mechanism can't handle it or you prefer not to inject in collections directly for some reason, you can add another layer of abstraction by creating another interface to bundle the idea of multiple repositories and a specific corresponding validation service (and potentially other implementation specific concepts later); something like this:
//you could also make this generic for TDto, and generalize
// away from CustomerDto to make this a more general purpose solution
public interface IValidatingRepositorySet
{
//IEnumerable<>, or whatever collection you like
IValidatingRepository[] RepositorySet { get; set; }
//optional; you could move the strategy detection out of the service
// and into the IValidatingRepositorySet implementation.
void Save(CustomerDTO customer);
}
And of course alter the service implementation to have an instance of IValidatingRepositorySet, and the ctor to take in an instance of it, and a default implementation somewhere, etc. Personally, I wouldn't do this unless I had one or more other things that applied to the set as a whole or otherwise belonged at a peer level to the set...such as moving the strategy selection logic out of the service (the optional Save method above).
NOTE: I wrote the code in the comment window on the fly; it's broad strokes only.
[1]Alternately, if for legacy support reasons you must retain the original ctor you can just wrap the passed in IValidationService & ICustomerRepository into a concrete default ValidatingRepository (you'll have to decide on a wildcard key, or have a special case implementation of ValidatingRepository that ignores key or supports an alternate key) that provides a simple implementation identical to the current functionality and set the IValidatingRepositorySet field / property to a new collection containing this instance. I would avoid this if you can, but it is an option.
CustomerService.Save(CustomerDTO customer)
decide which repository to use?