2015-07-17 3 views
3

Я написал код ниже, и, как вы видите, в конструкторе я вызываю некоторые методы для выполнения определенных операций. И теперь, о чем я спрашиваю, есть ли хорошая практика вызывать эти методы из конструктора ИЛИ объявлять эти методы как общедоступные и создавать экземпляр объекта из информации о классе, и позволить объекту вызвать эти методы? Какая хорошая практика для этого?Методы вызова от конструктора

Код:

class Info { 
public RoadInfo(String cityName, double lat, double lng) throws FileNotFoundException, SAXException, IOException, XPathExpressionException { 
    // TODO Auto-generated constructor stub 
    this.cityname = cityName; 
    this.lat = lat; 
    this.lng = lng; 

    this.path = "c:"+File.separatorChar+this.cityname+".xml"; 

    System.out.println(path); 

    this.initXPath(); 
    this.method1() 
    this.method2() 
    .. 

    this.expr = "//node[@lat='"+this.lat+"'"+"]/following-sibling::tag[1]/@v"; 
    this.xPath.compile(this.expr); 
    String s = (String) this.xPath.evaluate(this.expr, this.document, XPathConstants.STRING); 
    System.out.println(s); 
} 
+2

Лично я ожидал, что конструктор предоставит мне объект, с которым я могу начать работать сразу, поэтому я бы не захотел вызвать методы для инициализации объектов после конструктора каждый раз, когда я создаю экземпляр этого класса , Мне кажется, что метод вызова из конструктора мне кажется прекрасным. –

+1

(В комментарии, потому что я недостаточно информирован, чтобы изложить это точно): По-моему, * нет *. Ваш конструктор предназначен только для захвата состояния, переданного ему. Если вам нужно выполнить логику состояния, переданного вашему конструктору, вам нужен еще один уровень косвенности. –

+1

Сохраните конструктор просто. Конструктор должен иметь код, связанный с инициализацией объекта. Я предпочитаю перемещать неинициализирующую часть кода вне конструктора. –

ответ

6

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

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

class Info { 
public RoadInfo(String cityName, double lat, double lng) throws FileNotFoundException, SAXException, IOException, XPathExpressionException { 
    // TODO Auto-generated constructor stub 
    this.cityname = cityName; 
    this.lat = lat; 
    this.lng = lng; 

    this.path = "c:"+File.separatorChar+this.cityname+".xml"; 

    System.out.println(path); 

    this.initXPath(); 
    this.method1() 
    this.method2() 
    .. 

    this.expr = "//node[@lat='"+this.lat+"'"+"]/following-sibling::tag[1]/@v"; 
    this.xPath.compile(this.expr); 
    String s = (String) this.xPath.evaluate(this.expr, this.document, XPathConstants.STRING); 
    System.out.println(s); 
} 

Так, например, это конструктор является загрузка файла, разбор его в XPath .. если я когда-либо хотите создать RoadInfo объект, теперь я могу сделать это только при загрузке файлов и волнуясь об исключениях. Этот класс также становится тяжело сложным для модульного теста, потому что теперь вы не можете протестировать метод this.initXPath() изолированно, например, если у this.initXPath(), this.method1() или this.method2() есть какие-либо сбои, то каждый из ваших тестовых случаев не удастся. Плохо!

Я бы предпочел, чтобы выглядеть примерно так:

class RoadInfoFactory { 
    public RoadInfo getRoadInfo(String cityName, double lat, double lng) { 
    String path = this.buildPathForCityName(cityName); 
    String expression = this.buildExpressionForLatitute(lat); 
    XPath xpath = this.initializeXPath(); 
    XDocument document = ...; 

    String s = (String) xpath.evaluate(expression, document, XPathConstants.STRING); 
    // Or whatever you do with it.. 
    return new RoadInfo(s); 
    } 
} 

Не говоря уже о том, что у вас есть по крайней мере 5 обязанностей здесь. OS-нейтральный путь

  • выражение

