Il modello visitatore è valido in questo scenario?


9

L'obiettivo della mia attività è progettare un piccolo sistema in grado di eseguire attività ricorrenti pianificate. Un'attività ricorrente è qualcosa come "inviare un'e-mail all'amministratore ogni ora dalle 8:00 alle 17:00, dal lunedì al venerdì".

Ho una classe base chiamata 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(){

    }
}

E ho diverse classi che sono ereditate da RecurringTask . Uno di questi si chiama SendEmailTask .

public class SendEmailTask : RecurringTask{
    private Email email;

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

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

E ho un EmailService che può aiutarmi a inviare un'e-mail.

L'ultima classe è RecurringTaskScheduler , è responsabile del caricamento delle attività dalla cache o del database ed esecuzione dell'attività.

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

Ecco il mio problema: dove devo mettere EmailService ?

Opzione 1 : Iniettare 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);
    }
}

Ci sono già alcune discussioni sull'opportunità di iniettare un servizio in un'entità e la maggior parte delle persone concorda sul fatto che non è una buona pratica. Vedere questo articolo .

Opzione2: If ... Else 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
                }
            }
        }
    }
}

Mi è stato detto se ... Altrimenti e il cast come sopra non è OO, e porterà più problemi.

Opzione 3: modifica la firma di Esegui e crea ServiceBundle .

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

}

Iniettare questa classe in 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);
            }
        }
    }
}

Il metodo Run di SendEmailTask sarebbe

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

Non vedo grossi problemi con questo approccio.

Opzione 4 : modello visitatore.
L'idea di base è creare un visitatore che incapsuli i servizi proprio come 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){
        //
    }
}

E dobbiamo anche cambiare la firma del metodo Run . Il metodo Run di SendEmailTask è

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

È un'implementazione tipica del modello visitatore e il visitatore verrà iniettato in RecurringTaskScheduler .

In sintesi: tra questi quattro approcci, qual è il migliore per il mio scenario? E ci sono grandi differenze tra Opzione 3 e Opzione 4 per questo problema?

O hai un'idea migliore su questo problema? Grazie!

Aggiornamento 22/05/2015 : Penso che la risposta di Andy riassuma davvero bene le mie intenzioni; se sei ancora confuso sul problema stesso, ti suggerisco di leggere prima il suo post.

Ho appena scoperto che il mio problema è molto simile al problema di Message Dispatch , che porta a Option5.

Opzione 5 : converti il ​​mio problema in Invio messaggio .
Esiste un mapping uno a uno tra il mio problema e il problema di invio dei messaggi :

Message Dispatcher : riceve IMessage e invia sottoclassi di IMessage ai gestori corrispondenti. → RecurringTaskScheduler

IMessage : un'interfaccia o una classe astratta. → Task ricorrenti

Messaggio A : si estende da IMessage , con alcune informazioni aggiuntive. → InviaEmailTask

Messaggio B : un'altra sottoclasse di IMessage . → CleanDiskTask

MessageAHandler : quando ricevi MessageA , gestiscilo → SendEmailTaskHandler, che contiene EmailService, e invierà un'email quando riceve SendEmailTask

MessageBHandler : uguale a MessageAHandler , ma gestisce invece MessageB . → CleanDiskTaskHandler

La parte più difficile è il modo di spedire diversi tipi di IMessage a diversi gestori. Ecco un link utile .

Mi piace molto questo approccio, non inquina la mia entità con il servizio e non ha alcuna classe divina .


Non hai taggato una lingua o una piattaforma, ma ti consiglio di guardare cron . La tua piattaforma potrebbe avere una libreria che funziona in modo simile (ad esempio jcron che sembra defunto). La pianificazione di lavori e attività è in gran parte un problema risolto: hai già esaminato altre opzioni prima di lanciarne una tua? Ci sono stati motivi per non usarli?

@Snowman Potremmo passare a una libreria matura in seguito. Tutto dipende dal mio manager. Il motivo per cui pubblico questa domanda è che voglio trovare un modo per risolvere questo "tipo" di problema. Ho visto questo tipo di problema più di una volta e non sono riuscito a trovare una soluzione elegante. Quindi mi chiedo se ho fatto qualcosa di sbagliato.
Sher10ck,

