Le but de ma tâche est de concevoir un petit système capable d'exécuter des tâches récurrentes planifiées. Une tâche récurrente est quelque chose comme "envoyer un e-mail à l'administrateur toutes les heures de 8h00 à 17h00, du lundi au vendredi".
J'ai une classe de base appelée 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(){
}
}
Et j'ai plusieurs classes héritées de RecurringTask . L'un d'eux s'appelle SendEmailTask .
public class SendEmailTask : RecurringTask{
private Email email;
public SendEmailTask(Email email){
this.email = email;
}
public override void Run(){
// need to send out email
}
}
Et j'ai un EmailService qui peut m'aider à envoyer un email.
La dernière classe est RecurringTaskScheduler , elle est chargée de charger les tâches depuis le cache ou la base de données et d'exécuter la tâche.
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();
}
}
}
}
Voici mon problème: où dois-je mettre EmailService ?
Option1 : injecter EmailService dans 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);
}
}
Il y a déjà des discussions sur l'opportunité d'injecter un service dans une entité, et la plupart des gens conviennent que ce n'est pas une bonne pratique. Voir cet article .
Option 2: Si ... Sinon dans 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
}
}
}
}
}
On m'a dit que si ... Sinon et lancer comme ci-dessus n'est pas OO, et apportera plus de problèmes.
Option3: modifiez la signature de Run et créez ServiceBundle .
public class ServiceBundle{
public EmailService EmailService{get;set}
public CleanDiskService CleanDiskService{get;set;}
// and other services for other recurring tasks
}
Injectez cette classe dans 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);
}
}
}
}
La méthode Run de SendEmailTask serait
public void Run(ServiceBundle serviceBundle){
serviceBundle.EmailService.send(this.email);
}
Je ne vois pas de gros problèmes avec cette approche.
Option4 : modèle de visiteur.
L'idée de base est de créer un visiteur qui encapsulera des services comme 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){
//
}
}
Et nous devons également modifier la signature de la méthode Run . La méthode Run de SendEmailTask est
public void Run(RecurringTaskVisitor visitor){
visitor.visit(this);
}
Il s'agit d'une implémentation typique de Visitor Pattern, et le visiteur sera injecté dans RecurringTaskScheduler .
En résumé: Parmi ces quatre approches, laquelle est la meilleure pour mon scénario? Et y a-t-il une grande différence entre Option3 et Option4 pour ce problème?
Ou avez-vous une meilleure idée de ce problème? Merci!
Mise à jour du 22/05/2015 : Je pense que la réponse d'Andy résume très bien mon intention; si vous êtes toujours confus au sujet du problème lui-même, je vous suggère de lire d'abord son article.
Je viens de découvrir que mon problème est très similaire au problème d' envoi de messages , ce qui conduit à l'option 5.
Option 5 : convertir mon problème en envoi de messages .
Il existe un mappage un à un entre mon problème et le problème d' envoi de messages :
Répartiteur de messages : recevez IMessage et envoyez des sous-classes IMessage à leurs gestionnaires correspondants. → RecurringTaskScheduler
IMessage : une interface ou une classe abstraite. → Tâche récurrente
MessageA : S'étend de IMessage , ayant des informations supplémentaires. → SendEmailTask
MessageB : une autre sous-classe de IMessage . → CleanDiskTask
MessageAHandler : lorsque vous recevez MessageA , gérez-le → SendEmailTaskHandler, qui contient EmailService, et enverra un e-mail lorsqu'il recevra SendEmailTask
MessageBHandler : Identique à MessageAHandler , mais gère MessageB à la place. → CleanDiskTaskHandler
La partie la plus difficile est de savoir comment envoyer différents types d' IMessage à différents gestionnaires. Voici un lien utile .
J'aime vraiment cette approche, elle ne pollue pas mon entité avec le service et elle n'a pas de classe divine .
SendEmailTask
me semble plus un service qu'une entité. J'irais pour l'option 1 sans hésitation.
accept
visiteurs. La motivation de Visitor est que vous avez de nombreux types de classes dans certains agrégats qui nécessitent une visite, et il n'est pas pratique de modifier leur code pour chaque nouvelle fonctionnalité (opération). Je ne vois toujours pas ce que sont ces objets agrégés et je pense que Visitor n'est pas approprié. Si c'est le cas, vous devez modifier votre question (qui fait référence au visiteur).