Classi con diversa semantica ma stessa implementazione

di il
8 risposte

Classi con diversa semantica ma stessa implementazione

Buongiorno a tutti!
Ho un dubbio che mi assilla, e che ci tengo a risolvere in modo da migliorarmi.
Se ho due classi con la stessa implementazione ma diversa semantica, come dovrei comportarmi per ottenere un buon progetto?
Ad esempio: ho una classe "Cooler" e una "Heater" che entrambe hanno gli stessi metodi "switchOn()" e "switchOff()", e medesima implementazione.
All'università mi insegnano a progettare considerando quanto più le interfacce, in modo che futuri cambiamenti nell'implementazione non incidano sul componente che utilizza l'interfaccia.
Ho pensato a diverse soluzioni che non soddisfano a pieno il mio intento, come:

8 Risposte

  • Re: Classi con diversa semantica ma stessa implementazione

    Un adapter converte un'interfaccia in un'altra. E questo non mi pare il caso.
    Il punto 1 mi pare l'approccio corretto. Per conservare la semantica è sufficiente rendere il costruttore protetto di SwitchDevice (in modo da non poter istanziare direttamente la SwitchDevice, se decidi per questo nome), e poi derivare la classi Cooler e Heater da SwitchDevice.
  • Re: Classi con diversa semantica ma stessa implementazione

    La ringrazio per la l'aiuto, effettivamente l'adapter pattern non è indicato nel mio caso, quindi lo escludo.
    La sua soluzione mi è ben chiara; se invece volessi utilizzare le interfacce?
    Ad es. ho un task che gestisce la temperatura di un ambiente, utilizzando un heater e un cooler.
    Il mio intento è quello di far utilizzare, al task, l'heater e il cooler tramite le relative interfacce, in questo modo: .
    Il problema è che, pur essendo semanticamente diversi, l'heater e il cooler, in quanto svolgono diverse funzioni (anzi opposte), hanno lo stesso contratto (switchOn e switchOff) in quanto entrambi sono dei device che vengo accesi e spenti.
    Quindi mi chiedo è corretto avere due interfacce con medesimo contratto o meglio generalizzare in un'unica interfaccia?

    Infine, potrei anche fare in modo che le interfacce heater e cooler abbiano diversi contratti, ed utilizzare la composizione , anche se alla fine le relative classi concrete hanno medesima implementazione; però se in futuro c'è la necessità di riscrivere la logica, la composizione mi permette di non apportare modifiche al task a differenza dell'uso dell'ereditarietà...o mi sbaglio?
  • Re: Classi con diversa semantica ma stessa implementazione

    Il problema è che, pur essendo semanticamente diversi, l'heater e il cooler, in quanto svolgono diverse funzioni (anzi opposte), hanno lo stesso contratto (switchOn e switchOff) in quanto entrambi sono dei device che vengo accesi e spenti.
    Quindi mi chiedo è corretto avere due interfacce con medesimo contratto o meglio generalizzare in un'unica interfaccia?
    Entrambi sono dei device pertanto è preferibile avere un'unica interfaccia.
    Ad esempio:
    
    public interface Device {
        public void switchOn();
        public void switchOff();
    }
    
    public class Heater implements Device {
        /* proprie di Heater */ 
        public void heat() { /* codice vario */ ; }
        public void noHeat() { /* codice vario */ ; }
    
        /* Implementa le funzioni di interfaccia */  
        public void switchOn() { this.heat(); }
        public void switchOff() { this.noHeat(); }
       
    }
    
    public class Cooler implements Device {
        /* proprie di Cooler*/ 
        public void cool() { /* codice vario */ ; }
        public void noCool() { /* codice vario */ ; }
    
        /* Implementa le funzioni di interfaccia */  
        public void switchOn() { this.cool(); }
        public void switchOff() { this.noCool(); }
    }
    
    public class TemperatureControllerTask{
        Device cl;
        Device ht;
     
        public TemperatureControllerTask(Device cl, Device ht) {
            this.cl = cl;
            this.ht = ht;
        }
        
        public void action() {
        	this.ht.switchOff();
        	this.cl.switchOn();
        	this.cl.switchOff();
        	this.ht.switchOn();
        }
    }
    
    
    a questo punto il TemperatureControllerTask può essere anche implementato come uno state pattern se le circostanze lo richiedono.
    Se poi serve un NullDevice è sufficiente fornire un'implementazione vuota all'interfaccia Device.
    Allargando un po' il discorso, si potrebbe anche implementare un PressureDevice (se la cosa ha senso) fornendo l'opportuna implementazione all'interfaccia Device, e poi passare l'oggetto a TemperatureControllerTask. Il succo del discorso è che la chiamata a action() non viene modificata.
    Il contratto è rispettato è la logica cambia in base all'oggetto (oggetti) istanziati in TemperatureControllerTask. (Quel che fa il pattern State appunto).
    Infine, potrei anche fare in modo che le interfacce heater e cooler abbiano diversi contratti, ed utilizzare la composizione https://pastebin.com/GdxW1iw, anche se alla fine le relative classi concrete hanno medesima implementazione; però se in futuro c'è la necessità di riscrivere la logica, la composizione mi permette di non apportare modifiche al task a differenza dell'uso dell'ereditarietà...o mi sbaglio?
    Ereditarietà e composizione si scelgono in base al criterio is a o has a.
    Come hai notato tu stesso sia Heater che Cooler sono Device, non hanno un Device, per cui qui si deve usare l'ereditarietà.
    Alla luce di questo, nel secondo link che hai postato è come se tu avessi messo un termostato in un termostato, il che non ha molto senso.
  • Re: Classi con diversa semantica ma stessa implementazione

    Spiegazione impeccabile, la ringrazio molto.

    shodan ha scritto:


    a questo punto il TemperatureControllerTask può essere anche implementato come uno state pattern se le circostanze lo richiedono.
    Si, in realtà non sono entrato nel merito per focalizzare l'attenzione sul dubbio che avevo sulla modellazione dei vari device.
    Il contesto richiede una modellazione con macchine a stati finiti per cui, anzichè creare delle classi-stato (in linea con lo state pattern), modello ogni task con una fsm.

    shodan ha scritto:


    Allargando un po' il discorso, si potrebbe anche implementare un PressureDevice (se la cosa ha senso) fornendo l'opportuna implementazione all'interfaccia Device, e poi passare l'oggetto a TemperatureControllerTask.
    Se ho capito bene, intende dire di creare un'unica classe concreta PressureDevice, che implementa l'interfaccia Device, e il cooler e l'heater istanze della classe PressureDevice?

    shodan ha scritto:


    Alla luce di questo, nel secondo link che hai postato è come se tu avessi messo un termostato in un termostato, il che non ha molto senso.
    Capisco perfettamente, infatti sembrava anche a me di arrampicarmi sugli specchi per trovare una soluzione migliore, inutilmente.
  • Re: Classi con diversa semantica ma stessa implementazione

    Se ho capito bene, intende dire di creare un'unica classe concreta PressureDevice, che implementa l'interfaccia Device, e il cooler e l'heater istanze della classe PressureDevice?
    No. Il PressureDevice è solo un esempio di quale potrebbe essere una classe aggiuntiva.
    Nel codice di esempio, TemperatureControllerTask si aspetta che le classi contenute rispettino il contratto di SwitchOn() e SwitchOff() per cui ogni classe derivata da Device va bene allo scopo. Per cui Device è l'interfaccia che stipula il contratto con TemperatureControllerTask, mentre Cooler, Heater, o quel che serve sono le classi concrete che implementano la logica di funzionamento dei vari dispositivi.
  • Re: Classi con diversa semantica ma stessa implementazione

    Scusa, ma il titolo ed il concetto espresso E' TOTALMENTE CANNATO (che e' peggio di SBAGLIATO )

    1) STESSA IMPLEMENTAZIONE IMPLICA STESSA SEMANTICA. Altrimenti PERCHE' AVERE DUE CLASSI CON LA STESSA IMPLEMENTAZIONE?

    Se hai DUE classi, avrai

    2) classi con STESSA SEMANTICA e DIVERSA IMPLEMENTAIZIONE (che ha PERFETTAMENTE senso)

    oppure, meglio

    3) una parte del comportamento ha la STESSA SEMANTICA
    4) poi, ci sono comportamenti specifici

    Infatti:

    1) due classi: Cooler, Heater
    2) STESSA SEMANTICA: switchOn, swtchOff
    3) DIVERSA IMPLEMENTAZIONE: CONCETTUALMENTE, anche se, per caso, hai fatto copia/incolla, essendo DUE OGGETTI di tipo DIVERSO, e' solo un caso, ma devi ragionare come se fossero implementazioni DIVERSE

    La tua affermazione successiva e' DI NUOVO SBAGLIATA:

    1) i due oggetti sono oggett che si possono ACCENDERE e SPEGNERE
    2) QUINDI devono implementare la STESSA INTERFACCIA, e cioe' l'interfaccia che descrive QUALUNQUE OGGETTO che puo' essere acceso/spento

    La tua affermazione finale e' QUASI giusta: le tue classi dovrebbero implementare DIVERSE interfacce: ogni interfaccia deve descrivere un particolare aspetto del comportamento degli oggetti modellati.

    In alternativa, ragionevole, le due interfacce specifiche derivano dalla stessa interfaccia base

    Quindi:

    1) poiche' possono essere accesi/spenti, implementano l'interfaccia interface IActivable { bool switchOn(); bool switchOff(); bool isOn(); }
    2) heater & cooler, inoltre, implementeranno interfacce specifiche per il loro utilizzo/comportamento
    interface IHeater extends IActivable, interface ICooler extends IActivable

    L'uso dell'interfaccia ti permette, un domani, di implementare un nuovo oggetto che fornisce gli stessi servizi di heater e cooler, ma in modo completamente diverso.

    Ad esempio:

    1) class RiscaldamentoMedianteBiondaGalattica implements IHeater {...}
    2) class RaffredamentoDovutoABucoInBancaGeneratoDaSpeseBiondaGalatica implements ICooler {...}

    (non so come fai a switchOn la BiondaGalattica o a switchOff il BucoInBanca, ma e' prevista l'implementazione nulla dei metodi )
  • Re: Classi con diversa semantica ma stessa implementazione

    Si effettivamente mi sono espresso malissimo.
    Approfondendo l'argomento ( ), se ho due classi che rispettano lo stesso contratto (quindi semanticamente uguali, per cui implementano la medesima interfaccia), la loro implementazione deve essere diversa, altrimenti che senso avrebbe?
    Questo problema si risolve tranquillamente creando un'unica classe che rispetta quel contratto, e due istanze di quella classe, o mi sbaglio?
    A meno che come, giustamente, mi suggerisce le due classi sono simili ma non uguali, nel senso che hanno una parte dello stesso comportamento, ma poi hanno ognuno specifici comportamenti.

    Le sue soluzioni le ho comprese, ma per le mie specifiche non credo siano utili (altrimenti illuminami )

    Specifiche dei requisiti: periodicamente si deve controllare la temperatura della serra, in modo tale da mantenere un clima favorevole alla crescita delle piante. Quindi, si deve accendere il riscaldamento quando la temperatura supera la soglia minima, mentre si deve accendere il sistema di raffreddamento quando la temperatura supera la soglia massima. Entrambi i sistemi (riscaldamento e di raffreddamento) si devono spegnere quando la temperatura è nel range desiderato. Inoltre, i due sistemi non possono essere accessi contemporaneamente (d'altronde fisicamente impossibile).

    Con queste specifiche io farei, come ha detto lei, un'interfaccia IActivable ma un'unica classe Device che la implementa. In questo modo, cooler e heater sono due istanze della classe Device.
    Altrimenti, se creo altre due interfacce, ICooler e IHeater, queste non avrebbero alcun contratto (sarebbero vuote) se non il medesimo dell'interfaccia IActivable, quindi non avrebbe senso, a mio parere.

    Altra ipotesi: se creo IActivable (con switchOn e switchOff), ICooler (con cool e noCool) e IHeater (con heat e noHeat) alla fine avrei che noCool e noHeat richiamano internamente switchOff; cool e heat richiamano switchOn. Un giro che a mio avviso non serve.

    Sia ben chiaro che non dico che le sue soluzioni non siano corrette per il mio caso, ma cerco di farle capire i miei dubbi
  • Re: Classi con diversa semantica ma stessa implementazione

    shodan ha scritto:


    Se ho capito bene, intende dire di creare un'unica classe concreta PressureDevice, che implementa l'interfaccia Device, e il cooler e l'heater istanze della classe PressureDevice?
    No. Il PressureDevice è solo un esempio di quale potrebbe essere una classe aggiuntiva.
    Nel codice di esempio, TemperatureControllerTask si aspetta che le classi contenute rispettino il contratto di SwitchOn() e SwitchOff() per cui ogni classe derivata da Device va bene allo scopo. Per cui Device è l'interfaccia che stipula il contratto con TemperatureControllerTask, mentre Cooler, Heater, o quel che serve sono le classi concrete che implementano la logica di funzionamento dei vari dispositivi.
    Se la logica in Heater e Cooler è la medisima possono creare un'unica classe e due diverse istanze, come detto anche a migliorabile...sarebbe più elegante la soluzione giusto?

    Avrei voluto creare due interfacce IHeater e ICooler per evitare di passare a TemperaturControllerTask un Device che non riscaldasse o che non raffreddasse...quindi piucchealtro una questione di "sicurezza", ma forse sbaglio a pensarla così...
Devi accedere o registrarti per scrivere nel forum
8 risposte