Abbastanza giusto, cerco sempre di raccomandare il riutilizzo del codice, se possibile, però.

1
SendEmailTaskmi sembra più un servizio che un'entità. Vorrei scegliere l'opzione 1 senza esitazione.
Bart van Ingen Schenau,

3
Ciò che manca (per me) per Visitatore è la struttura della classe che acceptè visitatori. La motivazione per Visitatore è che in alcuni aggregati ci sono molti tipi di classe che devono essere visitati e non è conveniente modificare il loro codice per ogni nuova funzionalità (operazione). Continuo a non vedere cosa siano quegli oggetti aggregati e penso che Visitatore non sia appropriato. In tal caso, è necessario modificare la domanda (che si riferisce al visitatore).
Fuhrmanator,

Risposte:


4

Direi che l' opzione 1 è la strada migliore da percorrere. Il motivo per cui non dovresti respingerlo è che nonSendEmailTask è un'entità. Un'entità è un oggetto interessato a contenere dati e stato. La tua classe ha ben poco. In realtà, non è un'entità, ma contiene un'entità: l' oggetto che stai memorizzando. Ciò significa che non dovrebbe prendere un servizio o avere un metodo. Invece, dovresti avere servizi che accettano entità, come la tua . Quindi stai già seguendo l'idea di mantenere i servizi fuori dalle entità.EmailEmail#SendEmailService

Poiché SendEmailTasknon è un'entità, è quindi perfettamente corretto iniettare l'e-mail e il servizio al suo interno, e ciò dovrebbe essere fatto tramite il costruttore. Effettuando l'iniezione del costruttore, possiamo essere certi che SendEmailTaskè sempre pronto a svolgere il suo lavoro.

Ora diamo un'occhiata al perché non fare le altre opzioni (in particolare rispetto a SOLID ).

opzione 2

Ti è stato giustamente detto che la ramificazione di questo tipo porterà più mal di testa lungo la strada. Diamo un'occhiata al perché. In primo luogo, iftendono a raggrupparsi e crescere. Oggi è compito inviare e-mail, domani, ogni diverso tipo di classe ha bisogno di un servizio o di un comportamento diverso. Gestire questa ifaffermazione diventa un incubo. Poiché stiamo ramificando il tipo (e in questo caso il tipo esplicito ), stiamo sovvertendo il sistema di tipi incorporato nella nostra lingua.

L'opzione 2 non è Single Responsibility (SRP) perché il precedente riutilizzabile RecurringTaskSchedulerora deve conoscere tutti questi diversi tipi di attività e tutti i diversi tipi di servizi e comportamenti di cui potrebbero aver bisogno. Quella classe è molto più difficile da riutilizzare. Inoltre non è aperto / chiuso (OCP). Poiché è necessario conoscere questo tipo di attività o quella (o questo tipo di servizio o quella), modifiche diverse alle attività o ai servizi potrebbero forzare i cambiamenti qui. Aggiungi una nuova attività? Aggiungi un nuovo servizio? Cambiare il modo in cui viene gestita la posta elettronica? Change RecurringTaskScheduler. Poiché il tipo di attività è importante, non aderisce a Liskov Substitution (LSP). Non può semplicemente ottenere un compito ed essere fatto. Deve chiedere il tipo e in base al tipo fare questo o farlo. Invece di incapsulare le differenze nei compiti, stiamo trascinando tutto ciò nel RecurringTaskScheduler.

Opzione 3

L'opzione 3 ha alcuni grossi problemi. Anche nell'articolo a cui ti colleghi , l'autore scoraggia a farlo:

  • Puoi comunque utilizzare un localizzatore di servizi statici ...
  • Evito il localizzatore di servizio quando posso, specialmente quando il localizzatore di servizio deve essere statico ...

Stai creando un localizzatore di servizi con la tua ServiceBundleclasse. In questo caso, non sembra essere statico, ma presenta ancora molti dei problemi inerenti a un localizzatore di servizi. Le tue dipendenze sono ora nascoste sotto questo ServiceBundle. Se ti do la seguente API del mio nuovo fantastico compito:

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