    • Построить Сложение XPath для широты/долготы
    • Создание XPath doocument
    • Получить s - все, что
    • Создать новый RoadInfo экземпляру

    Каждый из этих обязанностей (кроме последних) следует разделить на свои классы (ИМО) и иметь RoadInfoFactory организуют их все вместе.

  • +0

    Я бы сказал, что разделение здания выражения XPath на отдельный класс, вероятно, немного от избытка, но отдельный метод определенно. – biziclop

    +0

    Я предпочел бы создавать выражение XPath программным способом, чем вручную, поэтому я предлагаю его разделить. –

    +0

    @ biziclop извините за недопонимание, но вы имеете в виду, что хорошо отделить создание выражения XPath в классах или нет? – rmaik

    5

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

    ... после создания экземпляра класса X, помните всегда вызывать initX(), или плохие вещи случаются!

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

    В этих случаях есть две вещи, которые вы можете сделать:

    1. перепишите конструктор так требует содержимое файла вместо имени. Позвольте вызывающему абоненту выполнить загрузку. Главное отличие состоит в том, что вы требуете, чтобы вызывающий абонент сделал что-то до, объект был создан, и вы можете выразить его с подписью своего конструктора: public RoadInfo(String cityName, Document cityDatabase, double lat, double lng) {...} Конечно, вы можете пойти еще дальше и сразу же потребовать значение s и позволить вызывающий выполняет поиск XPath. Обратите внимание, что все эти шаги перемещают класс к одной ответственности, что считается хорошей вещью.
    2. Но теперь вам нужно, чтобы вызывающий выполнял много шагов, прежде чем они смогут построить ваш RoadInfo. Здесь вы можете использовать заводы, которые также выполняют эту дополнительную инициализацию и возвращают полностью построенные объекты RoadInfo.

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

    Если вы вызываете такой метод, всегда есть шанс, что класс, переопределяющий метод, делает что-то, что нарушает ваши функции, например, выставляя this внешнему миру до завершения строительства. Вот пример:

    public abstract class Foo { 
        public Foo(String param) { 
         if (this.processParam(param) == null) 
          throw new IllegalArgumentException("Can't process param."); 
        } 
    
        protected abstract processParam(String param); 
    } 
    
    public class Bar extends Foo { 
        public Bar(String param) {super(param);} 
    
        protected processParam(String param) { 
         SomeOtherClass.registerListener(this); // Things go horribly wrong here 
         return null; 
        } 
    } 
    

    Если Вы вызываете new Bar("x"), конструктор Foo будет сгенерировано исключение, потому что он считает параметр недействительным. Но Bar.processParam() просочился ссылку this на номер SomeOtherClass, что позволило SomeOtherClass использовать экземпляр Bar, которого даже не должно быть.

    +0

    , пожалуйста, проясните следующее: «вызов общедоступных методов - это не очень хорошая идея, если только методы или сам класс не помечены как окончательные». Почему мне нужно пометить класс sfinal, когда я хочу вызвать открытый метод – user2121

    +0

    @ user2121 Я имел в виду публичные методы построения объекта, вы можете, конечно, свободно обращаться к общественным методам других объектов. Я попытаюсь как-то прояснить это. – biziclop

    +1

    @ user2121, потому что если вы вызываете неконкретный метод в конструкторе в классе, который может быть расширен, кто-то может переопределить этот метод в подклассе. Допустим, что метод overriden зависит от какого-либо состояния объекта, ну, если вы установите это состояние * после того, как * вы назовете ваш неконфигурированный метод, тогда неконкретный метод будет считать состояние неправильным. Вы также можете случайно протечь объект, пока он не полностью сконструирован для других частей приложения. Это большая банка червей, которую легко избежать, просто установив методы в «final». –

    4

    Как правило, классы, требующие большой инициализации, предоставляются клиенту с помощью заводского метода. Конструкторы часто являются слишком ограничительными — случайным примером является невозможность объединить вызов super или this с try-catch.

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

    2

