Что-то не так с немедленными действиями в конструкторах?

У меня есть классы вроде этого:

class SomeObject
{
    public function __construct($param1, $param2)
    {
       $this->process($param1, $param2);
    }
    ...
}

Итак, я могу мгновенно "называть" его как некую глобальную функцию, как

new SomeObject($arg1, $arg2);

который имеет преимущества

  • оставаться кратким,
  • легко понять,

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

Должен ли я продолжать чувствовать себя плохо из-за плохой практики, или там действительно не о чем беспокоиться?

Разъяснение:

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

Пример:

Чтобы дать вам представление о том, как я обычно использую этот подход:

new Email('[email protected]', 'Subject line', 'Body Text');

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

Ответы

Ответ 1

если код в конструкторе является частью создания и инициализации объекта для использования, тогда я бы поставил его там, но это лично, некоторые люди могут не согласиться

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

Сохраните конструктор для построения.

Ответ 2

Это плохая практика.

Использование конструктора в качестве глобальной функции вовсе не то, для чего оно предназначено, и его легко смутить. Это не легко понять.

Если вы хотите глобальную функцию, объявите ее:

function myGlobalFunction(){ return $something; }

Это действительно не так сложно...

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

Итак, программа в некотором смысле имеет смысл. Это действительно не так сложно. Несколько лишних нажатий клавиш могут сэкономить вам массу путаницы.

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

 class myFactory{
       public static function makeObject(){
           $obj = new Object();
           $obj->init1();
           return $obj;
       }
 }

 $obj = myFactory::makeObject();

Ответ 3

Оккам-бритва говорит, что entia non sunt multipicanda praeter требует, буквально: "сущности не должны умножаться за пределами необходимости". Нет ничего плохого в выполнении работы в конструкторе [*], но не требуют вызова вызывающих объектов для создания объекта, который они не используют, просто для получения побочных эффектов его конструктора.

[*] Предполагая, что вам нравится механизм, используемый вашим языком программирования для указания отказа конструктора. Возврат кода успеха/отказа из конструктора может быть не таким, поэтому, если вы хотите избежать исключения в случае сбоя, то вы можете свести к установке флаги "isable" в объекте, что не очень удобно для удобства использования.

Ответ 4

Я думаю, что это плохая практика по двум причинам.

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

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

Что касается второй точки... если конструктор содержит операцию, которая непредсказуемо генерирует исключения (в отличие от исключений, вызванных из-за проверки параметров), то код обработки исключений становится неудобным. Рассмотрим следующий пример в С#. Обратите внимание, как хороший дизайн имеет более элегантное чувство. Nevermind, тот факт, что четко названный метод по крайней мере на порядок читабельнее.

public class BadDesign : IDisposable
{
  public BadDesign()
  {
    PerformIOBoundOperation();
  }

  private void PerformIOBoundOperation() { }
}

public class GoodDesign : IDisposable
{
  public GoodDesign()
  {

  }

  public void PerformIOBoundOperation() { }
}

public static void Main()
{
  BadDesign bad = null;
  try
  {
    bad = new BadDesign();
  }
  catch
  {
    // 'bad' is left as null reference. There is nothing more we can do.
  }
  finally
  {
    if (bad != null)
    {
      bad.Dispose();
    }
  }

  GoodDesign good = new GoodDesign();
  try
  {
    good.PerformIOBoundOperation();
  }
  catch
  {
    // Do something to 'good' to recover from the error.
  }
  finally
  {
    good.Dispose();
  }
}

Ответ 5

Что касается этого шаблона:

  • Нарушение принципа единой ответственности
  • Реализация функционального декомпозиции Антипаттерн
  • Несоблюдение общих ожиданий потребителей

Принцип единой ответственности

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

Функциональный декомпозиционный антипаттерн

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

Общие потребительские ожидания

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

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

Допустимое использование?

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

/* This is a common usage of this class */
File f;
f.Open(path);

/* This constructor constructs the object and puts it in the open state in one step 
 * for convenience */
File f(path);

Но даже это сомнительно. Поддерживает ли класс файла путь к файлу внутри организации или просто используется для открытия дескриптора файла? Если он сохраняет путь к файлу, теперь Open() и новый конструктор имеют более чем одну ответственность (SetPath (p) и Open()). В этом случае, возможно, конструктор удобностей File (path) не должен открывать файл, а скорее должен просто установить путь в объекте.

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

Ответ 6

На мой взгляд, это зависит от цели функции.

Например, что-то, что, по моему мнению, вполне уместно вызывать в конце конструктора, может быть чем-то вроде функции refreshCache(). Это реальная публичная функция, которую другие объекты могут пожелать назвать, но в то же время вы знаете, что та же логика, которую вы хотите применить, при настройке своего объекта.

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

Если у вас есть объект, который выполняет какую-то обработку на входе, я бы определенно избегал

s = new MyObject(a, b)
return s.getResult()

в пользу

s = new MyObject() // perhaps this is done elsewhere, even once at startup
return s.process(a, b)

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

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

Ответ 7

Это кратким, но оно не является отдаленно идиоматичным, поэтому другим не понятно.

В будущем вам может быть нелегко понять это.

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

Ответ 8

Ваш класс технически хорош, и он может просто перейти к личной философии.

но другим читающим, которые могут получить некоторые сумасшедшие идеи о конструкторах, читайте это:

http://www.ibm.com/developerworks/java/library/j-jtp0618.html

Если вы собираетесь начать метать ссылку "this", вы можете столкнуться с большими проблемами, особенно в многопоточных средах. Просто FYI.