Ist das Besuchermuster in diesem Szenario gültig?

9

Das Ziel meiner Aufgabe ist es, ein kleines System zu entwerfen, das geplante wiederkehrende Aufgaben ausführen kann. Eine wiederkehrende Aufgabe ist etwa "Senden Sie von Montag bis Freitag stündlich von 8.00 bis 17.00 Uhr eine E-Mail an den Administrator".

Ich habe eine Basisklasse namens 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(){

    }
}

Und ich habe mehrere Klassen, die von RecurringTask geerbt werden . Eine davon heißt SendEmailTask .

public class SendEmailTask : RecurringTask{
    private Email email;

    public SendEmailTask(Email email){
        this.email = email;
    }

    public override void Run(){
        // need to send out email
    }
}

Und ich habe einen EmailService, der mir beim Versenden einer E-Mail helfen kann.

Die letzte Klasse ist RecurringTaskScheduler . Sie ist dafür verantwortlich, Aufgaben aus dem Cache oder der Datenbank zu laden und die Aufgabe auszuführen.

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();
            }
        }
    }
}

Hier ist mein Problem: Wo soll ich EmailService platzieren ?

Option 1 : Injizieren Sie EmailService in 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);
    }
}

Es gibt bereits einige Diskussionen darüber, ob wir einem Unternehmen einen Service hinzufügen sollten, und die meisten Menschen sind sich einig, dass dies keine gute Praxis ist. Siehe diesen Artikel .

Option 2: Wenn ... sonst in 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
                }
            }
        }
    }
}

Mir wurde gesagt, wenn ... Sonst und Besetzung wie oben ist nicht OO, und wird mehr Probleme bringen.

Option 3: Ändern Sie die Signatur von Ausführen und erstellen Sie ServiceBundle .

public class ServiceBundle{
    public EmailService EmailService{get;set}
    public CleanDiskService CleanDiskService{get;set;}
    // and other services for other recurring tasks

}

Fügen Sie diese Klasse in RecurringTaskScheduler ein

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);
            }
        }
    }
}

Die Run- Methode von SendEmailTask wäre

public void Run(ServiceBundle serviceBundle){
    serviceBundle.EmailService.send(this.email);
}

Ich sehe keine großen Probleme mit diesem Ansatz.

Option 4 : Besuchermuster.
Die Grundidee besteht darin, einen Besucher zu erstellen, der Dienste wie ServiceBundle kapselt .

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){
        //
    }
}

Außerdem müssen wir die Signatur der Run- Methode ändern . Die Run- Methode von SendEmailTask lautet

public void Run(RecurringTaskVisitor visitor){
    visitor.visit(this);
}

Dies ist eine typische Implementierung des Besuchermusters, und der Besucher wird in RecurringTaskScheduler eingefügt .

Zusammenfassend: Welcher dieser vier Ansätze ist der beste für mein Szenario? Und gibt es für dieses Problem einen großen Unterschied zwischen Option3 und Option4?

Oder haben Sie eine bessere Vorstellung von diesem Problem? Vielen Dank!

Update 22.05.2015 : Ich denke, Andys Antwort fasst meine Absicht wirklich gut zusammen. Wenn Sie immer noch verwirrt sind über das Problem selbst, empfehle ich, zuerst seinen Beitrag zu lesen.

Ich habe gerade herausgefunden, dass mein Problem dem Problem des Nachrichtenversands sehr ähnlich ist , was zu Option 5 führt.

Option 5 : Konvertieren Sie mein Problem in Message Dispatch .
Es gibt eine Eins-zu-Eins-Zuordnung zwischen meinem Problem und dem Problem des Nachrichtenversands :

Message Dispatcher : Empfangen Sie IMessage und senden Sie Unterklassen von IMessage an die entsprechenden Handler. → RecurringTaskScheduler

IMessage : Eine Schnittstelle oder eine abstrakte Klasse. → RecurringTask

MessageA : Erweitert sich aus IMessage und enthält einige zusätzliche Informationen. → SendEmailTask

NachrichtB : Eine weitere Unterklasse von IMessage . → CleanDiskTask

