2009-02-24 3 views
64

Мне было интересно, какой лучший (то есть самый чистый/безопасный/самый эффективный) способ обработки нескольких конструкторов в Java? Особенно, когда в одном или нескольких конструкторах указаны не все поля:Лучший способ обработки нескольких конструкторов в Java

public class Book 
{ 

    private String title; 
    private String isbn; 

    public Book() 
    { 
     //nothing specified! 
    } 

    public Book(String title) 
    { 
     //only title! 
    } 

    ...  

} 

Что делать, если не указаны поля? Я до сих пор использовал значения по умолчанию в классе, так что поле никогда не было нулевым, но является ли это «хорошим» способом делать вещи?

+0

Это зависит от того, нужны ли все поля для хранения значения? – CodeMonkey

+0

Эта статья может быть полезна: http://www.yegor256.com/2015/05/28/one-primary-constructor.html – yegor256

+3

Мне не нравится, когда люди задают вопросы, а затем не принимают ответы. – muni764

ответ

127

Несколько упрощенный ответ:

public class Book 
{ 
    private final String title; 

    public Book(String title) 
    { 
     this.title = title; 
    } 

    public Book() 
    { 
     this("Default Title"); 
    } 

    ... 
} 
+6

+1 Я считаю хорошим правилом, что все ваши конструкторы должны пройти через общую «точку затухания». – cletus

+1

Кроме того, всегда желательно начинать каждый конструктор с помощью этого() или super() = 8-) – Yuval

+0

Цепь конструктора всегда хороша. – Powerlord

32

Рассмотрим использование шаблона Builder. Он позволяет вам устанавливать значения по умолчанию для ваших параметров и инициализировать четким и сжатым способом. Например:


    Book b = new Book.Builder("Catcher in the Rye").Isbn("12345") 
     .Weight("5 pounds").build(); 

Edit: Это также устраняет необходимость в нескольких конструкторах с различными подписями и способ более удобным для чтения.

+0

Это лучшая идея, если у вас есть несколько конструкторов, вы можете легко расширить их для новых типов и меньше ошибок. – dinsim

+0

@ Dinesh - Я согласен, я использую Builders по всему моему коду. Мне нравится образец! – kgrad

+0

В моем opioni это как Свободный интерфейс (http://www.codemonkeyism.com/archives/2007/10/10/fluent-interface-and-reflection-for-object-building-in-java/) – alepuzio

18

Необходимо указать, какие инварианты класса, то есть свойства, которые всегда будут истинны для экземпляра класса (например, название книги никогда не будет равно нулю, или размер собаки всегда будет > 0).

Эти инварианты должны быть установлены во время строительства и сохраняться по времени жизни объекта, а это значит, что методы не должны нарушать инварианты. Конструкторы могут установить эти инварианты либо путем обязательных аргументов, или путем установки значений по умолчанию:

class Book { 
    private String title; // not nullable 
    private String isbn; // nullable 

    // Here we provide a default value, but we could also skip the 
    // parameterless constructor entirely, to force users of the class to 
    // provide a title 
    public Book() 
    { 
     this("Untitled"); 
    } 

    public Book(String title) throws IllegalArgumentException 
    { 
     if (title == null) 
      throw new IllegalArgumentException("Book title can't be null"); 
     this.title = title; 
     // leave isbn without value 
    } 
    // Constructor with title and isbn 
} 

Однако выбор этих инвариантов в значительной степени зависит от класса вы пишете, как вы будете его использовать, и т.д. ., поэтому нет окончательного ответа на ваш вопрос.

+0

Вы хотите проверить, что null не передается этому второму конструктору. –

+0

Действительно, я добавлю это. –

3

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

public Book(String title) 
{ 
    if (title==null) 
     throw new IllegalArgumentException("title can't be null"); 
    this.title = title; 
} 
+1

Вы должны делать это для всех своих общедоступных методов. Всегда проверяйте аргументы, это спасет вас от головной боли по дороге. – cdmckay

6

Некоторые общие конструктору советы:

  • Попробуйте сосредоточить все инициализации в один конструктор и называть его от других конструкторов
    • Это хорошо работает, если существует несколько конструкторов для имитации параметров по умолчанию
  • Никогда не вызывать неконечное метод из конструктора
    • Частные методы являются окончательными по определению
    • Полиморфизм может убить вас здесь; Вы можете закончить вызов реализации подкласса до подкласса инициализирован
    • Если вам нужно «вспомогательной» метода, не забудьте сделать их частными или окончательным
  • Четко в ваших звонках супер()
    • Вы были бы удивлены, сколько программистов Java не понимают, что вызов super() вызывается, даже если вы явно не записываете его (если у вас нет вызова к этому (...))
  • Знайте порядок правил инициализации для конструкторов. Это в основном:

    1. это (...) если он присутствует (только перехода к другому конструктору)
    2. вызова супер (...) [если не явно, вызвать супер() неявно]
    3. (построить суперкласс с помощью этих правил рекурсивны)
    4. инициализировать поля с помощью своих деклараций
    5. запустить тело текущего конструктора
    6. возвращение к предыдущим конструкторам (если вы столкнулись с этими (...) звонками)

Общий поток заканчивается время:

  • движение вплоть суперкласса иерархию объектов
  • пока не сделали
    • INIT поля
    • запустить конструктор тела
    • свернуть до su bclass

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

package com.javadude.sample; 

/** THIS IS REALLY EVIL CODE! BEWARE!!! */ 
class A { 
    private int x = 10; 
    public A() { 
     init(); 
    } 
    protected void init() { 
     x = 20; 
    } 
    public int getX() { 
     return x; 
    } 
} 

class B extends A { 
    private int y = 42; 
    protected void init() { 
     y = getX(); 
    } 
    public int getY() { 
     return y; 
    } 
} 

public class Test { 
    public static void main(String[] args) { 
     B b = new B(); 
     System.out.println("x=" + b.getX()); 
     System.out.println("y=" + b.getY()); 
    } 
} 

Я добавлю комментарии, описывающие, почему вышеуказанные работы, как это делает. .. Некоторые из них могут быть очевидны; некоторые нет ...

+0

1. Метод init() никогда не вызывается при создании B (B его переопределяет) –

+0

2. Метод init() B вызывается из конструктора A –

+0

В init() B вызывается * до того, как инициализаторы B вызывают !. y присваивается 42 * после * назначается в init() –

0

Я хотел бы сделать следующее:

 
public class Book 
{ 
    private final String title; 
    private final String isbn; 

    public Book(final String t, final String i) 
    { 
     if(t == null) 
     { 
      throw new IllegalArgumentException("t cannot be null"); 
     } 

     if(i == null) 
     { 
      throw new IllegalArgumentException("i cannot be null"); 
     } 

     title = t; 
     isbn = i; 
    } 
} 

Я делаю предположение, что здесь:

1) название не изменится (отсюда название является окончательным) 2) isbn никогда не изменится (следовательно, isbn является окончательным) 3) что недопустимо иметь книгу без названия и isbn.

