L'obiettivo della mia attività è progettare un piccolo sistema in grado di eseguire attività ricorrenti pianificate. Un'attività ricorrente è qualcosa come "inviare un'e-mail all'amministratore ogni ora dalle 8:00 alle 17:00, dal lunedì al venerdì".
Ho una classe base chiamata RecurringTask .
public abstract class RecurringTask{
// I've already figured out this part
public bool isOccuring(DateTime dateTime){
// implementation
}
// run the task
public abstract void Run(){
}
}
E ho diverse classi che sono ereditate da RecurringTask . Uno di questi si chiama SendEmailTask .
public class SendEmailTask : RecurringTask{
private Email email;
public SendEmailTask(Email email){
this.email = email;
}
public override void Run(){
// need to send out email
}
}
E ho un EmailService che può aiutarmi a inviare un'e-mail.
L'ultima classe è RecurringTaskScheduler , è responsabile del caricamento delle attività dalla cache o del database ed esecuzione dell'attività.
public class RecurringTaskScheduler{
public void RunTasks(){
// Every minute, load all tasks from cache or database
foreach(RecuringTask task : tasks){
if(task.isOccuring(Datetime.UtcNow)){
task.run();
}
}
}
}
Ecco il mio problema: dove devo mettere EmailService ?
Opzione 1 : Iniettare EmailService in SendEmailTask
public class SendEmailTask : RecurringTask{
private Email email;
public EmailService EmailService{ get; set;}
public SendEmailTask (Email email, EmailService emailService){
this.email = email;
this.EmailService = emailService;
}
public override void Run(){
this.EmailService.send(this.email);
}
}
Ci sono già alcune discussioni sull'opportunità di iniettare un servizio in un'entità e la maggior parte delle persone concorda sul fatto che non è una buona pratica. Vedere questo articolo .
Opzione2: If ... Else in RecurringTaskScheduler
public class RecurringTaskScheduler{
public EmailService EmailService{get;set;}
public class RecurringTaskScheduler(EmailService emailService){
this.EmailService = emailService;
}
public void RunTasks(){
// load all tasks from cache or database
foreach(RecuringTask task : tasks){
if(task.isOccuring(Datetime.UtcNow)){
if(task is SendEmailTask){
EmailService.send(task.email); // also need to make email public in SendEmailTask
}
}
}
}
}
Mi è stato detto se ... Altrimenti e il cast come sopra non è OO, e porterà più problemi.
Opzione 3: modifica la firma di Esegui e crea ServiceBundle .
public class ServiceBundle{
public EmailService EmailService{get;set}
public CleanDiskService CleanDiskService{get;set;}
// and other services for other recurring tasks
}
Iniettare questa classe in RecurringTaskScheduler
public class RecurringTaskScheduler{
public ServiceBundle ServiceBundle{get;set;}
public class RecurringTaskScheduler(ServiceBundle serviceBundle){
this.ServiceBundle = ServiceBundle;
}
public void RunTasks(){
// load all tasks from cache or database
foreach(RecuringTask task : tasks){
if(task.isOccuring(Datetime.UtcNow)){
task.run(serviceBundle);
}
}
}
}
Il metodo Run di SendEmailTask sarebbe
public void Run(ServiceBundle serviceBundle){
serviceBundle.EmailService.send(this.email);
}
Non vedo grossi problemi con questo approccio.
Opzione 4 : modello visitatore.
L'idea di base è creare un visitatore che incapsuli i servizi proprio come ServiceBundle .
public class RunTaskVisitor : RecurringTaskVisitor{
public EmailService EmailService{get;set;}
public CleanDiskService CleanDiskService{get;set;}
public void Visit(SendEmailTask task){
EmailService.send(task.email);
}
public void Visit(ClearDiskTask task){
//
}
}
E dobbiamo anche cambiare la firma del metodo Run . Il metodo Run di SendEmailTask è
public void Run(RecurringTaskVisitor visitor){
visitor.visit(this);
}
È un'implementazione tipica del modello visitatore e il visitatore verrà iniettato in RecurringTaskScheduler .
In sintesi: tra questi quattro approcci, qual è il migliore per il mio scenario? E ci sono grandi differenze tra Opzione 3 e Opzione 4 per questo problema?
O hai un'idea migliore su questo problema? Grazie!
Aggiornamento 22/05/2015 : Penso che la risposta di Andy riassuma davvero bene le mie intenzioni; se sei ancora confuso sul problema stesso, ti suggerisco di leggere prima il suo post.
Ho appena scoperto che il mio problema è molto simile al problema di Message Dispatch , che porta a Option5.
Opzione 5 : converti il mio problema in Invio messaggio .
Esiste un mapping uno a uno tra il mio problema e il problema di invio dei messaggi :
Message Dispatcher : riceve IMessage e invia sottoclassi di IMessage ai gestori corrispondenti. → RecurringTaskScheduler
IMessage : un'interfaccia o una classe astratta. → Task ricorrenti
Messaggio A : si estende da IMessage , con alcune informazioni aggiuntive. → InviaEmailTask
Messaggio B : un'altra sottoclasse di IMessage . → CleanDiskTask
MessageAHandler : quando ricevi MessageA , gestiscilo → SendEmailTaskHandler, che contiene EmailService, e invierà un'email quando riceve SendEmailTask
MessageBHandler : uguale a MessageAHandler , ma gestisce invece MessageB . → CleanDiskTaskHandler
La parte più difficile è il modo di spedire diversi tipi di IMessage a diversi gestori. Ecco un link utile .
Mi piace molto questo approccio, non inquina la mia entità con il servizio e non ha alcuna classe divina .
SendEmailTask
mi sembra più un servizio che un'entità. Vorrei scegliere l'opzione 1 senza esitazione.
accept
è visitatori. La motivazione per Visitatore è che in alcuni aggregati ci sono molti tipi di classe che devono essere visitati e non è conveniente modificare il loro codice per ogni nuova funzionalità (operazione). Continuo a non vedere cosa siano quegli oggetti aggregati e penso che Visitatore non sia appropriato. In tal caso, è necessario modificare la domanda (che si riferisce al visitatore).