如何有效实现可变对象的不可变和只读版本?

huangapple go评论75阅读模式
英文:

How to implement immutable and read-only versions of mutable objects effectively?

问题

Context:

* package data, public:

public interface _Data {
    public String getData();
}

public class _PackageAPI {
    DataHolder holder;

    public void createHolder(String data) {
        holder = new DataHolder();
        holder.setData(data);
    }

    public void mutateHolder(String data) {
        holder.setData(data);
    }

    public _Data getSnapshot() {
        return DataSnapshot.from(holder.getData());
    }

    public _Data getReader() {
        return holder.readOnly();
    }
}

* package data, package-private:

class DataHolder {
    private String data;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public _Data readOnly() {
        return new _Data() {
            @Override
            public String getData() {
                return DataHolder.this.data;
            }
        };
    }
}

class DataSnapshot {
    public static _Data from(String data){
        return new _Data() {
            @Override
            public String getData() {
                return data;
            }
        };
    }
}

* sample client usage:

package clientPackage;

import data._Data;
import data._PackageAPI;

public class ExampleRunner {

    public static void main(String[] args) {

        _PackageAPI handler;

        System.out.println("Creating...");

        handler = new _PackageAPI();
        handler.createHolder("INITIAL DATA");

        System.out.println("Done creating...");

        _Data initialSnapShot =  handler.getSnapshot();
        _Data initialReader = handler.getReader();

        System.out.println("initialSnapShot holds :" + initialSnapShot.getData() );
        System.out.println("initialSnapShot class :" + initialSnapShot.getClass() );
        System.out.println("initialReader class :" + initialReader.getClass() );

        System.out.println("initialReader holds :" + initialReader.getData() );

        System.out.println("Mutating...");

        handler.mutateHolder("MUTATED DATA");
        _Data subsequentSnapShot =  handler.getSnapshot();
        _Data subsequentReader = handler.getReader();

        System.out.println("Done mutating...");

        System.out.println("initialSnapShot holds :" + initialSnapShot.getData() );
        System.out.println("initialReader holds :" + initialReader.getData() );

        System.out.println("subsequentSnapShot holds :" + subsequentSnapShot.getData() );
        System.out.println("subsequentReader holds :" + subsequentReader.getData() );
    }
}

* And console output:

Creating...
Done creating...
initialSnapShot holds :INITIAL DATA
initialSnapShot class :class data.DataSnapshot$1
initialReader class :class data.DataHolder$1
initialReader holds :INITIAL DATA
Mutating...
Done mutating...
initialSnapShot holds :INITIAL DATA
initialReader holds :MUTATED DATA
subsequentSnapShot holds :MUTATED DATA
subsequentReader holds :MUTATED DATA

* FIRST QUESTION : given getSnapshot() returns a _Data (of class : DataSnapshot$1) whose method getData() returns the "real" data reference, ie the content of the variable data of the DataHolder object, is this safe or is it somehow possible to mutate DataHolder leveraging access to this reference? If yes, how ?

* FIRST QUESTION SHORTENED : is it anyhow possible to mutate content of the memory referenced by a reference, using only the reference ?

(Of course solution to this is to clone the String being referenced.)

* SECOND QUESTION : is there anyway to mutate a DataSnapshot$1 (the "immutable" version of _Data) instance ?

* THIRD QUESTION : given DataHolder$1 (the "readOnly" version of _Data) holds internally a reference to the DataHolder instance providing it, is it safe to expose such a DataHolder$1, or is there anyway to mess with the DataHolder instance from the DataHolder$1 object ?

EDIT : I would have put a paranoid tag if there was one
英文:

Context :

  • package data, public :
public interface _Data {
public String getData();
}
public class _PackageAPI {
DataHolder holder;
public void createHolder(String data) {
holder = new DataHolder();
holder.setData(data);
}
public void mutateHolder(String data) {
holder.setData(data);
}
public _Data getSnapshot() {
return DataSnapshot.from(holder.getData());
}
public _Data getReader() {
return holder.readOnly();
}
}
  • package data, package-private :
class DataHolder {
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public _Data readOnly() {
return new _Data() {
@Override
public String getData() {
return DataHolder.this.data;
}
};
}
}
class DataSnapshot {
public static _Data from(String data){
return new _Data() {
@Override
public String getData() {
return data;
}
};
}
}
  • sample client usage :
package clientPackage;
import data._Data;
import data._PackageAPI;
public class ExampleRunner {
public static void main(String[] args) {
_PackageAPI handler;
System.out.println("Creating...");
handler = new _PackageAPI();
handler.createHolder("INITIAL DATA");
System.out.println("Done creating...");
_Data initialSnapShot =  handler.getSnapshot();
_Data initialReader = handler.getReader();
System.out.println("initialSnapShot holds :" + initialSnapShot.getData() );
System.out.println("initialSnapShot class :" + initialSnapShot.getClass() );
System.out.println("initialReader class :" + initialReader.getClass() );
System.out.println("initialReader holds :" + initialReader.getData() );
System.out.println("Mutating...");
handler.mutateHolder("MUTATED DATA");
_Data subsequentSnapShot =  handler.getSnapshot();
_Data subsequentReader = handler.getReader();
System.out.println("Done mutating...");
System.out.println("initialSnapShot holds :" + initialSnapShot.getData() );
System.out.println("initialReader holds :" + initialReader.getData() );
System.out.println("subsequentSnapShot holds :" + subsequentSnapShot.getData() );
System.out.println("subsequentReader holds :" + subsequentReader.getData() );
}
}
  • And console output:
