Aggiungi un livello o due di astrazione.
Aggiungi un'interfaccia per racchiudere le implementazioni vincolate; qualcosa come:
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);
}
Implementa il repository IValidating, ad esempio:
//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 */
}
}
Modificare l'implementazione del servizio in modo da avere un'istanza di IValidatingRepository [] (o IEnumerable o altra raccolta di propria scelta) anziché le istanze di IValidationService e ICustomerRepository.
Cambia il ctor in modo che corrisponda a [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);
}
Ovunque tu stia costruendo l'istanza di CustomerService, componi insieme le coppie di servizio e di deposito corrette in una raccolta di istanze di IValidatingRepository e inseriscile nel servizio immesso da Customer.Type o qualsiasi altra cosa sia applicabile o funzioni per il tuo utilizzo. Se si aggiungono più coppie servizio / repository in seguito, nessun grosso problema. Se vuoi inserire diversi set / mock per i test, puoi farlo. Ecc.
EXTENDED
Se il tuo meccanismo di iniezione non è in grado di gestirlo o preferisci non inserirlo direttamente nelle raccolte per qualche motivo, puoi aggiungere un altro livello di astrazione creando un'altra interfaccia per raggruppare l'idea di più repository e un servizio di convalida corrispondente specifico ( e potenzialmente altri concetti specifici di implementazione in seguito); qualcosa di simile:
//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);
}
E naturalmente modificare l'implementazione del servizio per avere un'istanza di IValidatingRepositorySet, e il ctor per prendere un'istanza di esso, e un'implementazione predefinita da qualche parte, ecc. Personalmente, non lo farei se non ne avessi uno o più altre cose che si applicavano all'insieme nel suo complesso o appartenevano a un livello peer al set ... come lo spostamento della logica di selezione della strategia dal servizio (il metodo di salvataggio opzionale in alto).
NOTA: ho scritto il codice nella finestra dei commenti al volo; è solo a tratti ampi.
[1] In alternativa, se per ragioni di supporto legacy è necessario conservare il ctor originale, è sufficiente racchiudere il passato in IValidationService & ICustomerRepository in un concreto ValidatingRepository predefinito (dovrai decidere su una chiave jolly, o avere un'implementazione caso speciale di ValidatingRepository che ignora la chiave o supporta una chiave alternativa) che fornisce un'implementazione semplice identica alla funzionalità corrente e imposta il campo IValidatingRepositorySet / proprietà a una nuova raccolta contenente questa istanza. Eviterei questo se puoi, ma è un'opzione.