O objetivo da minha tarefa é projetar um pequeno sistema que possa executar tarefas recorrentes agendadas. Uma tarefa recorrente é algo como "envie um email para o administrador a cada hora, das 8:00 às 17:00, de segunda a sexta-feira".
Eu tenho uma classe base chamada 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 eu tenho várias classes que são herdadas de RecurringTask . Um deles é chamado 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 eu tenho um EmailService que pode me ajudar a enviar um email.
A última classe é RecurringTaskScheduler , é responsável por carregar tarefas do cache ou banco de dados e executar a tarefa.
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();
}
}
}
}
Aqui está o meu problema: onde devo colocar o EmailService ?
Opção 1 : Inject EmailService em 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);
}
}
Já existem algumas discussões sobre se devemos injetar um serviço em uma entidade e a maioria das pessoas concorda que não é uma boa prática. Veja este artigo .
Option2: If ... Else em 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
}
}
}
}
}
Foi-me dito se ... Else e elenco como acima não é OO, e trará mais problemas.
Opção3: altere a assinatura de Executar e crie ServiceBundle .
public class ServiceBundle{
public EmailService EmailService{get;set}
public CleanDiskService CleanDiskService{get;set;}
// and other services for other recurring tasks
}
Injete esta classe no 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);
}
}
}
}
O método Run de SendEmailTask seria
public void Run(ServiceBundle serviceBundle){
serviceBundle.EmailService.send(this.email);
}
Não vejo grandes problemas com essa abordagem.
Opção4 : padrão de visitante.
A idéia básica é criar um visitante que encapsule serviços como o 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 também precisamos alterar a assinatura do método Run . O método Run de SendEmailTask é
public void Run(RecurringTaskVisitor visitor){
visitor.visit(this);
}
É uma implementação típica do Padrão do Visitante, e o visitante será injetado no RecurringTaskScheduler .
Em resumo: dentre essas quatro abordagens, qual é a melhor para o meu cenário? E há alguma grande diferença entre a Opção3 e a Opção4 para esse problema?
Ou você tem uma idéia melhor sobre esse problema? Obrigado!
Atualização em 22/05/2015 : Acho que a resposta de Andy resume muito bem minha intenção; Se você ainda está confuso sobre o problema em si, sugiro ler o post dele primeiro.
Acabei de descobrir que meu problema é muito semelhante ao problema do Envio de Mensagens , o que leva à Option5.
Opção 5 : Converter meu problema em Envio de mensagens .
Há um mapeamento individual entre meu problema e o problema de Envio de Mensagens :
Despachante de mensagens : receba sub-classes IMessage e despache- IMessage para seus manipuladores correspondentes. → RecurringTaskScheduler
IMessage : Uma interface ou uma classe abstrata. → RecurringTask
MessageA : estende-se do IMessage , com algumas informações adicionais. → SendEmailTask
MessageB : Outra subclasse de IMessage . → CleanDiskTask
MessageAHandler : Quando receber MessageA , manipule-o → SendEmailTaskHandler, que contém EmailService, e enviará um email quando receber SendEmailTask
MessageBHandler : O mesmo que MessageAHandler , mas manipula MessageB . → CleanDiskTaskHandler
A parte mais difícil é como despachar diferentes tipos de IMessage para diferentes manipuladores. Aqui está um link útil .
Eu realmente gosto dessa abordagem, ela não polui minha entidade com serviço e não possui nenhuma classe de Deus .
SendEmailTask
parece mais um serviço do que uma entidade para mim. Eu iria para a opção 1 sem e hesitação.
accept
é visitantes. A motivação para o Visitor é que você tem muitos tipos de classe em alguns agregados que precisam ser visitados, e não é conveniente modificar o código para cada nova funcionalidade (operação). Ainda não vejo o que são esses objetos agregados e acho que o Visitor não é apropriado. Se for esse o caso, você deve editar sua pergunta (que se refere ao visitante).