2015-04-06 4 views
3

Я читаю о конкретных рекомендациях, которые необходимо соблюдать при создании Неизменяемого класса в Effective Java.Лучший дизайн для создания Неизменяемого класса

Я читал, что в методе Неизменяемого класса нельзя допускать переопределение, иначе переопределенный метод может изменить поведение метода. Ниже дизайн подходов доступен в Java, чтобы решить эту проблему: -

  1. Мы можем отметить класс окончательного, но согласно моему пониманию, он имеет один недостаток, что делает класс нерастяжимым.

  2. Во-вторых, чтобы сделать индивидуальные методы окончательными, но я не могу получить другой недостаток, кроме того, что мы должны индивидуально отмечать каждый метод как окончательный, чтобы предотвратить переопределение.

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

Мой вопрос: Даже если мы включаем частные или конструктор по умолчанию в классе, он не может быть продлен больше в одном пакете (в другой упаковке, в случае пакета-частного конструктора), он имеет те же проблемы, которые первый был. Как он считается лучшим подходом, чем предыдущие?

+4

Вы, конечно же, не можете продлить неизменяемый класс. Это одно из ключевых требований к неизменному классу. –

+1

Вы можете увидеть примеры для подхода, делающего конструктор частным в классах-оболочках примитивного типа: 'Integer',' Double' и т. Д. При неизменности объект часто рассматривается как значение. Таким образом, не важно, представлено ли одно и то же значение только одним объектом или (потенциально) несколькими объектами. Поэтому предоставляется статический заводский метод, который может возвращать один и тот же объект для одного и того же значения. – Seelenvirtuose

+0

Последний класс, например. 'java.lang.String' является **' final' ** и не может ни расширяться, ни модифицироваться после создания экземпляра. –

ответ

2

Предоставление статического заводского метода дает вам возможность реализовать шаблон мухи.

Они заявляют, что вы должны скрыть возможность создания нового объекта с помощью конструктора и скорее сделать вызов метода, который проверяет, существует ли объект с аналогичным состоянием в «пуле объектов» (карта заполнены объектами, ожидающими повторного использования). Не повторное использование неизменяемых объектов является пустой тратой памяти; поэтому рекомендуется String литералов, и new String() избегает (если это необходимо).

class ImmutableType { 
    private static final Map<Definition, ImmutableType> POOL = new HashMap<>(); 

    private final Definition definition; 

    private ImmutableType(Definition def) { 
     definition = def; 
    } 

    public static ImmutableType get(Definition def) { 
     if(POOL.contains(def)) 
       return POOL.get(def); 
     else { 
       ImmutableType obj = new ImmutableType(def); 
       POOL.put(def, obj); 

       return obj; 
     } 
    } 
} 

Definition сохраняет состояние окружающей ImmutableType. Если тип с таким же определением уже существует в пуле, повторите его использование. В противном случае создайте его, добавьте в пул и верните его в качестве значения.

Что касается утверждения о маркировке класса final, неизменяемые типы не должны расширяться в первую очередь (во избежание модификации поведения). Маркировка каждого метода final просто сумасшедшая для неизменяемых классов.

2

Неизменяемый объект не должен быть расширяемым. Зачем?

Потому что расширение может позволить либо прямой доступ к полям (если они protected, что позволит использовать методы записи, которые их изменяют), либо добавление состояния, которое может быть изменено.

Представьте себе, что мы написали класс FlexiblyRoundableDouble, который расширяет Double, у которого есть дополнительное поле roundingMode, что позволяет нам выбрать «режим округления». Вы можете написать сеттер для этого поля, и теперь ваш объект изменен.

Вы можете утверждать, что если все методы установлены как окончательные, вы не можете изменить исходное поведение объекта. Единственными методами, которые могли бы получить доступ к вашему полю roundingMode, являются новые методы, которые не доступны для полиморфизма, если вы назначили свой объект переменной Double.Но когда в контракте класса говорится, что он неизменен, вы принимаете решения на основе этого. Например, если вы пишете метод или конструктор копирования clone() для класса с полями Double, вы знаете, что вам не нужно глубоко копировать поля Double, так как они не меняют свое состояние и поэтому могут быть безопасно разделены между двумя клонов.

Также вы можете написать методы, возвращающие внутренний объект, не опасаясь, что вызывающий объект изменит этот объект. Если объект изменен, вам нужно будет сделать «защитную копию». Но если это неизменное, безопасно возвращать ссылку на фактический внутренний объект.

Однако, что произойдет, если кто-то назначил FlexiblyRoundableDouble одному из ваших полей Double? Этот объект будет изменчивым. clone() предполагает, что это не так, он будет разделен между двумя объектами, возможно, даже возвращен методом. Затем вызывающий может вернуть его обратно в FlexiblyRoundableDouble, изменить поле ... и он повлияет на другие объекты, которые используют тот же самый экземпляр.

Следовательно, неизменяемые объекты должны быть окончательными.


Все это не имеет ничего общего с проблемой конструктора. Объекты могут быть безопасно неизменными с помощью публичных конструкторов (как это показано String, Double, Integer и другими стандартными Java-непреложными). Статический заводский метод - это просто способ с использованием того факта, что объект неизменен, а несколько других объектов могут безопасно содержать ссылки на него, чтобы создать меньше объектов с одинаковым значением.

Смежные вопросы