Рассмотрим класса Student:

 
public class Student 
{ 
    private final StudentID id; 
    private String firstName; 
    private String lastName; 

    public Student(final StudentID i, 
        final String first, 
        final String last) 
    { 
     if(i == null) 
     { 
      throw new IllegalArgumentException("i cannot be null"); 
     } 

     if(first == null) 
     { 
      throw new IllegalArgumentException("first cannot be null"); 
     } 

     if(last == null) 
     { 
      throw new IllegalArgumentException("last cannot be null"); 
     } 

     id  = i; 
     firstName = first; 
     lastName = last; 
    } 
} 

Там студент должен быть создан с идентификатором, первым именем и фамилией. Идентификатор студента никогда не может измениться, но фамилия и имя человека могут измениться (выйти замуж, изменить имя из-за потери ставки и т. Д.).

При принятии решения о том, какие конструкторы вам действительно нужны, нужно подумать о том, что имеет смысл иметь. Все часто люди добавляют методы set/get, потому что их учат - но очень часто это плохая идея.

Неизменяемые классы намного лучше иметь (то есть классы с конечными переменными) над изменчивыми. В этой книге: http://books.google.com/books?id=ZZOiqZQIbRMC&pg=PA97&sig=JgnunNhNb8MYDcx60Kq4IyHUC58#PPP1,M1 (Эффективная Java) есть хорошая дискуссия о неизменности. Посмотрите на пункты 12 и 13.

+0

Почему вы делаете параметры окончательными? –

+0

конечные параметры делают так, чтобы вы случайно не делали t = название, например. – TofuBeer

0

Несколько человек рекомендовали добавить нулевую проверку. Иногда это правильно, но не всегда. Посмотрите эту замечательную статью, в которой показано, почему вы ее пропустите.

http://misko.hevery.com/2009/02/09/to-assert-or-not-to-assert/

9

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

В вопросе использования конструктора: Я всегда стараюсь иметь один базовый конструктор, который все остальные откладывают, цепляясь с «опущенными» параметрами следующему логическому конструктору и заканчивая базовым конструктором. Итак:

class SomeClass 
{ 
SomeClass() { 
    this("DefaultA"); 
    } 

SomeClass(String a) { 
    this(a,"DefaultB"); 
    } 

SomeClass(String a, String b) { 
    myA=a; 
    myB=b; 
    } 
... 
} 

Если это невозможно, я пытаюсь получить частный метод init(), который все конструкторы откладывают.

И сохраните количество конструкторов и параметров малым - максимум 5 из них в качестве ориентира.

+0

yeap, более пяти параметров в вашем конструкторе, и вы должны оценить реализацию проекта objetc ... см. «Эффективная Java» для более подробной информации ... – opensas

1

Возможно, стоит использовать статический заводский метод вместо конструктора.

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

Это довольно простое решение, особенно в сравнении с шаблоном Builder (как показано в Джошуа Блоха Эффективное Java 2-е издание - берегитесь, Банда Design Patterns четверки определяют совершенно другой дизайн шаблона с таким же именем, так что может быть немного запутанным), что подразумевает создание вложенного класса, объекта-строителя и т. д.

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

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

Вы можете прочитать гораздо больше о том, что в (уже упоминалось) Джошуа Блоха Эффективное Java 2-е издание - это важный инструмент в инструментарии все девелоперские и не удивительно, это не предмет 1-й главе книги. ;-)

После вашего примера:

public class Book { 

    private static final String DEFAULT_TITLE = "The Importance of Being Ernest"; 

    private final String title; 
    private final String isbn; 

    private Book(String title, String isbn) { 
     this.title = title; 
     this.isbn = isbn; 
    } 

    public static Book createBook(String title, String isbn) { 
     return new Book(title, isbn); 
    } 

    public static Book createBookWithDefaultTitle(String isbn) { 
     return new Book(DEFAULT_TITLE, isbn); 
    } 

    ... 

}

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

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