Защитная копия с эффективной Java - PullRequest
18 голосов
/ 16 марта 2012

Я читаю «Эффективную Яву» Джошуа Блоха, пункт 39 делает защитную копию, и у меня есть несколько вопросов.Я всегда использую следующую конструкцию:

MyObject.getSomeRef().setSomething(somevalue);

, что означает:

SomeRef s = MyClass.getSomeRef();
s.setSomething();
MyObject.setSomeRef(s);

Это всегда работает, но я думаю, что если мой getSomeRef() возвращал копию, то мой ярлыкне работает, как я могу узнать, скрыта ли реализация MyObject, безопасно ли использовать ярлык или нет?

Ответы [ 5 ]

26 голосов
/ 16 марта 2012

Вы нарушаете два правила OO программирования:

  • не говорите с незнакомцами
  • инкапсуляция

Обратите внимание, что эти правила - просто правилаи что они могут или даже должны иногда нарушаться.

Но если некоторые данные принадлежат объекту, и объект должен гарантировать некоторые инварианты для объектов, которыми он владеет, то он не должен выставлять своиизменяемые внутренние структуры данных наружу.Отсюда необходимость защищенной копии.

Еще одна часто используемая идиома - возвращать неизменяемые представления изменяемых структур данных:

public List<Foo> getFoos() {
    return Collections.unmodifiableList(this.foos);
}

Эта идиома, или идиома защитной копии, может быть важнойНапример, если вы должны убедиться, что каждая модификация списка проходит через объект:

public void addFoo(Foo foo) {
    this.foos.add(foo);
    someListener.fooAsBeenAdded(foo);
}

Если вы не сделаете защитную копию или не вернете неизменяемый вид списка, вызывающая сторона можетдобавьте foo в список напрямую, и слушатель не будет вызван.

10 голосов
/ 16 марта 2012

Документация - это способ, которым вы (должны) знать. MyObject должен документировать, могут ли экспонаты, которые он выставляет, использовать для изменения MyObject. Вы должны когда-либо изменять объект только способами, явно предоставленными классом.

Например, вот Javadocs для двух методов в List, один из которых результат не может быть использован для изменения List, а другой - результат List:

toArray():

Возвращенный массив будет «безопасным», так как в этом списке нет ссылок на него. (Другими словами, этот метод должен выделять новый массив, даже если этот список поддерживается массивом). Таким образом, вызывающая сторона может изменять возвращаемый массив.

subList():

Возвращенный список поддерживается этим списком, , поэтому неструктурные изменения в возвращенном списке отражаются в этом списке , и наоборот. Возвращенный список поддерживает все необязательные операции списка, поддерживаемые этим списком.

Я бы сказал, что молчание из документации означает, что вы не должны использовать его для изменения объекта (используйте его только для чтения).

2 голосов
/ 16 марта 2012

Защитная копия - хорошая идея, но вы должны понимать, когда и где она используется. Если сеть объектов, которыми вы манипулируете, является внутренней и не предназначена для обеспечения многопоточной обработки, тогда защитное копирование применяется неправильно.

С другой стороны, если это сеть объектов, которые становятся достоянием общественности, то вы рискуете нарушить Закон Деметры . Если это так, рассмотрите возможность использования API манипулятора на вашем myObject.

Как пример, ваш пример кода сделал getSomeRef похожим на статический API. Я бы предложил вам назвать любой статический API, который возвращает копию некоторого синглтона соответственно (например, copyOfSomething()). Аналогично для статического фабричного метода.

1 голос
/ 20 сентября 2012

Я бы предложил определить readableThing интерфейс или класс и извлечь из него mutableThing и immutableThing интерфейсы.Получатель свойства должен вернуть один из этих интерфейсов на основе отношения возвращаемого элемента к списку:

  1. Он должен вернуть mutableThing, если вещь может быть безопасно изменена таким образом, что изменения будут сохранены восновной список.
  2. Он должен возвращать readableThing, если получатель объекта не может использовать его для изменения коллекции, но есть вероятность, что будущие операции с коллекцией могут повлиять на объект.
  3. Он должен вернуть immutableThing, если он может гарантировать, что рассматриваемый объект никогда не изменится.
  4. Если ожидаемым результатом метода является то, что вызывающий объект должен иметь изменяемую вещь, которая инициализируется данными из коллекции, но которая не прикреплена к нему, я бы предложил иметь метод, который принимает mutableThing от вызывающего инастраивает свои поля соответствующим образом.Обратите внимание, что такое использование будет ясно для всех, кто читает код, что объект не был присоединен к коллекции.Можно также иметь вспомогательный метод GetMutableCopyOfThing.

Жаль, что Java по своей сути не делает лучше, декларативно указывает, кто "владеет" различными объектами.До появления фреймворков GC было досадно отслеживать, кому принадлежат все объекты, независимо от того, являются ли они изменяемыми или нет.Поскольку неизменяемые объекты часто не имеют естественного владельца, отслеживание владения неизменяемыми объектами было большой проблемой.Как правило, однако, любой объект Foo с состоянием, которое может быть видоизменено, должен иметь ровно одного владельца, который рассматривает изменяемые аспекты состояния Foo как части своего собственного состояния.Например, ArrayList является владельцем массива, который содержит элементы списка.Маловероятно писать безошибочные программы, использующие изменяемые объекты, если не отслеживать, кому они принадлежат (или, по крайней мере, их изменяемые аспекты).

1 голос
/ 16 марта 2012

вызовите getSomeRef () два раза и сравните там ссылку, если они различаются, тогда функция возвращает копию, иначе она возвращает тот же экземпляр.

if(MyObject.getSomeRef() == MyObject.getSomeRef()){
     // same instance
}else{
     // copied instance
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...