MessageAHandler : Wenn Sie MessageA empfangen , behandeln Sie es → SendEmailTaskHandler, das EmailService enthält, und senden Sie eine E-Mail, wenn SendEmailTask ​​empfangen wird

MessageBHandler : Wie MessageAHandler , jedoch stattdessen MessageB verarbeiten . → CleanDiskTaskHandler

Der schwierigste Teil ist, wie verschiedene Arten von IMessage an verschiedene Handler gesendet werden. Hier ist ein nützlicher Link .

Ich mag diesen Ansatz wirklich, er verschmutzt mein Wesen nicht mit Dienst und es gibt keine Gottklasse .

Sher10ck
quelle
Sie haben keine Sprache oder Plattform markiert, aber ich empfehle, sich mit cron zu befassen . Ihre Plattform verfügt möglicherweise über eine Bibliothek, die ähnlich funktioniert (z. B. jcron, das nicht mehr funktioniert ). Das Planen von Jobs und Aufgaben ist größtenteils ein gelöstes Problem: Haben Sie andere Optionen geprüft, bevor Sie Ihre eigenen rollen? Gab es Gründe, sie nicht zu benutzen?
@Snowman Möglicherweise wechseln wir später zu einer ausgereiften Bibliothek. Es hängt alles von meinem Manager ab. Der Grund, warum ich diese Frage stelle, ist, dass ich einen Weg finden möchte, um diese Art von Problem zu lösen. Ich habe diese Art von Problem mehr als einmal gesehen und konnte keine elegante Lösung finden. Also frage ich mich, ob ich etwas falsch gemacht habe.
Sher10ck
Fairerweise versuche ich immer, die Wiederverwendung von Code zu empfehlen, wenn dies möglich ist.
1
SendEmailTaskscheint mir eher eine Dienstleistung als eine Einheit zu sein. Ich würde ohne zu zögern für Option 1 gehen.
Bart van Ingen Schenau
3
Was (für mich) für Besucher fehlt, ist die Klassenstruktur, die acceptBesucher sind. Die Motivation für Besucher ist, dass Sie viele Klassentypen in einem Aggregat haben, die besucht werden müssen, und es nicht bequem ist, ihren Code für jede neue Funktionalität (Operation) zu ändern. Ich sehe immer noch nicht, was diese aggregierten Objekte sind, und denke, dass Besucher nicht angemessen ist. Wenn dies der Fall ist, sollten Sie Ihre Frage bearbeiten (die sich auf den Besucher bezieht).
Fuhrmanator

Antworten:

4

Ich würde sagen, Option 1 ist der beste Weg. Der Grund, warum Sie es nicht entlassen sollten, ist, dass SendEmailTaskes sich nicht um eine Entität handelt. Eine Entität ist ein Objekt, das Daten und Status enthält. Ihre Klasse hat sehr wenig davon. Tatsächlich ist es keine Entität, aber es enthält eine Entität: das EmailObjekt, das Sie speichern. Das heißt, das Emailsollte keinen Dienst annehmen oder eine #SendMethode haben. Stattdessen sollten Sie über Dienste verfügen, die Entitäten wie Ihre übernehmen EmailService. Sie verfolgen also bereits die Idee, Dienste von Entitäten fernzuhalten.

Da SendEmailTaskes sich nicht um eine Entität handelt, ist es daher vollkommen in Ordnung, die E-Mail und den Dienst in sie einzufügen, und dies sollte über den Konstruktor erfolgen. Durch die Konstruktorinjektion können wir sicher sein, dass SendEmailTaskdas Unternehmen immer bereit ist, seine Aufgabe auszuführen.

Schauen wir uns nun an, warum Sie die anderen Optionen nicht ausführen sollten (insbesondere in Bezug auf SOLID ).

Option 2

Ihnen wurde zu Recht gesagt, dass eine solche Verzweigung mehr Kopfschmerzen verursachen wird. Schauen wir uns an, warum. Erstens ifneigen s dazu, sich zu sammeln und zu wachsen. Heute ist es eine Aufgabe, E-Mails zu senden. Morgen benötigt jede andere Art von Klasse einen anderen Dienst oder ein anderes Verhalten. Das Verwalten dieser ifAussage wird zum Albtraum. Da wir nach Typ (und in diesem Fall nach explizitem Typ ) verzweigen , untergraben wir das in unsere Sprache integrierte Typsystem.