Quali sono i servizi che sto usando? Quali servizi devono essere derisi in un test? Cosa mi impedisce di utilizzare tutti i servizi nel sistema, solo perché?

Se voglio usare il tuo sistema di attività per eseguire alcune attività, ora dipendo da ogni servizio nel tuo sistema, anche se ne uso solo alcune o addirittura nessuna.

Non ServiceBundleè veramente SRP perché ha bisogno di conoscere ogni servizio nel tuo sistema. Inoltre non è OCP. L'aggiunta di nuovi servizi implica modifiche a ServiceBundlee modifiche a ServiceBundlepossono comportare modifiche diverse alle attività altrove. ServiceBundlenon segrega la sua interfaccia (ISP). Ha un'interfaccia tentacolare di tutti questi servizi e, poiché è solo un fornitore per quei servizi, potremmo considerare la sua interfaccia per comprendere anche le interfacce di tutti i servizi che fornisce. Le attività non aderiscono più a Dependency Inversion (DIP), poiché le loro dipendenze sono offuscate dietro ServiceBundle. Anche questo non aderisce al Principio della Minima Conoscenza (alias la Legge di Demetra) perché le cose conoscono molte più cose di quelle che devono.

Opzione 4

In precedenza, c'erano molti piccoli oggetti che erano in grado di operare in modo indipendente. L'opzione 4 prende tutti questi oggetti e li divide in un singolo Visitoroggetto. Questo oggetto agisce come un oggetto divino su tutti i tuoi compiti. Riduce i tuoi RecurringTaskoggetti in ombre anemiche che richiamano semplicemente un visitatore. Tutto il comportamento passa a Visitor. Hai bisogno di cambiare comportamento? Devi aggiungere una nuova attività? Change Visitor.

La parte più stimolante è che, poiché tutti i diversi comportamenti sono tutti in una singola classe, cambiando alcuni polimorficamente si trascina lungo tutti gli altri comportamenti. Ad esempio, vogliamo avere due modi diversi per inviare e-mail (forse dovrebbero usare server diversi?). Come lo faremmo? Potremmo creare IVisitorun'interfaccia e implementare quel codice, potenzialmente duplicando, come #Visit(ClearDiskTask)dal nostro visitatore originale. Quindi se ci viene in mente un nuovo modo per cancellare un disco, dobbiamo implementare e duplicare di nuovo. Quindi vogliamo entrambi i tipi di modifiche. Implementare e duplicare di nuovo. Questi due comportamenti diversi e disparati sono indissolubilmente legati.

Forse invece potremmo semplicemente subclassare Visitor? Sottoclasse con nuovo comportamento della posta elettronica, sottoclasse con nuovo comportamento del disco. Nessuna duplicazione finora! Sottoclasse con entrambi? Ora l'uno o l'altro deve essere duplicato (o entrambi se questa è la tua preferenza).

Confrontiamo con l'opzione 1: abbiamo bisogno di un nuovo comportamento e-mail. Possiamo creare un nuovo RecurringTaskche comporti il ​​nuovo comportamento, iniettare nelle sue dipendenze e aggiungerlo alla raccolta di attività nel file RecurringTaskScheduler. Non abbiamo nemmeno bisogno di parlare della cancellazione dei dischi, perché quella responsabilità è completamente da qualche altra parte. Abbiamo ancora a disposizione l'intera gamma di strumenti OO. Ad esempio, potremmo decorare quell'attività con la registrazione.

L'opzione 1 ti darà il minimo dolore ed è il modo più corretto per gestire questa situazione.


La tua analisi su Otion2,3,4 è fantastica! Mi aiuta davvero molto. Ma per Option1, direi che * SendEmailTask ​​* è un'entità. Ha id, ha il suo modello ricorrente e altre informazioni utili che dovrebbero essere archiviate in db. Penso che Andy riassuma bene la mia intenzione. Forse un nome come * EMailTaskDefinitions * è più appropriato. non voglio inquinare la mia entità con il mio codice di servizio. Euforico menziona qualche problema se inietto un servizio in entità. Inoltre aggiorno la mia domanda e includo Option5, che credo sia la soluzione migliore finora.
Sher10ck,

