Цель моей задачи - спроектировать небольшую систему, которая может выполнять запланированные повторяющиеся задачи. Повторяющаяся задача - это что-то вроде «отправлять электронное письмо администратору каждый час с 8:00 до 17: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: Если ... Остальное в 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
}
}
}
}
}
Мне сказали, что если ... иначе и приведение, как указано выше, не является ОО, и принесет больше проблем.
Вариант 3: измените подпись Run и создайте 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);
}
}
}
}
Run метод SendEmailTask будет
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 . Метод Run метода SendEmailTask :
public void Run(RecurringTaskVisitor visitor){
visitor.visit(this);
}
Это типичная реализация шаблона посетителя, и посетитель будет вставлен в RecurringTaskScheduler .
Резюмируя: какой из этих четырех подходов является лучшим для моего сценария? И есть ли большая разница между Option3 и Option4 для этой проблемы?
Или у вас есть лучшее представление об этой проблеме? Спасибо!
Обновление 22.05.2015 : Я думаю, что ответ Энди очень хорошо отражает мое намерение; если вы все еще не уверены в самой проблеме, советую сначала прочитать его пост.
Я только что узнал, что моя проблема очень похожа на проблему отправки сообщений , которая приводит к Option5.
Вариант 5 : преобразовать мою проблему в рассылку сообщений .
Между моей проблемой и проблемой отправки сообщений существует взаимно-однозначное соответствие :
Диспетчер сообщений : получение IMessage и отправка подклассов IMessage их соответствующим обработчикам. → RecurringTaskScheduler
IMessage : интерфейс или абстрактный класс. → RecurringTask
MessageA : расширяется от IMessage , имея некоторую дополнительную информацию. → SendEmailTask
MessageB : еще один подкласс IMessage . → CleanDiskTask
MessageAHandler : при получении MessageA обработайте его → SendEmailTaskHandler, который содержит EmailService, и отправит электронное письмо при получении SendEmailTask.
MessageBHandler : то же, что MessageAHandler , но вместо этого обрабатывается MessageB . → CleanDiskTaskHandler
Самое сложное - как отправить разные сообщения IM различным обработчикам. Вот полезная ссылка .
Мне действительно нравится этот подход, он не загрязняет мою сущность служением, и у него нет никакого класса Бога .
SendEmailTask
для меня это больше похоже на услугу, чем на сущность. Я бы пошел на вариант 1 без колебаний.
accept
посещают посетители. Мотивация для посетителя состоит в том, что в некотором агрегате есть много типов классов, которые необходимо посетить, и не удобно изменять их код для каждой новой функциональности (операции). Я до сих пор не вижу, что это за совокупные объекты, и думаю, что Visitor не подходит. Если это так, вы должны отредактировать свой вопрос (который относится к посетителю).