2017-11-28 1 views
1

Ich möchte nicht die tiefe Kopie machen.Wie kann man ein veränderbares Objekt sicher veröffentlichen?

Angenommen, ich habe ein Feld eines veränderlichen Typs, eine x, y, z Koordinate zum Beispiel. Gelegentlich muss ich dieses Feld einigen Zuschauern zugänglich machen. Und ich möchte, dass es schreibgeschützt ist. Ich erinnere mich, dass ich so etwas wie einen Wrapper gelesen habe, um diese Art von Sachen zu machen, aber ich erinnere mich nicht an die Details.

Das x, y, z-Koordinatenbeispiel ist möglicherweise zu einfach, weil x, y, z primitiver Typ sind. Also gibt getX() immer eine Kopie zurück.

Ich möchte eine allgemeine Lösung, auch wenn die x, y, z Felder von einem anderen veränderlichen Typ sind.

Kann jemand helfen?


EDIT:

public class Client 
{ 

    public static final Holder holder = new Holder(); 


    public static void main(String[] args) 
    { 

     UserWrapper user = holder.getUser(); 
     System.out.println(user);    //UserWrapper{user=User{address=Address{street='street 101'}}} 
     user.getAddress().setStreet("mars"); //UserWrapper{user=User{address=Address{street='mars'}}} 
     System.out.println(user); 

    } 
} 

public class Holder 
{ 

    private User user; 

    public Holder() 
    { 
     user = new User(); 
     Address address = new Address(); 
     address.setStreet("street 101"); 
     user.setAddress(address); 
    } 

    public UserWrapper getUser() 
    { 
     return new UserWrapper(user); 
    } 

} 

public class User 
{ 

    private Address address; 

    public Address getAddress() 
    { 
     return address; 
    } 

    public void setAddress(Address address) 
    { 
     this.address = address; 
    } 
} 

public class UserWrapper 
{ 

    private User user; 

    public UserWrapper(User user) 
    { 
     this.user = user; 
    } 

    public Address getAddress() 
    { 
     return user.getAddress(); 
    } 
} 

EDIT: Kredit I don't know who (er löscht die Antwort), finde ich this link er in seinem ursprünglichen Beitrag erwähnt sehr hilfreich.

+0

Was meinen Sie durch die * tiefe Kopie Art und Weise *? – jrook

+0

OK, also schreibe einen schreibgeschützten Wrapper darum herum. Was ist deine Frage? – azurefrog

+0

@azurefrog Angenommen, das besagte Feld ist ein 'User'-Objekt. Ich kann einen schreibgeschützten Wrapper um 'User' schreiben, der die Setter versteckt, so dass der Client 'getUserWrapper() nicht aufrufen kann. SetAddress()'; Aber sie können immer noch 'getUserWrapper() aufrufen. GetAddress(). SetStreet()'; Das ist, was ich meine. – du369

Antwort

2

Die traditionellen Wege:

  • tiefe Kopie - verhindert, dass Mutationen des Clients auswirken, die
  • unveränderliche Objekte liest - anstelle des Kopierens für den Client, kopieren Sie einen alten Zeiger zu aktualisieren und erhält der Client Referenz.
  • Kunden-Iterator - Sie stellen Ihre eigene Iterator-/Navigationsschnittstelle bereit, die auf ein mit der Datenstruktur eingebettetes Feld "Version" reagiert. Vor dem Besuch jedes Elements prüft es, dass die Version seit der Erstellung des Iterators nicht geändert wurde (java collections tut dies).
  • starke Synchronisation - während ein Leser liest, hält der Leser eine Sperre für die Datenstruktur verhindert Update. Im Allgemeinen eine schlechte Lösung, aber gelegentlich nützlich (zur Vollständigkeit enthalten).
  • Lazy Copy - Sie konstruieren ein Objekt, das hauptsächlich auf das Original verweist, aber (als Listener) auf das Original getriggert wird. Wenn eine Mutation am Original erfolgt, kopieren Sie den vormutierten Wert lokal. Dies ist wie eine faule tiefe Kopierstrategie.

Es gibt andere, aber diese sollten Sie beginnen.

2

Es gibt keinen eingebauten Mechanismus in Java, der Ihnen dies ermöglicht. Normalerweise, wenn Sie Instanzen bewegen, würden Sie entweder:

  1. Verwendung unveränderliche Objekte
  2. Pass auf Kopien der Objekte