Option 2 ist nicht Single Responsibility (SRP), da der früher wiederverwendbare Benutzer RecurringTaskSchedulernun über all diese verschiedenen Arten von Aufgaben und über alle Arten von Diensten und Verhaltensweisen Bescheid wissen muss, die er möglicherweise benötigt. Diese Klasse ist viel schwieriger wiederzuverwenden. Es ist auch nicht offen / geschlossen (OCP). Da es über diese oder jene Art von Aufgabe (oder diese oder jene Art von Dienst) Bescheid wissen muss, können unterschiedliche Änderungen an Aufgaben oder Diensten hier Änderungen erzwingen. Neue Aufgabe hinzufügen? Neuen Service hinzufügen? Ändern Sie die Art und Weise, wie E-Mails behandelt werden? Ändern RecurringTaskScheduler. Da die Art der Aufgabe von Bedeutung ist, wird die Liskov-Substitution (LSP) nicht eingehalten. Es kann nicht einfach eine Aufgabe bekommen und erledigt werden. Es muss nach dem Typ fragen und basierend auf dem Typ dies oder das tun. Anstatt die Unterschiede in die Aufgaben einzubeziehen, ziehen wir all das in die RecurringTaskScheduler.

Option 3

Option 3 hat einige große Probleme. Selbst in dem Artikel, auf den Sie verlinken , rät der Autor davon ab:

  • Sie können weiterhin einen statischen Service Locator verwenden…
  • Ich vermeide Service Locator, wenn ich kann, besonders wenn der Service Locator statisch sein muss…

Sie erstellen mit Ihrer Klasse einen Service LocatorServiceBundle . In diesem Fall scheint es nicht statisch zu sein, aber es weist immer noch viele der Probleme auf, die einem Service Locator inhärent sind. Ihre Abhängigkeiten sind jetzt darunter versteckt ServiceBundle. Wenn ich dir die folgende API meiner coolen neuen Aufgabe gebe:

class MyCoolNewTask implements RecurringTask
{
    public bool isOccuring(DateTime dateTime) {
        return true; // It's always happenin' here!
    }

    public void Run(ServiceBundle bundle) {
        // yeah, some awesome stuff here
    }
}

Welche Dienste nutze ich? Welche Dienste müssen in einem Test verspottet werden? Was hindert mich daran, jeden Dienst im System zu nutzen, nur weil?

Wenn ich Ihr Task-System zum Ausführen einiger Aufgaben verwenden möchte, bin ich jetzt von jedem Dienst in Ihrem System abhängig, auch wenn ich nur wenige oder gar keine verwende.

Das ServiceBundleist nicht wirklich SRP, weil es über jeden Dienst in Ihrem System Bescheid wissen muss. Es ist auch nicht OCP. Das Hinzufügen neuer Dienste bedeutet Änderungen an der ServiceBundleund Änderungen an der ServiceBundlekönnen unterschiedliche Änderungen an Aufgaben an anderer Stelle bedeuten. ServiceBundletrennt seine Schnittstelle (ISP) nicht. Es verfügt über eine weitläufige Schnittstelle all dieser Dienste, und da es nur ein Anbieter für diese Dienste ist, können wir davon ausgehen, dass die Schnittstelle auch die Schnittstellen aller von ihm bereitgestellten Dienste umfasst. Aufgaben unterliegen nicht mehr der Abhängigkeitsinversion (DIP), da ihre Abhängigkeiten hinter dem verschleiert sind ServiceBundle. Dies entspricht auch nicht dem Prinzip des geringsten Wissens (auch bekannt als das Gesetz von Demeter), da die Dinge über viel mehr Dinge wissen, als sie müssen.

Option 4

