2015-10-28 13 views
6

Sono abbastanza nuovo al concetto di classi immutabili. Considerate questa classe:Immutabilità vs cambio di stato in una classe

public class ConnectionMonitor implements MessageConsumer { 

    private final MonitorObject monitorObject; 
    private boolean isConnected = true; 

    private final static Logger logger = LogManager.getLogger(ConnectionMonitor.class); 

    public ConnectionMonitor(final MonitorObject monitorObject) { 
     this.monitorObject = monitorObject; 
    } 

    public boolean isConnected() { 
     return isConnected; 
    } 

    public void waitForReconnect() { 
     logger.info("Waiting for connection to be reestablished..."); 
     synchronized (monitorObject) { 
      enterWaitLoop(); 
     } 
    } 

    private void enterWaitLoop() { 
     while (!isConnected()) { 
      try { 
       monitorObject.wait(); 
      } catch (final InterruptedException e) { 
       logger.error("Exception occured while waiting for reconnect! Message: " + e.getMessage()); 
      } 
     } 
    } 

    private void notifyOnConnect() { 
     synchronized (monitorObject) { 
      monitorObject.notifyAll(); 
     } 
    } 

    @Override 
    public void onMessage(final IMessage message) { 
     if (message.getType() == IMessage.Type.CONNECTION_STATUS) { 
      final String content = message.getContent(); 
      logger.info("CONNECTION_STATUS message received. Content: " + content); 
      processConnectionMessageContent(content); 
     } 
    } 

    private void processConnectionMessageContent(final String messageContent) { 
     if (messageContent.contains("Disconnected")) { 
      logger.warn("Disconnected message received!"); 
      isConnected = false; 
     } else if (messageContent.contains("Connected")) { 
      logger.info("Connected message received."); 
      isConnected = true; 
      notifyOnConnect(); 
     } 
    } 
} 

Sto cercando di capire come questa classe potrebbe essere cambiato ad uno immutabile.

In particolare, non vedo come il campo booleano isConnected possa essere reso definitivo, poiché rappresenta lo stato della connessione.

Tutti i client di ConnectionMonitor devono solo richiedere isConnected() per ottenere lo stato della connessione.

Sono consapevole che il blocco delle modifiche su isConnected è possibile o si utilizza un booleano atomico.

Ma non vedo come riscrivere questo in una classe immutabile.

+0

Non tutti gli oggetti devono essere immutabili. –

+1

Quindi mi serve solo più esperienza per giudicare quando creare classi mutevoli/immutabili ?! – Juergen

+0

L'esperienza aiuta. In generale, preferisci l'immutabilità quando possibile, ma solo quando possibile. Ho aggiunto più dettagli in una risposta qui sotto. –

risposta

5

L'ideale è minimizza la mutabilità - non eliminarlo.

Gli oggetti immutabili presentano numerosi vantaggi. Sono semplici, thread-safe e possono essere condivisi liberamente.

Tuttavia, a volte abbiamo bisogno di mutabilità.

In "Effective Java", Joshua Bloch suggerisce queste linee guida:

  • classi dovrebbero essere immutabili a meno che non ci sia una buona ragione per farli mutevole.
  • Se una classe non può essere resa immutabile, limitare la sua mutabilità il più possibile.

Nel tuo esempio, c'è una buona ragione per le istanze della classe ad essere mutevole. Ma puoi anche vedere la seconda linea guida in gioco: il campo monitorObject è contrassegnato come finale.

+0

Sembra un consiglio ragionevole. Dopo aver interrogato sull'argomento ho avuto l'impressione che tutte le classi dovrebbero essere immutabili. Ma mi chiedo come sia possibile farlo e perché non usare un puro linguaggio di programmazione funzionale. – Juergen

-1

Nessun oggetto della vita reale (quando non si considerano le classi con un unico scopo) al di fuori del libro di testo o della cantina del paradigma zelota è immutabile al 100% o apolide. Coloro che predicano l'immutabilità alle esposizioni ogni volta che ottengono una possibilità di solito assomigliano molto al suono della loro voce. C'è sempre un certo grado di mutabilità, ma la chiave qui è farlo in modo sicuro e non violento e non modificare gli oggetti solo perché. Se riesci a capire perché una determinata parte dell'oggetto debba essere mutabile e conosci tutti i possibili effetti collaterali prima della mano, è perfettamente adatto per essere mutabile. Come hai detto, il valore booleano isConnected deve avere setter e getter o qualche altro meccanismo che rifletta lo stato corrente della connessione. E poiché è fondamentalmente un interruttore binario che ha solo due stati, l'impatto di esso è mutabile è minimo. Potresti ottenere lo stesso risultato in qualche altro modo, ma dovresti passare attraverso cerchi e loop per ottenerlo con un metodo esterno di classe diversa.

