2010-09-13 15 views
8

Builder реализует Cloneable и overrides clone(), а вместо копирования каждого поля строителя неизменяемый класс хранит частный клон строителя. Это упрощает возврат нового конструктора и создание слегка измененных копий неизменяемого экземпляра.Является ли это допустимой реализацией Java неизменяемого класса и шаблона Builder?

Таким образом, я могу пойти

MyImmutable i1 = new MyImmutable.Builder().foo(1).bar(2).build(); 
MyImmutable i2 = i1.builder().foo(3).build(); 

Cloneable интерфейс называется несколько нарушена, но все это нарушает хорошее кодирование практики Java, есть какие-то проблемы с этой конструкции?

final class MyImmutable { 
    public int foo() { return builder.foo; } 
    public int bar() { return builder.bar; } 
    public Builder builder() { return builder.clone(); } 
    public static final class Builder implements Cloneable { 
    public Builder foo(int val) { foo = val; return this; } 
    public Builder bar(int val) { bar = val; return this; } 
    public MyImmutable build() { return new MyImmutable(this.clone()); } 
    private int foo = 0; 
    private int bar = 0; 
    @Override public Builder clone() { try { return (Builder)super.clone(); } catch(CloneNotSupportedException e) { throw new AssertionError(); } } 
    } 
    private MyImmutable(Builder builder) { this.builder = builder; } 
    private final Builder builder; 
} 

ответ

5

Как правило, класс, построенный из Builder, не имеет каких-либо специальных знаний о строителе. То есть Неизменное бы конструктор на поставку значения для обува и бара:

public final class MyImmutable { 
    public final int foo; 
    public final int bar; 
    public MyImmutable(int foo, int bar) { 
    this.foo = foo; 
    this.bar = bar; 
    } 
} 

Застройщик будет отдельный класс:

public class MyImmutableBuilder { 
    private int foo; 
    private int bar; 
    public MyImmutableBuilder foo(int val) { foo = val; return this; } 
    public MyImmutableBuilder bar(int val) { bar = val; return this; } 
    public MyImmutable build() { return new MyImmutable(foo, bar); } 
} 

Если вы хотите, вы можете добавить статический метод MyImmutable строитель начать с существующей MyImmutable например:

public static MyImmutableBuilder basedOn(MyImmutable instance) { 
    return new MyImmutableBuilder().foo(instance.foo).bar(instance.bar); 
} 
+0

Я пытался вырезать несколько углов и не копировать поля явно. Метод «basedOn» действительно ясен, но мне потребуется копировать поля (снова). Возможно, я слишком ленив. – Aksel

+0

Отличное предложение метода basedOn :) – troig

2

Я не видел этот подход раньше, но похоже, что он будет работать нормально.

В основном это делает шаблон строителя относительно простым в реализации, за счет немного более высоких затрат времени выполнения (дополнительные объекты + операции клонирования + уровень косвенности в функциях доступа, которые могут или не могут быть скомпилированы).

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

+0

Создание неизменяемого строителя - это интересное предложение, но, возможно, я должен просто следовать примеру Джошуа Блохса на письмо и перестать пытаться быть умным. Хорошо, что это случилось неправильно, ясно, что Джошуа Блох очень умный. То, что я имел в виду, следовало за пунктом 2 его превосходной книги. – Aksel

+0

При подходе, как указано, для создания нового неизменяемого объекта с любым количеством изменений с другого неизменяемого объекта потребуются две защитные копии.Без изменчивого построителя для внесения изменений в N потребуется N защитных копий. Какой подход лучше будет зависеть от количества изменений. – supercat

2

Ваша реализация аналогична реализации, описанной в Джоша Блоха Эффективное Java 2nd Edition.

Одним из пунктов разговора будет ваш метод build(). Если один построитель создает неизменяемый экземпляр, было бы справедливым разрешить его повторное использование, учитывая, что это была сделана работа? Здесь предостережение заключается в том, что, хотя вы создаете неизменяемый объект, изменчивость вашего строителя может привести к нескольким довольно «неожиданным» ошибкам.

Чтобы исправить это, можно предположить, что метод сборки должен создать экземпляр, а затем сделать строитель неспособным снова построить объект, требуя нового построителя. Хотя котельная плита может показаться утомительной, выгоды, которые будут получены позже, перевешивают требуемые усилия в настоящее время. Затем класс реализации может получить экземпляр Builder в качестве параметра конструктора, но разработчик должен затем позволить экземпляру вытащить все необходимое состояние, освободив экземпляр компоновщика и сохранив окончательные ссылки на данные.

+0

«Эффективная Java» отмечает это как преимущество: «Для создания нескольких объектов можно использовать один строитель. Параметры конструктора могут быть изменены между созданием объектов для изменения объектов». – slim

+0

Да, это так. То, что вы не хотите, это предыдущее использование конструктора, чтобы помешать новому объекту, который он в настоящее время используется для создания. – gpampara