Оборонительная копия из эффективной Java - программирование
Подтвердить что ты не робот

Оборонительная копия из эффективной Java

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

MyObject.getSomeRef().setSomething(somevalue);

что сокращается для:

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

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

4b9b3361

Ответ 1

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

  • не разговаривайте с незнакомыми людьми
  • Инкапсуляция

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

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

Другая часто используемая идиома - это возврат немодифицируемых представлений изменяемых структур данных:

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

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

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

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

Ответ 2

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

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

toArray():

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

subList():

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 5

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

if(MyObject.getSomeRef() == MyObject.getSomeRef()){
     // same instance
}else{
     // copied instance
}