TL; DR: Lo stato mutevole non è il male stesso, ma può essere utilizzato in modo scorretto per causare grande malvagità.

+0

Basta chiedersi, perché i downvotes? Non penso che la mia risposta sia effettivamente errata. –

+0

Forse tu sei una di quelle persone che dice che se è immutabile, allora non dovremmo chiamarlo un "oggetto", ma se davvero credi che nessuno scriva mai classi Java che abbiano solo campi 'finali', allora posso mostrarti qualche migliaio di contro esempi. –

+3

Non sono uno dei downvoters, ma l'affermazione che nessun oggetto della vita reale è immutabile non è chiaramente vero ('String',' BigInteger', 'Double',' ImmutableList', 'new int [0]', la maggior parte delle costanti enum, 'Locale', etc etc). Il punto qui è che non tutte le classi dovrebbero essere immutabili, non che non ha senso immutabilità. –

2

Basta mettere quello stato da qualche altra parte. Ma per la tua situazione, è logico? Dovresti chiedertelo.

Forse è meglio lasciare ConnectionMonitor mutabile.È responsabile del "monitoraggio" di una connessione, quindi è tenuto a tenere traccia dei valori che possono cambiare. Altrimenti, avrai bisogno di un altro oggetto che sia mutabile per tenere traccia di quello stato.

Se questo non è abbastanza convincente, poi qui ci sono alcuni modi:

si potrebbe avere una classe di contenitore per i monitor, che mappano ConnectionMonitors alla sua ConnectionState:

class MonitorManager { 
    Map<ConnectionMonitor, Boolean> connectionStatuses = ...; 
} 

Per farla semplice, si potrebbe passare questo manager per ogni monitor, permettendo all'ascoltatore di accedere alla mappa e modificare il valore booleano per quella connessione:

class ConnectionMonitor { 
    private MonitorManager manager; 

    //.... 

    private void processConnectionMessageContent(final String messageContent) { 
     if (messageContent.contains("Disconnected")) { 
      logger.warn("Disconnected message received!"); 
      manager.connectionStatuses.put(this, false); 
     } else if (messageContent.contains("Connected")) { 
      logger.info("Connected message received."); 
      manager.connectionStatuses.put(this, true); 
      notifyOnConnect(); 
     } 
    } 
} 

Ma som Gli sviluppatori si allineano per dirti che gli oggetti figlio non dovrebbero conoscere i loro contenitori.

Quindi creare un nuovo oggetto questo è responsabile per i dati raccolti durante il monitoraggio della connessione:

class MonitorManager { 
    private Map<ConnectionMonitor, MonitoredData> data = ...; 

    public void createMonitor() { 
     MonitoredData data = new MonitoredData(); 
     this.data.put(new ConnectionMonitor(data), data); 
    } 
} 

class ConnectionMonitor inplements MessageConsumer { 
    private MonitoredData data; 

    public ConnectionMonitor(MonitoredData data) { 
     this.data = data; 
    } 

    //... 

    private void processConnectionMessageContent(final String messageContent) { 
     if (messageContent.contains("Disconnected")) { 
      logger.warn("Disconnected message received!"); 
     data.setConnected(false); 
    } else if (messageContent.contains("Connected")) { 
      logger.info("Connected message received."); 
      data.setConnected(true); 
      notifyOnConnect(); 
     } 
    } 
} 

class MonitoredData { 
    private boolean connected; 

    public void setConnected(boolean connected) { 
     this.connected = connected; 
    } 

    public boolean isConnected() { 
     return connected; 
    } 
} 

Forse i dettagli in MonitoredData sarebbe migliore vestibilità nell'oggetto che viene monitorato. Sarebbe più semplice aiutare se fornissi più contesto.

+0

Grazie per la risposta lunga e le alternative. Ma nel mio stile cerco di evitare setter/mutatori come "MonitoredData". Finora credo che le classi immutabili siano più utili per gli oggetti valore, ma ConnectionMonitor ha identità. – Juergen

+0

@Juergen Capisco, anch'io uso uno stile più comportamentale. Ma per tener conto di ciò, 'connected' dovrebbe essere impostato quando vengono chiamati' connect() 'o' disconnect() '(stato modificato dal comportamento). Ma in questo caso, non hai il controllo di chiamare tali metodi (se lo sei, questo è il percorso che dovresti prendere). Sebbene gli oggetti abbiano un comportamento, alcuni oggetti sono oggetti dati (memoria persistente per esempio, che 'read' rappresenta un getter in forma base e' create' o 'add' rappresenta un setter in un certo senso) –