Da Sie nicht wollen/können nicht entscheiden, ob der Auf diese Weise müssen Sie eine Alternative verwenden. Es gibt viele verschiedene Möglichkeiten, dies je nach Ihren Anforderungen und der Komplexität Ihrer Klassenstruktur zu implementieren. Der allgemeine Ansatz wäre jedoch, anstelle des Originals einen unveränderlichen Wrapper zu veröffentlichen.
Hier sind einige Beispiele:

public class XYZ { 
    public int x, y, z; 
} 

public class XYZWrapper { 
    private XYZ xyz; 

    public XYZWrapper(XYZ xyz) { 
     this.xyz = xyz; 
    } 

    public int getX() { return x; } 
    public int getY() { return y; } 
    public int getZ() { return z; } 
} 

public class Address { 
    public String name; 
    public XYZ xyz; 
} 

public class AddressWrapper { 
    private String name; // Note that this could be public since any String is immutable 
    private XYZWrapper xyzWrapper; 

    public AddressWrapper(String name, XYZ xyz) { 
     this.name = name; 
     this.xyzWrapper = new XYZWrapper(xyz); 
    } 

    public String getName() { 
     return name; 
    } 

    public XYZWrapper getXYZWrapper() { 
     return xyzWrapper; 
    } 
} 

Nun, wenn statt XYZ und Address Klassen, können Sie mit Schnittstellen arbeiten, können Sie 2-Implementierungen (zB XYZMutable & XYZImmutable), die Sie zu abstrahieren ermöglichen, welche Art von Klasse Sie werden zurückkehren und können auch eine Instanz von XYZImmutable aus einer Instanz von XYZMutable erstellen (vorausgesetzt, die Schnittstelle definiert nur & alle Getter-Methoden).

Noch ein Hinweis zu diesem Ansatz (besonders, wenn Sie den bevorzugten Weg über Interfaces verwenden): Selbst wenn Sie eine komplexe Klassenhierarchie haben, können Sie dies relativ mühelos tun, indem Sie eine Generatorklasse erstellen, die eine Schnittstelleninstanz empfängt. eine änderbare Implementierungsinstanz und gibt eine unveränderliche Implementierungsinstanz als Rückgabewert zurück.

+0

Ich verstehe, was du meinst. Das ist sehr hilfreich! Und eine andere Antwort erwähnt, dass "Mein unveränderlichen Wrapper hier ist nicht wirklich unveränderlich" ist auch sehr gut. Leider hat der Autor dieser Antwort seinen Beitrag gelöscht. – du369

1

Vielleicht denken Sie an die "copy on write" Redewendung. Auf diese Weise können Sie das Kopieren vermeiden, es sei denn, Sie müssen es tun. Die Verwendung wird im Allgemeinen nicht empfohlen, da sie nicht threadsicher ist, es sei denn, Sie verwenden eine Synchronisierung, die Anwendungen mit Singlethread unnötig verlangsamt.

Es funktioniert, indem es eine Referenzzählung seiner internen Daten führt; so etwas wie dieses ungetestete Stück Code:

public class User 
{ 
    private int addressReferenceCount; 
    private Address address; 

    public User(Address address) { 
     addressReferenceCount = 0; 
     this.address = address; 
    } 

    public Address getAddress() { 
     addressReferenceCount++; 
     return address; 
    } 

    public void setAddress(Address address) 
    { 
     if (addressReferenceCount == 0) { 
      this.address = address; 
     } 
     else { 
      this.address = new Address(address); 
      addressReferenceCount = 0; 
     } 
    } 
} 

dies, dass wie dieser Benutzercode sorgt für unterschiedliche Adressen erhalten, wenn nötig:

User u = new User(new Address("1 Acacia Avenue")); 
Address oldAddress = u.getAddress(); 
Address stillOldAddress = u.getAddress(); 
u.setAddress(new Address("2 Acacia Avenue")); 
Address newAddress = u.getAddress(); 
assert (oldAddress == stillOldAddress); // both refer to same object 
assert (oldAddress != newAddress); 
+1

COW (Kopie beim Schreiben) wird normalerweise mit unveränderlichen Objekten kombiniert. Anstatt zu kopieren, um sicherzustellen, dass der Client eine unveränderbare Kopie erhält, erstellen Sie eine neue Datenstruktur, koordinieren Sie das Programm, um es zu verwenden, und der Client erhält die alte Kopie. –

Verwandte Themen