私のタスクの目標は、スケジュールされた繰り返しタスクを実行できる小さなシステムを設計することです。定期的なタスクとは、「月曜日から金曜日の午前8時から午後5時まで、毎時間管理者にメールを送信する」のようなものです。
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から継承されたクラスがいくつかあります。それらの1つは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の If ... Else
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
}
}
}
}
}
もし上記のようにElse and castがOOではないので、さらに問題が発生すると言われています。
オプション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);
}
}
}
}
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に注入されます。
要約すると、これらの4つのアプローチのうち、私のシナリオに最適なのはどれですか。また、この問題について、Option3とOption4の間に大きな違いはありますか?
それとも、この問題についてより良い考えがありますか?ありがとう!
2015年5月22日更新:Andyの回答は私の意図を非常によく要約していると思います。問題自体についてまだ混乱している場合は、まず彼の投稿を読むことをお勧めします。
私の問題がメッセージディスパッチの問題と非常に似ていることがわかりました。これはOption5につながります。
オプション5:問題をメッセージディスパッチに変換します。
私の問題とメッセージディスパッチの問題の間には1対1のマッピングがあります。
メッセージディスパッチャ:受信IMessageがのとディスパッチサブクラスIMessageがをそれらに対応するハンドラに。→RecurringTaskScheduler
IMessage:インターフェースまたは抽象クラス。→RecurringTask
MessageAは:から延びIMessageがいくつかの追加情報を有します。→SendEmailTask
MessageB:IMessageの別のサブクラス。→CleanDiskTask
MessageAHandler:受信MessageAを、→EmailServiceが含まれているSendEmailTaskHandlerを、それを処理し、それがSendEmailTaskを受信したときに電子メールを送信します
MessageBHandler:MessageAHandlerと同じですが、代わりにMessageBを処理します。→CleanDiskTaskHandler
最も難しいのは、さまざまな種類のIMessageをさまざまなハンドラーにディスパッチする方法です。こちらが便利なリンクです。
私はこのアプローチが本当に好きです。サービスでエンティティを汚染することはなく、Godクラスもありません。
SendEmailTask
私にとっては、実体というよりサービスのようです。私は迷わずにオプション1を選びます。
accept
。訪問者の動機は、訪問する必要があるいくつかの集合体に多くのクラス型があり、新しい機能(操作)ごとにコードを変更するのが不便であることです。これらの集約オブジェクトが何であるかはまだわかりません。また、Visitorは適切ではないと思います。その場合は、質問(訪問者を参照)を編集する必要があります。