我任务的目标是设计一个可以运行计划的定期任务的小型系统。重复执行的任务类似于“星期一至星期五,从上午8:00到下午5:00,每小时发送一封电子邮件给管理员”。
我有一个名为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(){
}
}
我有几个从RecurringTask继承的类。其中之一称为SendEmailTask。
public class SendEmailTask : RecurringTask{
private Email email;
public SendEmailTask(Email email){
this.email = email;
}
public override void Run(){
// need to send out email
}
}
我有一个EmailService,可以帮助我发送电子邮件。
最后一个类是RecurringTaskScheduler,它负责从缓存或数据库中加载任务并运行任务。
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();
}
}
}
}
这是我的问题:我应该将EmailService放在哪里?
选项1:将EmailService注入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);
}
}
关于是否应该将服务注入实体,已经有一些讨论,并且大多数人都认为这不是一个好习惯。看到这篇文章。
选项2: If ... Else在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
}
}
}
}
}
有人告诉我If ... Else和上面的转换不是OO,这会带来更多问题。
选项3:更改运行的签名并创建ServiceBundle。
public class ServiceBundle{
public EmailService EmailService{get;set}
public CleanDiskService CleanDiskService{get;set;}
// and other services for other recurring tasks
}
将此类注入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);
}
}
}
}
SendEmailTask的Run方法将是
public void Run(ServiceBundle serviceBundle){
serviceBundle.EmailService.send(this.email);
}
我看不到这种方法有什么大问题。
选项4:访客模式。
基本思想是创建一个访问者,该访问者将像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){
//
}
}
并且我们还需要更改Run方法的签名。SendEmailTask的Run方法是
public void Run(RecurringTaskVisitor visitor){
visitor.visit(this);
}
这是访客模式的典型实现,并且访客将被注入RecurringTaskScheduler中。
总结:在这四种方法中,哪种方法最适合我的情况?在此问题上,选项3和选项4之间有什么大区别吗?
还是您对此问题有更好的主意?谢谢!
2015年5月22日更新:我认为Andy的回答很好地总结了我的意图;如果您仍然对问题本身感到困惑,建议您先阅读他的文章。
我刚刚发现我的问题与Message Dispatch问题非常相似,后者导致了Option5。
选项5:将我的问题转换为消息调度。
我的问题和消息调度问题之间存在一对一的映射:
消息调度:接收即时聊天和调度子类的即时聊天到其相应的处理程序。→RecurringTaskScheduler
IMessage:接口或抽象类。→重复任务
MessageA:从IMessage扩展,具有一些其他信息。→SendEmailTask
MessageB:IMessage的另一个子类。→CleanDiskTask
MessageAHandler:接收到MessageA时,对其进行处理→SendEmailTaskHandler,其中包含EmailService,并且在接收到SendEmailTask时将发送一封电子邮件
MessageBHandler:与MessageAHandler相同,但改为处理MessageB。→CleanDiskTaskHandler
最困难的部分是如何将不同类型的IMessage调度到不同的处理程序。这是一个有用的链接。
我真的很喜欢这种方法,它不会因服务而污染我的实体,也没有任何神职人员。
SendEmailTask
对我来说,似乎更像是一种服务而不是实体。我会毫不犹豫地选择选项1。
accept
。对Visitor的动机是,您在某种聚合中有许多类类型需要访问,并且为每种新功能(操作)修改其代码并不方便。我仍然看不到这些聚合对象是什么,并且认为Visitor不适合。如果是这种情况,则应编辑您的问题(指访问者)。