    Нет хорошей практики, просто плохая практика, которую вы не должны делать.

    При вызове метода в конструкторе, некоторые опасны здесь:

    1) метод может быть перезаписан, и его подкласс реализации сломать ограничение вашего класса защищено конструктором реализация находится вне вашего контроля.

    class T { 
        int v; 
    
        T() { 
         v = getValue(); 
        } 
    
        int getValue() { 
         return 1; 
        } 
    } 
    
    class Sub extends T { 
        @Override 
        int getValue() { 
         return -1; 
        } 
    } 
    

    здесь v T предположим, что будет 1, если вы звоните new T(), но при создании new Sub(), «v» будет установлено значение -1, которое может нарушить ограничение T, и это происходит бессознательно.

    2) Проповедь полупостроенного объекта, в то время как его статус может быть незаконным.

    class T { 
        int a, b; 
    
        T(C c) { 
         // status of "this" is illegal now, but visible to c 
         c.calc(this); 
         a = 1; 
         b = 2; 
        } 
    } 
    
    class C { 
        int calc(T t) { 
         return t.a/t.b; 
        } 
    } 
    

    3) то, что больше я не знаю ...

    , если вы можете предотвратить все из них, вы можете делать то, что вы хотите.

    +0

    спасибо за ваш ответ. Прошу прояснить, как этот метод может быть перезаписан как u, указанный в пункте №1. Также я не понимаю, что означает u наполовину сконструированный и просочившийся объект ??! – rmaik

    +0

    @maik, если вы не делаете окончательный класс, я мог бы подклассифицировать ваш класс, а затем перезаписать метод, который вы вызываете. –

    3

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

    К сожалению, единственный хороший ответ на это Это зависит от объекта.

    Если объект предназначен для информации удержания, то ответ должен быть , вероятно, не так, старайтесь избегать его, поскольку объект должен действительно только do one thing.

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

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

    К сожалению - ответ на противоположный вопрос:

    ли это плохо практика вызвать некоторые методы из конструктора?

    Это также Это зависит от объекта по тем же причинам.

    +1

    Как трудно, как я в своем ответе, этот бит этого ответа нельзя недооценивать: «это зависит». В программировании нет жестких и быстрых правил, и иногда вам нужно писать плохой/неэлегантный/уродливый код, чтобы выполнить эту работу. –

    0
    • (Старайтесь, чтобы исключения не бросили. Таким образом, конструктор может хорошо инициализировать поле.)
    • Не называйте переопределение методов в конструкторе.

    О ловушках в Overridable вызова метода в конструкторе:

    Оценка в (ребенка) конструктора:

    • "ноль" все поля (0, null, 0.0, false)
    • Вызов суперконструктора (неявный, если нет в коде)
    • Вызовите все поле в itializations (декларации поля с = ...)
    • ли остальные конструктор кода

    Итак:

    class A { 
        A() { f(); } 
        protected void f() { } 
    } 
    
    class B implements A { 
        String a; 
        String b = null; 
        String c = "c"; 
    
        B() { 
         //[ a = null, b = null, c = null; ] 
         //[ super(); 
         // B.f(); 
         //] 
         //[ b = null; ] 
         //[ c = "c"; ] 
    
         // "nullA" - null - "c" 
         System.out.printf("end %s - %s - %s%n", a, b, c); 
        } 
    
        @Override 
        protected void f() { 
         System.out.printf("enter f : %s - %s - %s%n", a, b, c); 
         // null - null - null 
         a += "A"; 
         b += "B"; 
         c += "C"; 
         // "nullA" - "nullB" - "nullC" 
         System.out.printf("leave f : %s - %s - %s%n", a, b, c); 
        } 
    } 
    

    Такое поведение вполне мутит воду, и здесь делать задания, которые немедленно перезаписаны инициализация полей.

    Обычный вызов, который часто видит в конструкторах, - это сеттер, который может иметь некоторый код нормализации. Сделайте этот сеттер public final void setX(X x);.

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