Evita correttamente l'ereditarietà?

2

Voglio aggiungere funzionalità per una classe astratta Model per inviare eventi ogni volta che si aggiorna. Per poter inviare eventi, è necessario avere accesso a un EventManager . Pertanto, l'intento era innanzitutto quello di essere in grado di gestire gli eventi (ad esempio, l'operazione di salvataggio) e la seconda di inviare eventi al gestore di eventi di sistema.

Il mio approccio è implementare una nuova interfaccia EventManagerAware in ExistingModel class, che richiede questo comportamento, come questo:

interface EventManagerAware {

    public function handleEvent(EventManager em);
}

class ExistingModel extends Model implements EventManagerAware {

    public function handleEvent(EventManager em) {
        em.sendEvent(Event::SAVE);
    }
}

abstract class Model() {

    public function save() {

        if (this instanceOf EventManagerAware) {
            this.handleEvent(this.getServiceManager().getEventManager());
        }
}

Dopo la revisione, qualcuno ha sostenuto che è meglio estendere solo la classe Model , sovrascrivere il metodo save() e modificare ExistingModel dall'estensione di Model a EventPublisherModel class, in questo modo:

class EventPublisherModel extends Model {

    public function save() {
        this.getServiceManager().getEventManager().sendEvent(Event::SAVE);
    }
}

class ExistingModel extends EventPublisherModel {

}

FYI, la classe Model esistente ha il seguente aspetto:

abstract class Model {
    protected ServiceManager serviceManager;

    public function __construct(ServiceManager sm) {
        this.serviceManager = sm;
    }

    public function getServiceManager() {
        return this.serviceManager;
    }

    public function save() {

    }
}

Uno degli argomenti messi per avere la classe EventPublisherModel era perché facendo il primo approccio, tutti gli oggetti Model avrebbero controllato EventManagerService quando facevano save() operazione, e questo non è necessario.

Non ho mai veramente pensato al motivo per cui il primo approccio sarebbe stato migliore, ma ho pensato che ho segmentato l'interfaccia e non pensavo che l'ereditarietà fosse necessaria perché l'invio degli eventi è in un dominio diverso dalla classe Model .

Qual è il modo migliore per farlo e perché?

    
posta imel96 06.10.2015 - 03:24
fonte

3 risposte

3

Innanzitutto, è possibile migliorare la denominazione dei metodi. handleEvent dovrebbe essere meglio chiamato sendSaveEvent , perché è responsabilità non "gestire" alcun evento, è per inviare eventi, e non è per inviare eventi generici, solo per eventi "salva".

In secondo luogo, il tuo esempio non mostra il codice di "salvataggio" effettivo. Se hai aggiunto un segnaposto per questo, diventa ovvio il motivo per cui la seconda soluzione non è davvero una buona idea - quando ciascuna sottoclasse ha bisogno di sovrascrivere il metodo save solo per aggiungere "anche l'invio di logica", il risultato è almeno un duplicato codice. Questo non è necessario, il codice può essere tenuto in un posto (nella classe base del modello astratto).

Considerando questo, vorrei prendere in considerazione la soluzione con il modello di metodo del modello :

abstract class Model{

    public function save() {
       // ... here is the actual saving code ...

       sendSaveEvent(this.getServiceManager().getEventManager());
    }
    public function sendSaveEvent(EventManager em) {
          // empty default implementation
    }
}

e nella classe derivata:

class ExistingModel extends Model {

    // overridden function
    public function sendSaveEvent(EventManager em) {
        em.sendEvent(Event::SAVE);
    }
}

Come vedi, il codice è più semplice, l'interfaccia EventManagerAware non è più necessaria, il EventPublisherModel non è necessario e ogni derivazione di Model può semplicemente scegliere se e come inviare un evento SAVE.

UPDATE: l'uso di dependency injection con il pattern null object può rendere ancora più semplice il tuo desing:

  class Model  // maybe abstract
  {

    // Initialize this variable with a value by constructor 
    // injection either directly in the Model class, 
    // or in a subclass, whatever suits your needs.
    EventManager _myEventManager;

    public Model()
    {
        // The "EmptyEventManager" has an empty "sendEvent" method
        // (some kind of "null" object).    
        _myEventManager = new EmptyEventManager()
    }

    public Model(EventManager em)
    {
       _myEventManager=em;
    }

    public function save() {
       // ... here is the actual saving code ...

       _myEventManager.sendEvent(Event::SAVE); // sendEvent is a virtual method
    }
}

Questo non solo elimina il tuo "localizzatore di servizi", ma ti permette anche di passare da "nessun invio di eventi" e "invio di eventi" solo fornendo una sottoclasse EventManager diversa al tuo oggetto Modello (senza la necessità di introdurre una classe come "ExistingModel", ma se hai bisogno di questa sottoclasse per diversi motivi, puoi comunque aggiungerla, se lo desideri). Lo schema applicato è il ben noto modello strategia , con EventManager come oggetto della strategia.

Come ultima osservazione: non conosco il tuo caso d'uso "reale", ma non avrebbe più senso fornire un'implementazione predefinita nella classe base Model in cui l'evento SAVE è effettivamente inviato?

    
risposta data 06.11.2015 - 07:53
fonte
0

Quale è il modo migliore dipende dalle tue esigenze. Con la prima, ogni classe avrà il codice identico o quasi identico per la gestione degli eventi. Ciò causa la duplicazione del codice. Ogni classe può gestire facilmente gli eventi a modo suo. È possibile aggiungere ulteriori informazioni al gestore eventi lungo la catena di ereditarietà. Un altro aspetto negativo è il controllo del tipo per ogni classe basata su Model in save .

Nel secondo modo il codice per la gestione degli eventi si trova in un unico punto. Non è necessario aggiungerlo a ogni classe. I tipi che non richiedono la gestione degli eventi non ne sono influenzati. I modelli che richiedono la gestione personalizzata degli eventi possono creare la propria implementazione di salvataggio. La consapevolezza del manager degli eventi dovrà essere aggiunta alle classi nel punto che derivano dal modello. Nel complesso, il codice sarà più semplice e facile da mantenere.

In sintesi, la seconda ( EventPublisher ) è in genere la strada da percorrere.

    
risposta data 06.10.2015 - 05:18
fonte
0

Sembra che l'invio di eventi sia una preoccupazione secondaria per la tua classe Model , quindi perché non farne interamente il lavoro di una classe separata?

public interface Model {
    void save();
}

public class ExistingModel implements Model {
    public void save() {
        //save stuff
    }
}

public class EventPublishingModel implements Model {
    private Model delegate;
    private EventManager events;

    public EventPublishingModel(Model delegate, EventManager events) {
        this.delegate = delegate;
        this.events = events;
    }

    public void save() {
        delegate.save();
        events.sendEvent(Event::SAVE);
    }

    //other methods can just call the delegate
}

E quindi puoi avere la semplice classe del modello, senza alcuna modifica:

 Model model = new ExistingModel();

Ma decoralo con la funzionalità di pubblicazione degli eventi, dove appropriato:

 model = new EventPublishingModel(model, eventManager);
    
risposta data 06.11.2015 - 02:23
fonte

Leggi altre domande sui tag