2015-10-28 9 views
6

Ich bin ziemlich neu in das Konzept der unveränderlichen Klassen. Betrachten Sie diese Klasse:Unveränderlichkeit vs Zustandswechsel in einer Klasse

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

Ich versuche zu verstehen, wie diese Klasse zu einem unveränderlichen einem geändert werden könnte.

Insbesondere sehe ich nicht, wie das boolesche Feld isConnected endgültig gemacht werden könnte, da es den Verbindungsstatus darstellt.

Alle Clients von ConnectionMonitor sollten nur isConnected() abfragen, um den Verbindungsstatus zu erhalten.

Ich bin mir bewusst, dass Sperren Änderungen an isConnected ist möglich oder mit einem atomaren boolean.

Aber ich sehe nicht, wie man dies in eine unveränderliche Klasse umschreiben kann.

+0

Nicht alle Objekte sollten unveränderlich sein. –

+1

Also brauche ich nur mehr Erfahrung zu beurteilen, wann man veränderbare/unveränderliche Klassen erstellen kann ?! – Juergen

+0

Erfahrung hilft. Im Allgemeinen bevorzugen Sie die Unveränderlichkeit wenn möglich - aber nur wenn es möglich ist. Ich habe mehr Details in einer Antwort unten hinzugefügt. –

Antwort

5

Das Ideal ist zu minimieren Wandlungsfähigkeit - nicht zu beseitigen.

Unveränderbare Objekte haben mehrere Vorteile. Sie sind einfach, Thread-sicher und können frei geteilt werden.

Manchmal brauchen wir jedoch Mutabilität.

In "Effective Java", schlägt Joshua Bloch diese Richtlinien:

  • Klassen unveränderlich sein sollte, es sei denn, ein sehr guter Grund ist sie wandelbar zu machen.
  • Wenn eine Klasse nicht unveränderbar gemacht werden kann, begrenzen Sie ihre Veränderlichkeit so weit wie möglich.

In Ihrem Beispiel gibt es einen sehr guten Grund für Instanzen der Klasse wandelbar zu sein. Sie können aber auch die zweite Richtlinie im Spiel sehen: Das Feld monitorObject ist als final gekennzeichnet.

+0

Klingt wie vernünftiger Rat. Nach der Abfrage des Themas bekam ich den Ausdruck, dass alle Klassen unveränderlich sein sollten. Aber ich frage mich, wie das gemacht werden kann und warum nicht stattdessen eine reine funktionale Programmiersprache verwendet wird. – Juergen

-1

Kein reales Objekt (wenn nicht einzelne Klassen in Betracht gezogen werden) außerhalb des Lehrbuchs oder Paradigmas Eiferkeller ist 100% unveränderlich oder zustandslos. Diejenigen, die auf den Ausstellungen jedes Mal, wenn sie eine Chance haben, unveränderlich predigen, mögen normalerweise nur den Klang ihrer Stimme. Es gibt immer ein gewisses Maß an Veränderlichkeit, aber der Schlüssel liegt darin, es auf sichere und gewaltfreie Weise zu tun und Objekte nicht einfach deshalb mutieren zu lassen. Wenn Sie nachvollziehen können, warum bestimmte Teile des Objekts veränderbar sein sollten und Sie alle möglichen Nebenwirkungen vorher kennen, ist es vollkommen in Ordnung, wenn es veränderlich ist. Wie Sie gesagt haben, muss der boolesche isConnected Setter und Getter oder einen anderen Mechanismus haben, damit er den aktuellen Verbindungsstatus widerspiegelt. Und da es sich im Grunde um einen binären Schalter handelt, der nur zwei Zustände hat, ist der Einfluss davon, dass er veränderlich ist, minimal. Sie könnten das gleiche Ergebnis auf andere Weise erreichen, aber Sie müssten durch Schleifen und Schleifen gehen, um es mit Outsider-Methode aus verschiedenen Klassen zu erreichen.

TL: DR: Veränderbarer Zustand ist nicht das Böse selbst, aber er kann missbraucht werden, um das große Übel zu verursachen.

+0

Ich frage mich nur, warum die Downvotes? Ich glaube nicht, dass meine Antwort sachlich falsch ist. –

+0

Vielleicht sind Sie einer jener Leute, die sagen, wenn es unveränderlich ist, dann sollten wir es kein "Objekt" nennen, aber wenn Sie wirklich glauben, dass niemand jemals Java-Klassen schreibt, die nur "endgültige" Felder haben, dann kann ich es Ihnen zeigen ein paar tausend Gegenbeispiele. –

+3

Ich gehöre nicht zu den Downvotern, aber die Behauptung, dass kein reales Objekt unveränderlich ist, ist eindeutig nicht wahr ('String',' BigInteger', 'Double',' ImmutableList', 'new int [0]', die meisten enum Konstanten, 'Locale', etc etc). Der Punkt hier ist, dass nicht alle Klassen unveränderlich sein sollten, nicht, dass es keinen Sinn in Unveränderlichkeit gibt. –

2

Setzen Sie einfach diesen Zustand woanders hin. Aber für Ihre Situation, ist es logisch zu? Sie sollten sich das fragen.

Vielleicht ist es am besten zu verlassen ConnectionMonitor veränderbar.Es ist verantwortlich für das "Überwachen" einer Verbindung, so dass es sicher ist, dass sich die Werte, die sich ändern können, verfolgen lassen. Andernfalls benötigen Sie ein anderes Objekt, das veränderbar ist, um diesen Status zu verfolgen.

Wenn das genug nicht überzeugend ist, dann sind hier einige Möglichkeiten:

Sie könnten eine Container-Klasse für Ihre Monitore haben, die ConnectionMonitors Karte, um es Connection ist:

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

Um es einfach zu machen, Sie diese Manager konnte zu jedem Monitor übergeben, den Hörer so dass die Karte für den Zugriff auf und den booleschen Wert für diese Verbindung:

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

Aber som Die Entwickler würden sich anordnen, um Ihnen zu sagen, dass untergeordnete Objekte nicht über ihre Container wissen sollten.

erstellen So ein neues Objekt, das ist verantwortlich für die Daten, während der Verbindungsüberwachung gesammelt:

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

Vielleicht die Details in MonitoredData würde besser passen in dem Objekt, das überwacht wird. Wäre einfacher zu helfen, wenn Sie mehr Kontext geben würden.

+0

Danke für die lange Antwort und Alternativen. Aber in meinem Stil versuche ich Setter/Mutatoren wie "MonitoredData" zu vermeiden. Bis jetzt glaube ich, dass unveränderliche Klassen für Wertobjekte nützlicher sind, aber ConnectionMonitor hat Identität. – Juergen

+0

@Juergen Ich verstehe, ich benutze auch einen eher verhaltensorientierten Stil. Um dies zu erreichen, müsste 'connected' gesetzt werden, wenn' connect() 'oder' disconnect() 'aufgerufen werden (Zustand durch Verhalten geändert). Aber in diesem Fall haben Sie keine Kontrolle über das Aufrufen solcher Methoden (wenn Sie das sind, sollten Sie die Route wählen). Obwohl Objekte Verhalten haben, sind einige Objekte Datenobjekte (z. B. persistenter Speicher, wobei "read" in Grundform einen Getter darstellt und "create" oder "add" in gewissem Sinne einen Setter darstellt). –

Verwandte Themen