@ Sher10ck Se stai estraendo la configurazione da SendEmailTaskun database, quella configurazione dovrebbe essere una classe di configurazione separata che dovrebbe essere iniettata anche nel tuo SendEmailTask. Se stai generando dati dal tuo SendEmailTask, dovresti creare un oggetto memento per memorizzare lo stato e inserirlo nel tuo database.
cbojar,

Devo estrarre la configurazione da db, quindi stai suggerendo di iniettare sia in EMailTaskDefinitionsche EmailServicein SendEmailTask? Quindi nel RecurringTaskScheduler, ho bisogno di iniettare qualcosa come la SendEmailTaskRepositorycui responsabilità è caricare la definizione e il servizio e iniettarli SendEmailTask. Ma ora vorrei discutere della RecurringTaskSchedulernecessità di conoscere il repository di ogni compito, come CleanDiskTaskRepository. E RecurringTaskSchedulerdevo cambiare ogni volta che ho una nuova attività (per aggiungere un repository nello Scheduler).
Sher10ck,

@ Sher10ck RecurringTaskSchedulerDeve solo essere consapevole del concetto di un repository di attività generalizzato e a RecurringTask. In questo modo, può dipendere da astrazioni. I repository di attività possono essere iniettati nel costruttore di RecurringTaskScheduler. Quindi i diversi repository devono essere conosciuti solo dove RecurringTaskSchedulersono istanziati (o potrebbero essere nascosti in una fabbrica e chiamati da lì). Poiché dipende solo dalle astrazioni, RecurringTaskSchedulernon è necessario modificare ogni nuova attività. Questa è l'essenza dell'inversione di dipendenza.
cbojar,

3

Hai dato un'occhiata alle librerie esistenti, ad esempio quarzo primaverile o lotto primaverile (non sono sicuro di cosa si adatta meglio alle tue esigenze)?

Alla tua domanda:

Presumo che il problema sia che si desidera mantenere alcuni metadati dell'attività in modo polimorfico, quindi a un'attività di posta elettronica sono assegnati indirizzi di posta elettronica, un'attività di registro a livello di registro e così via. È possibile memorizzare un elenco di quelli in memoria o nel database, ma per preoccupazioni separate non si desidera che l'entità venga inquinata con il codice di servizio.

La mia soluzione proposta:

