Celem mojego zadania jest zaprojektowanie małego systemu, który może uruchamiać zaplanowane zadania cykliczne. Powtarzającym się zadaniem jest coś takiego: „wysyłaj e-maile do administratora co godzinę od 8:00 do 17:00, od poniedziałku do piątku”.
Mam klasę podstawową o nazwie 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(){
}
}
I mam kilka klas, które są dziedziczone z RecurringTask . Jeden z nich nazywa się SendEmailTask .
public class SendEmailTask : RecurringTask{
private Email email;
public SendEmailTask(Email email){
this.email = email;
}
public override void Run(){
// need to send out email
}
}
I mam EmailService, który może mi pomóc wysłać e-mail.
Ostatnia klasa to RecurringTaskScheduler , odpowiada za ładowanie zadań z pamięci podręcznej lub bazy danych i uruchamianie zadania.
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();
}
}
}
}
Oto mój problem: gdzie powinienem umieścić EmailService ?
Opcja 1 : wstrzyknięcie usługi e- mail do 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);
}
}
Trwają już dyskusje na temat tego, czy powinniśmy wstrzyknąć usługę podmiotowi, a większość ludzi zgadza się, że nie jest to dobra praktyka. Zobacz ten artykuł .
Opcja 2: Jeśli ... Else w 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
}
}
}
}
}
Powiedziano mi, że jeśli ... Inne i obsada jak wyżej nie jest OO i przyniesie więcej problemów.
Opcja 3: Zmień podpis Run i utwórz ServiceBundle .
public class ServiceBundle{
public EmailService EmailService{get;set}
public CleanDiskService CleanDiskService{get;set;}
// and other services for other recurring tasks
}
Wstrzyknij tę klasę do 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 metoda SendEmailTask byłoby
public void Run(ServiceBundle serviceBundle){
serviceBundle.EmailService.send(this.email);
}
Nie widzę większych problemów z tym podejściem.
Opcja 4 : Wzorzec gościa.
Podstawową ideą jest stworzenie gościa, który będzie zawierał usługi podobnie jak 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){
//
}
}
Musimy także zmienić podpis metody Run . Run metoda SendEmailTask jest
public void Run(RecurringTaskVisitor visitor){
visitor.visit(this);
}
Jest to typowa implementacja Wzorca Odwiedzającego, a odwiedzający zostanie wstrzyknięty do RecurringTaskScheduler .
Podsumowując: Które z tych czterech podejść jest najlepsze dla mojego scenariusza? Czy istnieje jakaś duża różnica między Opcją 3 a Opcją 4 w przypadku tego problemu?
A może masz lepszy pomysł na ten problem? Dzięki!
Aktualizacja 22.05.2015 : Myślę, że odpowiedź Andy'ego bardzo dobrze podsumowuje moją intencję; jeśli nadal masz wątpliwości co do samego problemu, sugeruję najpierw przeczytać jego post.
Właśnie dowiedziałem się, że mój problem jest bardzo podobny do problemu Wysyłania wiadomości , który prowadzi do Opcji 5.
Opcja 5 : Konwertuj mój problem na wysyłanie wiadomości .
Pomiędzy moim problemem a problemem wysłania wiadomości istnieje mapowanie jeden do jednego :
Wiadomość Dyspozytor : Otrzymuj iMessage i wysyłka sub klas iMessage do odpowiadających im koparki. → RecurringTaskScheduler
WIADOMOŚĆ : Interfejs lub klasa abstrakcyjna. → Zadanie cykliczne
MessageA : Rozszerza z iMessage , mając jakieś dodatkowe informacje. → SendEmailTask
MessageB : Inna podklasa iMessage . → CleanDiskTask
MessageAHandler : Po otrzymaniu MessageA , obsłuż go → SendEmailTaskHandler, który zawiera EmailService i wyśle wiadomość e-mail, gdy otrzyma SendEmailTask
MessageBHandler : Taki sam jak MessageAHandler , ale zamiast tego obsłuż MessageB . → CleanDiskTaskHandler
Najtrudniej jest jak wysyłką inny rodzaj iMessage różne ładowarki. Oto przydatny link .
Naprawdę podoba mi się to podejście, nie zanieczyszcza ono mojej istoty służbą i nie ma żadnej klasy Boga .
SendEmailTask
wydaje mi się bardziej usługą niż bytem. Wybrałbym opcję 1 bez wahania.
accept
odwiedzają. Motywacją dla Odwiedzającego jest to, że masz wiele typów klas w niektórych agregatach, które wymagają odwiedzenia, i nie jest wygodne modyfikowanie ich kodu dla każdej nowej funkcjonalności (operacji). Nadal nie widzę, czym są te obiekty agregujące, i uważam, że Odwiedzający nie jest odpowiedni. W takim przypadku powinieneś edytować swoje pytanie (które odnosi się do odwiedzającego).