Zuvor hatten Sie viele kleine Objekte, die unabhängig voneinander arbeiten konnten. Option 4 nimmt alle diese Objekte und zerlegt sie zu einem einzigen VisitorObjekt. Dieses Objekt fungiert bei all Ihren Aufgaben als Gottobjekt . Es reduziert Ihre RecurringTaskObjekte auf anämische Schatten, die einfach einen Besucher ansprechen. Das gesamte Verhalten bewegt sich zum Visitor. Müssen Sie das Verhalten ändern? Müssen Sie eine neue Aufgabe hinzufügen? Ändern Visitor.

Der schwierigere Teil ist, dass, da alle verschiedenen Verhaltensweisen in einer einzigen Klasse sind, einige polymorphe Änderungen entlang des gesamten anderen Verhaltens gezogen werden. Zum Beispiel möchten wir zwei verschiedene Möglichkeiten zum Senden von E-Mails haben (sollten sie möglicherweise unterschiedliche Server verwenden?). Wie würden wir das machen? Wir könnten eine IVisitorSchnittstelle erstellen und diesen Code möglicherweise duplizieren, wie #Visit(ClearDiskTask)von unserem ursprünglichen Besucher. Wenn wir dann eine neue Methode zum Löschen einer Festplatte finden, müssen wir sie erneut implementieren und duplizieren. Dann wollen wir beide Arten von Änderungen. Implementieren und erneut duplizieren. Diese beiden unterschiedlichen Verhaltensweisen sind untrennbar miteinander verbunden.

Vielleicht könnten wir stattdessen einfach eine Unterklasse bilden Visitor? Unterklasse mit neuem E-Mail-Verhalten, Unterklasse mit neuem Festplattenverhalten. Bisher keine Vervielfältigung! Unterklasse mit beiden? Jetzt muss das eine oder andere dupliziert werden (oder beides, wenn Sie dies bevorzugen).

Vergleichen wir mit Option 1: Wir brauchen ein neues E-Mail-Verhalten. Wir können ein neues erstellen RecurringTask, das das neue Verhalten ausführt, seine Abhängigkeiten einfügen und es der Auflistung von Aufgaben in der hinzufügen RecurringTaskScheduler. Wir müssen nicht einmal über das Löschen von Datenträgern sprechen, da diese Verantwortung ganz woanders liegt. Wir haben auch noch die gesamte Palette an OO-Tools zur Verfügung. Wir könnten diese Aufgabe zum Beispiel mit der Protokollierung dekorieren.

Option 1 schmerzt Sie am wenigsten und ist der richtige Weg, um mit dieser Situation umzugehen.

cbojar
quelle
Ihre Analyse zu Otion2,3,4 ist fantastisch! Es hilft mir wirklich sehr. Aber für Option1 würde ich argumentieren, dass * SendEmailTask ​​* eine Entität ist. Es hat eine ID, ein wiederkehrendes Muster und andere nützliche Informationen, die in db gespeichert werden sollten. Ich denke, Andy fasst meine Absicht gut zusammen. Vielleicht ist ein Name wie * EMailTaskDefinitions * besser geeignet. Ich möchte meine Entität nicht mit meinem Service-Code verschmutzen. Euphoric erwähnt ein Problem, wenn ich einen Dienst in die Entität einspeise. Ich aktualisiere auch meine Frage und füge Option5 hinzu, die meiner Meinung nach die bisher beste Lösung ist.
Sher10ck
@ Sher10ck Wenn Sie die Konfiguration für Ihre SendEmailTaskaus einer Datenbank ziehen, sollte diese Konfiguration eine separate Konfigurationsklasse sein, die auch in Ihre Datenbank eingefügt werden sollte SendEmailTask. Wenn Sie Daten aus Ihrem generieren SendEmailTask, sollten Sie ein Erinnerungsobjekt erstellen, um den Status zu speichern und in Ihre Datenbank aufzunehmen.
Cbojar
Ich muss die Konfiguration von db abrufen. Schlagen Sie also vor, beide EMailTaskDefinitionsund EmailServicein zu injizieren SendEmailTask? Dann RecurringTaskSchedulermuss ich in der so etwas wie, SendEmailTaskRepositorydessen Verantwortung darin besteht, Definition und Service zu laden, injizieren und sie injizieren SendEmailTask. Aber ich würde jetzt argumentieren, die RecurringTaskSchedulerNotwendigkeit, Repository für jede Aufgabe zu kennen, wie CleanDiskTaskRepository. Und ich muss RecurringTaskSchedulerjedes Mal ändern, wenn ich eine neue Aufgabe habe (um dem Scheduler ein Repository hinzuzufügen).
Sher10ck
@ Sher10ck Der RecurringTaskSchedulersollte nur das Konzept eines verallgemeinerten Task-Repositorys kennen und a RecurringTask. Auf diese Weise kann es von Abstraktionen abhängen. Die Task-Repositorys können in den Konstruktor von eingefügt werden RecurringTaskScheduler. Dann müssen die verschiedenen Repositorys nur bekannt sein, wo sie RecurringTaskSchedulerinstanziiert werden (oder sie könnten in einer Fabrik versteckt und von dort aufgerufen werden). Da es nur auf die Abstraktionen ankommt, RecurringTaskSchedulermuss es sich nicht bei jeder neuen Aufgabe ändern. Das ist die Essenz der Abhängigkeitsinversion.
Cbojar
3