Vorrei separare la parte di esecuzione e quella di dati del compito, per avere eg TaskDefinitione a TaskRunner. TaskDefinition ha un riferimento a TaskRunner o a una factory che ne crea uno (ad es. Se è richiesta una configurazione come l'host smtp). La fabbrica è specifica: può solo gestire se EMailTaskDefinitionrestituisce solo istanze di EMailTaskRunners. In questo modo è più OO e cambia in modo sicuro: se si introduce un nuovo tipo di attività, è necessario introdurre un nuovo factory specifico (o riutilizzarne uno), se non lo si può compilare.

In questo modo si finirebbe con una dipendenza: livello entità -> livello servizio e viceversa, perché il Runner necessita di informazioni memorizzate nell'entità e probabilmente desidera effettuare un aggiornamento al suo stato nel DB.

È possibile interrompere il cerchio utilizzando una factory generica, che accetta una TaskDefinition e restituisce un TaskRunner specifico , ma ciò richiederebbe molti if. È possibile utilizzare la riflessione per trovare un corridore che abbia lo stesso nome della definizione, ma sii cauto che questo approccio può costare alcune prestazioni e può portare a errori di runtime.

PS Sto assumendo Java qui. Penso che sia simile in .net. Il problema principale qui è la doppia associazione.

Al modello del visitatore

Penso che fosse piuttosto destinato a essere utilizzato per scambiare un algoritmo per diversi tipi di oggetti dati in fase di esecuzione, piuttosto che per scopi di doppio legame puro. Ad esempio, se si dispone di diversi tipi di assicurazioni e di diversi tipi di calcolo, ad esempio perché diversi paesi lo richiedono. Quindi si sceglie un metodo di calcolo specifico e lo si applica su diverse assicurazioni.

Nel tuo caso, sceglieresti una specifica strategia (es. E-mail) e la applichi a tutte le tue attività, il che è sbagliato perché non tutte sono attività e-mail.

PS Non l'ho testato, ma penso che anche la tua Opzione 4 non funzionerà, perché è di nuovo a doppia associazione.


Riassumi davvero bene la mia intenzione, grazie! Vorrei spezzare il cerchio. Perché lasciare TaskDefiniton contiene un riferimento a TaskRunner o factory ha lo stesso problema di Option1. Io tratto fabbrica o TaskRunner come servizio. Se TaskDefinition necessita di un riferimento a loro, è necessario iniettare il servizio in TaskDefinition o utilizzare un metodo statico, che sto cercando di evitare.
Sher10ck,

1

Non sono completamente d'accordo con quell'articolo. I servizi (concretamente la loro "API") sono parti importanti del dominio aziendale e come tali esisteranno all'interno del modello di dominio. E non vi è alcun problema con le entità nel dominio aziendale che fanno riferimento a qualcos'altro nello stesso dominio aziendale.

Quando X invia la posta a Y.

È una regola aziendale. E per farlo, è necessario il servizio che invia posta. E l'entità che gestisce When Xdovrebbe conoscere questo servizio.

Ma ci sono alcuni problemi con l'implementazione. Dovrebbe essere trasparente all'utente dell'entità che l'entità stia utilizzando un servizio. Quindi aggiungere il servizio nel costruttore non è una buona cosa. Questo è anche un problema, quando si sta deserializzando l'entità dal database, poiché è necessario impostare sia i dati dell'entità sia le istanze dei servizi. La migliore soluzione a cui riesco a pensare è usare l'iniezione di proprietà dopo la creazione dell'entità. Forse forzare ogni istanza appena creata di qualsiasi entità a passare attraverso il metodo di "inizializzazione" che inietta tutte le entità di cui l'entità ha bisogno.


A quale articolo ti riferisci che non sei d'accordo? Tuttavia, interessante punto di vista sul modello di dominio. Probabilmente puoi vederlo in quel modo, tuttavia, le persone di solito evitano di mescolare servizi in entità, perché creerà molto presto un accoppiamento stretto.
Andy,

@Andy Quello che Sher10ck ha fatto riferimento nella sua domanda. E non vedo come creerebbe un accoppiamento stretto. Qualsiasi codice scritto male può introdurre un accoppiamento stretto.
Euforico,

1

Questa è un'ottima domanda e un problema interessante. Propongo di utilizzare una combinazione di modelli Chain of Responsibility e Double Dispatch (esempi di pattern qui ).

Per prima cosa definiamo la gerarchia delle attività. Si noti che ora ci sono più runmetodi per implementare Double Dispatch.

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

Successivamente, definiamo la Servicegerarchia. Useremo Services per formare la catena di 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);
        }
    }
}

L'ultimo pezzo è quello RecurringTaskSchedulerche orchestra il processo di caricamento ed esecuzione.

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

Ora, ecco l'applicazione di esempio che dimostra il sistema.

public class App {

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

Esecuzione degli output dell'applicazione:

EmailService che esegue SendEmailTask ​​con contenuto 'ecco che arriva la prima email'
EmailService che esegue SendEmailTask ​​con contenuto 'ecco la seconda email'
ExecuteService che esegue ExecuteTask con contenuto '/ root / python'
ExecuteService che esegue ExecuteTask con contenuto '/ bin / cat'
EmailService che esegue SendEmailTask ​​con content "ecco la terza email"
ExecuteService che esegue ExecuteTask con content "/ bin / grep"


Potrei avere un sacco di compiti . Ogni volta che aggiungo una nuova attività , devo cambiare RecurringTask e ho anche bisogno di cambiare tutte le sue sottoclassi, perché ho bisogno di aggiungere una nuova funzione come esecuzione booleana astratta pubblica (OtherService otherService) . Penso che Option4, il modello di visitatore che implementa anche il doppio dispaccio abbia lo stesso problema.
Sher10ck,

Buon punto. Ho modificato la mia risposta in modo che i metodi run (service) siano definiti in RecurringTask e restituiscano false per impostazione predefinita. In questo modo, quando è necessario aggiungere un'altra classe di attività, non è necessario toccare le attività di pari livello.
iluwatar,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.