web-dev-qa-db-fra.com

Le motif de visiteur est-il valide dans ce scénario?

L'objectif de ma tâche est de concevoir un petit système pouvant exécuter des tâches récurrentes planifiées. Une tâche récurrente est quelque chose comme "Envoyer un courrier électronique à l'administrateur toutes les heures de 8h00 à 17h00, du lundi au vendredi".

J'ai une classe de base appelée récurrenttask.

public abstract class RecurringTask{

    // I've already figured out this part
    public bool isOccuring(DateTime dateTime){
        // implementation
    }

    // run the task
    public abstract void Run(){

    }
}

Et j'ai plusieurs classes héritées de récurrenttask. L'un d'entre eux s'appelle SendemailTask.

public class SendEmailTask : RecurringTask{
    private Email email;

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

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

Et j'ai un Emailervice qui peut m'aider à envoyer un email.

La dernière classe est Recurringtaskscheduler, il est responsable de charger des tâches au cache ou de la base de données et exécutez la tâche.

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

Voici mon problème: Où devrais-je mettre Emailervice?

Option1 : Inject Emailervice 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);
    }
}

Il y a déjà quelques discussions sur la question de savoir si nous devrions injecter un service dans une entité et la plupart des gens sont d'accord que ce n'est pas une bonne pratique. Voir Cet article .

Option2: Si ... d'autre dans récurrenttaskschederuler

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

On m'a dit si ... sinon et jeté comme ci-dessus n'est pas OO, et apportera plus de problèmes.

Option3: Modifiez la signature de exécuter et créer ServiceBundle.

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

}

Injectez cette classe en récurrenttaskscheduler

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

Le exécuter Méthode de SendemailTask serait

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

Je ne vois pas de gros problèmes avec cette approche.

Option4 : Modèle de visiteur.
[.____] L'idée de base est de créer un visiteur qui encapsulera des services comme 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){
        //
    }
}

Et nous devons également modifier la signature de exécuter Méthode. The exécuter méthode de SendemailTask est

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

C'est une implémentation typique du motif de visiteur et le visiteur sera injecté dans récurrenttaskscheduler.

En résumé: parmi ces quatre approches, lequel est le meilleur pour mon scénario? Et y a-t-il une grande différence entre option3 et option4 pour ce problème?

Ou avez-vous une meilleure idée de ce problème? Merci!

Mise à jour 5/22/2015 : Je pense que la réponse de Andy résume vraiment mon intention; Si vous êtes toujours confus sur le problème lui-même, je suggère de lire son poste d'abord.

Je viens de découvrir que mon problème est très similaire à Dispatch de messages problème, ce qui conduit à l'option5.

Option5 : Convertissez mon problème en Dispatch du message.
[.____] Il existe un mappage unique entre mon problème et Dispêche de messages problème:

Dispatchers de messages: Recevoir IMessage et Dispatch Sous-classes de IMessage à leurs gestionnaires correspondants. → Récurrenttaskschédale

IMessage: une interface ou une classe abstraite. → Recurringttask

Messagea: s'étend de imessage, avoir des informations supplémentaires. → SendemailTask

MessageB: une autre sous-classe de IMessage. → CleanDiskTask

MessageaHandler: Lorsque vous recevez Messagea, gérez-le → SendemailTaskhandler, qui contient des e-mails et enverra un email lorsqu'il reçoit SendemailTask

MessageBhandler: même que MessageaHandler, mais gérer MessageB → CleanDiskTaskHandler

La partie la plus difficile est de savoir comment envoyer différents types de IMessage à différents gestionnaires. Voici un utile link .

J'aime vraiment cette approche, il ne pollue pas mon entité avec le service, et il n'a pas de Dieu classe.

9
Sher10ck

Avez-vous eu un coup d'oeil aux bibliothèques existantes par ex. Quartz de printemps ou lot de printemps (je ne suis pas sûr de ce qui correspond au mieux à vos besoins)?

À votre question:

Je suppose que le problème est que vous souhaitez persister certaines métadonnées à la tâche de manière polymorphe. Une tâche de messagerie dispose donc d'adresses de messagerie attribuées, d'une tâche d'enregistrement un niveau de journal, etc. Vous pouvez stocker une liste de ceux de la mémoire ou dans votre base de données, mais également de se séparer des préoccupations que vous ne voulez pas avoir l'entité polluée par le code de service.