Haben Sie sich vorhandene Bibliotheken angesehen, z. B. Spring Quarz oder Spring Batch (ich bin mir nicht sicher, was Ihren Anforderungen am besten entspricht)?

Zu Ihrer Frage:

Ich gehe davon aus, dass das Problem darin besteht, dass Sie einige Metadaten auf polymorphe Weise für die Aufgabe beibehalten möchten, sodass einer E-Mail-Aufgabe E-Mail-Adressen zugewiesen sind, einer Protokollaufgabe eine Protokollebene usw. Sie können eine Liste dieser Daten im Speicher oder in Ihrer Datenbank speichern. Um jedoch Bedenken auszuräumen, möchten Sie nicht, dass die Entität mit Service-Code verschmutzt wird.

Meine vorgeschlagene Lösung:

Ich würde den laufenden und den Datenteil der Aufgabe trennen, um zB TaskDefinitionund a zu haben TaskRunner. Die TaskDefinition enthält einen Verweis auf einen TaskRunner oder eine Factory, die einen erstellt (z. B. wenn ein Setup wie der SMTP-Host erforderlich ist). Die Factory ist eine bestimmte - sie kann nur EMailTaskDefinitions verarbeiten und gibt nur Instanzen von EMailTaskRunners zurück. Auf diese Weise ist es mehr OO und änderungssicher - wenn Sie einen neuen Aufgabentyp einführen, müssen Sie eine neue spezifische Factory einführen (oder eine wiederverwenden), wenn Sie dies nicht tun, können Sie nicht kompilieren.

Auf diese Weise erhalten Sie eine Abhängigkeit: Entity-Schicht -> Service-Schicht und wieder zurück, da der Runner Informationen benötigt, die in der Entität gespeichert sind, und wahrscheinlich eine Aktualisierung seines Status in der Datenbank vornehmen möchte.

Sie könnten den Kreis durchbrechen, indem Sie eine generische Factory verwenden, die eine TaskDefinition verwendet und einen bestimmten TaskRunner zurückgibt. Dies würde jedoch viele Wenns erfordern. Sie können Reflection verwenden, um einen Runner zu finden, der ähnlich wie Ihre Definition benannt ist. Seien Sie jedoch vorsichtig. Dieser Ansatz kann einige Leistung kosten und zu Laufzeitfehlern führen.

PS Ich gehe hier von Java aus. Ich denke, dass es in .net ähnlich ist. Das Hauptproblem hierbei ist die Doppelbindung.

Zum Besuchermuster

Ich denke, es war eher dazu gedacht, einen Algorithmus zur Laufzeit gegen verschiedene Arten von Datenobjekten auszutauschen, als für reine Doppelbindungszwecke. Zum Beispiel, wenn Sie verschiedene Arten von Versicherungen haben und diese unterschiedlich berechnen, z. B. weil verschiedene Länder dies verlangen. Dann wählen Sie eine bestimmte Berechnungsmethode und wenden sie auf mehrere Versicherungen an.