Creating...
Done creating...
initialSnapShot holds :INITIAL DATA
initialSnapShot class :class data.DataSnapshot$1
initialReader class :class data.DataHolder$1
initialReader holds :INITIAL DATA
Mutating...
Done mutating...
initialSnapShot holds :INITIAL DATA
initialReader holds :MUTATED DATA
subsequentSnapShot holds :MUTATED DATA
subsequentReader holds :MUTATED DATA
  • FIRST QUESTION : given getSnapshot() returns a _Data (of class : DataSnapshot$1) whose method getData() returns the "real" data reference, ie the content of the variable data of the DataHolder object, is this safe or is it somehow possible to mutate DataHolder leveraging access to this reference? If yes, how ?

  • FIRST QUESTION SHORTENED : is it anyhow possible to mutate content of the memory referenced by a reference, using only the reference ?

(Of course solution to this is to clone the String being referenced.)

  • SECOND QUESTION : is there anyway to mutate a DataSnapshot$1 (the "immutable" version of _Data) instance ?

  • THIRD QUESTION : given DataHolder$1 (the "readOnly" version of _Data) holds internally a reference to the DataHolder instance providing it, is it safe to expose such a DataHolder$1, or is there anyway to mess with the DataHolder instance from the DataHolder$1 object ?

EDIT : I would have put a paranoïd tag if there was one

答案1

得分: 0

以下是翻译好的内容:

> 是否安全,或者是否有可能通过访问该引用来突变 DataHolder?如果是,如何操作?

由于 getData 返回的是一个不可变的 String,调用者无法通过返回的引用来进行任何更改。您也无法通过 String 来访问 DataHolder。为什么 String 会知道您的 DataHolder 类呢?

> 有没有办法突变 DataSnapshot$1

没有,因为它是一个只有一个方法的匿名内部类,该方法返回一个参数。参数是按值传递,因此您不必担心调用者会在另一侧更改其值。它还是一个 String,这意味着调用者也不会对对象进行突变。

您可能之所以问这个问题,是因为您看到了 initialReader 是如何改变的。嗯,由于 DataHolder$1 是可变的 DataHolder 的内部类,即使它没有任何改变器方法,它实际上并不是不可变的。

> 是否安全暴露这样一个 DataHolder$1,或者是否有办法通过 DataHolder$1 对象操纵 DataHolder 实例?

无法从内部类访问外部类,因此由于 DataHolder$1 中没有改变器方法,您无法从外部突变 DataHolder,仅通过 DataHolder$1 也无法。

然而,如果 DataHolder 发生变化,这些变化将会反映在 DataHolder$1 上(正如您的示例代码所示),我认为这违背了不可变性的目的。

<hr>

以下是我在这种情况下如何实现不可变性。

我会让 DataHolder 实现 _DataDataHolder 当然可以做到这一点,不是吗?毕竟它有一个 String getData() 方法!DataHolder 将会有一个 asReadOnly 方法,该方法创建一个 this 的副本并返回 _Data。事实上,我会将 _Data 重命名为 ReadOnlyData

英文:

> is this safe or is it somehow possible to mutate DataHolder leveraging access to this reference? If yes, how ?

Since getData returns a String, which is immutable, the caller can't change anything through the returned reference. You can't access a DataHolder through a String either. Why would String know about your DataHolder class?

> is there anyway to mutate a DataSnapshot$1?

No, because it is an anonymous inner class that only has one method that returns a parameter. Parameters are passed by value, so you don't need to worry about callers changing their values on the other side. It's also a String, which means the callers won't be mutating the object either.

You might be asking this because you saw how initialReader has changed. Well, since DataHolder$1 is an inner class of the mutable DataHolder, it's not really immutable even if it doesn't have any mutator methods.

> is it safe to expose such a DataHolder$1, or is there anyway to mess with the DataHolder instance from the DataHolder$1 object ?

There is no way to access the outer class from the inner class, so since there are no mutator methods in DataHolder$1, you can't mutate DataHolder from the outside, with only a DataHolder$1.

However, if DataHolder changes, the changes will reflect on DataHolder$1 (as shown in your sample code), which I think defeats the purpose of immutability.

<hr>

Here's how I would implement immutability in this scenario.

I would make DataHolder implement _Data. DataHolder certainly can do this, can't it? It has a String getData() method after all! DataHolder would have an asReadOnly method that creates a copy of this and returns _Data. In fact, I'd rename _Data to ReadOnlyData.

huangapple
  • 本文由 发表于 2020年4月7日 02:48:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/61066861.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定