Ma solution proposée:

Je séparerais la partie de la tâche et de la partie de la tâche pour avoir par exemple TaskDefinition et a TaskRunner. La tâche TaskDéfinition a une référence à un groupe de travail ou une usine qui en crée un (par exemple, si une configuration est requise comme l'hôte SMTP). L'usine est spécifique - elle ne peut gérer que EMailTaskDefinitions et ne renvoie que des instances de EMailTaskRunners. Cette façon, c'est plus OO et changez de sécurité - Si vous introduisez un nouveau type de tâche, vous devez introduire une nouvelle usine spécifique (ou réutiliser une), si vous ne pouvez pas vous ne pouvez pas compiler.

De cette façon, vous vous retrouveriez avec une dépendance: couche d'entité -> couche de service et retour, car le coureur a besoin d'informations stockées dans l'entité et souhaite probablement faire une mise à jour de son état dans la DB.

Vous pouvez casser le cercle en utilisant une usine générique qui prend a TaskDefinition et renvoie A spécifique Taskrunner, mais cela nécessiterait beaucoup d'IFS. Vous pourrait Utiliser la réflexion pour trouver un coureur de la même manière que votre définition, mais que vous soyez prudent, cette approche peut coûter des performances et peut entraîner des erreurs d'exécution.

P.s. Je suppose Java ici. Je pense que c'est similaire dans .net. Le problème principal ici est la double contraignante.

Au modèle de visiteur

Je pense que cela était plutôt destiné à être utilisé pour échanger un algorithme pour différents types d'objets de données au moment de l'exécution que pour des fins de double contraignantes pures. Par exemple, si vous avez des types d'assurances différentes et de différents types de calcul, par exemple parce que différents pays l'exigent. Ensuite, vous choisissez une méthode de calcul spécifique et appliquez-la sur plusieurs assurances.

Dans votre cas, vous choisiriez une stratégie de tâche spécifique (E.G. Email) et l'appliquer à toutes vos tâches, ce qui ne va pas, car toutes les tâches de messagerie sont toutes.

P.s. Je ne l'ai pas testé, mais je pense que votre option 4 ne fonctionnera pas non plus, car elle est à nouveau contraignante.

3
Andy

C'est une grande question et un problème intéressant. Je propose que vous utilisiez une combinaison de chaîne de responsabilité et Double Dispatch motifs (exemples de motif ici ).

Premier permet de définir la hiérarchie de la tâche. Notez qu'il existe maintenant plusieurs méthodes run pour implémenter la double envoi.

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

Suivant permet de définir la hiérarchie Service. Nous utiliserons Services pour former la chaîne de responsabilité.

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

La dernière pièce est le RecurringTaskScheduler qui orchestre le processus de chargement et de fonctionnement.

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

Maintenant, voici l'exemple d'application démontrant le système.

public class App {

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

Exécuter les sorties d'application:

Emailervice exécutant SendemailTask ​​avec contenu 'Voici le premier email'
Emailervice exécutant SendemailTask ​​avec contenu 'Voici le deuxième email'
[
[
Emailervice exécutant SendemailTask ​​avec contenu 'Voici le troisième email'
[

1
iluwatar

Je suis complètement en désaccord avec cet article. Les services (concrètement leur "API") sont une partie importante du domaine commercial et, à ce titre, il existera dans le modèle de domaine. Et il n'y a pas de problème avec des entités dans le domaine des affaires faisant référence à quelque chose d'autre dans le même domaine d'entreprise.

Quand x envoyer un mail à Y.

Est une règle d'entreprise. Et pour faire cela, le service qui envoie un courrier est nécessaire. Et entité qui gère When X devrait savoir sur ce service.

Mais il y a des problèmes de mise en œuvre. Il devrait être transparent pour l'utilisateur de l'entité, que l'entité utilise un service. Ajout du service dans le constructeur n'est donc pas une bonne chose. C'est également un problème, lorsque vous désérialisez l'entité de la base de données, car vous devez définir à la fois les données de l'entité et des instances des services. Meilleure solution que je peux penser, c'est utiliser l'injection de la propriété après la création de l'entité. Peut-être forcer chaque instance nouvellement créée de toute entité à passer par la méthode "Initialiser" qui injecte toutes les entités dont l'entité a besoin.

1
Euphoric