In Ihrem Fall würden Sie eine bestimmte Aufgabenstrategie (z. B. E-Mail) auswählen und auf alle Ihre Aufgaben anwenden, was falsch ist, da nicht alle E-Mail-Aufgaben sind.

PS Ich habe es nicht getestet, aber ich denke, Ihre Option 4 wird auch nicht funktionieren, da sie wieder doppelt bindend ist.

Andy
quelle
Sie fassen meine Absicht wirklich gut zusammen, danke! Ich möchte den Kreis brechen. Da TaskDefiniton einen Verweis auf TaskRunner oder Factory enthält, hat dies das gleiche Problem wie Option1. Ich behandle Factory oder TaskRunner als Service. Wenn TaskDefinition einen Verweis auf diese enthält, müssen Sie entweder den Dienst in TaskDefinition einfügen oder eine statische Methode verwenden, die ich zu vermeiden versuche.
Sher10ck
1

Ich bin mit diesem Artikel überhaupt nicht einverstanden. Services (konkret ihre "API") sind wichtige Parteien der Geschäftsdomäne und werden als solche innerhalb des Domänenmodells existieren. Und es gibt kein Problem mit Entitäten in der Geschäftsdomäne, die auf etwas anderes in derselben Geschäftsdomäne verweisen.

Wenn X Mail an Y sendet.

Ist eine Geschäftsregel. Dazu wird ein Dienst benötigt, der E-Mails sendet. Und Entitäten, die damit umgehen, When Xsollten über diesen Service Bescheid wissen.

Es gibt jedoch einige Probleme bei der Implementierung. Für den Benutzer der Entität sollte transparent sein, dass die Entität einen Dienst verwendet. Das Hinzufügen des Dienstes im Konstruktor ist also keine gute Sache. Dies ist auch ein Problem, wenn Sie die Entität aus der Datenbank deserialisieren, da Sie sowohl die Daten der Entität als auch die Instanzen von Diensten festlegen müssen. Die beste Lösung, die ich mir vorstellen kann, ist die Verwendung der Eigenschaftsinjektion, nachdem die Entität erstellt wurde. Möglicherweise wird jede neu erstellte Instanz einer Entität gezwungen, die "Initialisierungs" -Methode zu durchlaufen, mit der alle Entitäten eingefügt werden, die die Entität benötigt.

Euphorisch
quelle
Auf welchen Artikel beziehen Sie sich, mit dem Sie nicht einverstanden sind? Interessante Sicht auf das Domain-Modell. Wahrscheinlich können Sie das so sehen, aber die Leute vermeiden es normalerweise, Dienste in Entitäten zu mischen, da dies sehr bald zu einer engen Kopplung führen wird.
Andy
@Andy Derjenige, auf den Sher10ck in seiner Frage verwiesen hat. Und ich sehe nicht, wie es zu einer engen Kopplung kommen würde. Jeder schlecht geschriebene Code kann zu einer engen Kopplung führen.
Euphorisch
1

Das ist eine gute Frage und ein interessantes Problem. Ich schlage vor, dass Sie eine Kombination aus Chain of Responsibility- und Double Dispatch- Mustern verwenden (Musterbeispiele hier ).

Definieren wir zunächst die Aufgabenhierarchie. Beachten Sie, dass es jetzt mehrere runMethoden gibt, um den doppelten Versand zu implementieren.

public abstract class RecurringTask {

    public abstract boolean isOccuring(Date date);

    public boolean run(EmailService emailService) {
        return false;
    }

    public boolean run(ExecuteService executeService) {
        return false;
    }
}

public class SendEmailTask extends RecurringTask {

    private String email;

    public SendEmailTask(String email) {
        this.email = email;
    }

    @Override
    public boolean isOccuring(Date date) {
        return true;
    }

    @Override
    public boolean run(EmailService emailService) {
        emailService.runTask(this);
        return true;
    }

    public String getEmail() {
        return email;
    }
}

public class ExecuteTask extends RecurringTask {

    private String program;

    public ExecuteTask(String program) {
        this.program = program;
    }

    @Override
    public boolean isOccuring(Date date) {
        return true;
    }

    public String getName() {
        return program;
    }

    @Override
    public boolean run(ExecuteService executeService) {
        executeService.runTask(this);
        return true;
    }
}

Als nächstes definieren wir die ServiceHierarchie. Wir werden Services verwenden, um die Kette der Verantwortung zu bilden.

public abstract class Service {

    private Service next;

    public Service(Service next) {
        this.next = next;
    }

    public void handleRecurringTask(RecurringTask req) {
        if (next != null) {
            next.handleRecurringTask(req);
        }
    }
}

public class ExecuteService extends Service {

    public ExecuteService(Service next) {
        super(next);
    }

    void runTask(ExecuteTask task) {
        System.out.println(String.format("%s running %s with content '%s'", this.getClass().getSimpleName(),
                task.getClass().getSimpleName(), task.getName()));
    }

    public void handleRecurringTask(RecurringTask req) {
        if (!req.run(this)) {
            super.handleRecurringTask(req);
        }
    }
}

public class EmailService extends Service {

    public EmailService(Service next) {
        super(next);
    }

    public void runTask(SendEmailTask task) {
        System.out.println(String.format("%s running %s with content '%s'", this.getClass().getSimpleName(),
                task.getClass().getSimpleName(), task.getEmail()));
    }

    public void handleRecurringTask(RecurringTask req) {
        if (!req.run(this)) {
            super.handleRecurringTask(req);
        }
    }
}

Das letzte Stück ist das, RecurringTaskSchedulerdas den Lade- und Ausführungsprozess koordiniert.

public class RecurringTaskScheduler{

    private List<RecurringTask> tasks = new ArrayList<>();

    private Service chain;

    public RecurringTaskScheduler() {
        chain = new EmailService(new ExecuteService(null));
    }

    public void loadTasks() {
        tasks.add(new SendEmailTask("here comes the first email"));
        tasks.add(new SendEmailTask("here is the second email"));
        tasks.add(new ExecuteTask("/root/python"));
        tasks.add(new ExecuteTask("/bin/cat"));
        tasks.add(new SendEmailTask("here is the third email"));
        tasks.add(new ExecuteTask("/bin/grep"));
    }

    public void runTasks(){
        for (RecurringTask task : tasks) {
            if (task.isOccuring(new Date())) {
                chain.handleRecurringTask(task);
            }
        }
    }
}

Hier ist die Beispielanwendung, die das System demonstriert.

public class App {

    public static void main(String[] args) {
        RecurringTaskScheduler scheduler = new RecurringTaskScheduler();
        scheduler.loadTasks();
        scheduler.runTasks();
    }
}

Ausführen der Anwendungsausgaben:

EmailService mit SendEmailTask ​​mit Inhalt 'hier kommt die erste E-Mail'
EmailService mit SendEmailTask ​​mit Inhalt 'hier ist die zweite E-Mail'
ExecuteService mit ExecuteTask mit Inhalt '/ root / python'
ExecuteService mit ExecuteTask mit Inhalt '/ bin / cat'
EmailService mit SendEmailTask ​​mit Inhalt 'Hier ist die dritte E-Mail'
ExecuteService, auf dem ExecuteTask mit Inhalt '/ bin / grep' ausgeführt wird.

iluwatar
quelle
Ich kann eine Menge Aufgabe haben . Jedes Mal, wenn ich eine neue Aufgabe hinzufüge , muss ich RecurringTask ändern und ich muss auch alle Unterklassen ändern, da ich eine neue Funktion wie public abstract boolean run (OtherService otherService) hinzufügen muss . Ich denke, Option4, das Besuchermuster, das auch den doppelten Versand implementiert, hat das gleiche Problem.
Sher10ck
Guter Punkt. Ich habe meine Antwort so bearbeitet, dass Ausführungsmethoden (Dienstmethoden) in der RecurringTask definiert sind und standardmäßig false zurückgeben. Auf diese Weise müssen Sie die Geschwisteraufgaben nicht berühren, wenn Sie eine weitere Aufgabenklasse hinzufügen müssen.
Iluwatar