Подтвердить что ты не робот

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

Я ответил на вопрос (ссылка), что я использовал создание нового объекта в конструкторе другого класса, вот пример:

class Person {
  public $mother_language;

  function __construct(){ // just to initialize $mother_language
    $this->mother_language = new Language('English');
}

И я получил комментарий от пользователя "Matija" (его профиль), и он написал: вы никогда не должны создавать экземпляр нового объекта внутри объекта consturctor, зависимости должны быть нажаты извне, поэтому любой, кто использует этот класс, знает, что этот класс зависит от!

В общем, я могу согласиться с этим, и я понимаю его точку зрения.

Однако я часто делал это так, например:

  • поскольку частные свойства других классов дают мне функциональность, которую я могу решить, не дублируя код, например, я могу создать список (реализация класса ArrayAccess) объектов), и этот класс будет использоваться в другом классе, который имеет такой список объектов,
  • В некоторых классах используются, например, объекты DateTime,
  • Если я include (или autoload) зависимый класс, у него не должно быть проблем с ошибками,
  • потому что зависимые объекты могут быть очень большим числом, передача всех из них в конструктор класса может быть очень длинной и непонятной, например

    $color = new TColor('red'); // do we really need these lines?
    $vin_number = new TVinNumber('xxx');
    $production_date = new TDate(...);
    ...
    $my_car = new TCar($color, $vin_number, $production_date, ...............);
    
  • поскольку я был "рожден" в Паскале, затем в Дельфи, у меня есть некоторые привычки оттуда. И в Delphi (и FreePascal как его конкурент) эта практика очень часто. Например, существует класс TStrings, который обрабатывает массив строк, и для их хранения он не использует array, а другой класс TList, который предоставляет некоторые полезные методы, а TStrings - это только некоторый тип интерфейс. Объект TList является объявленным пользователем и не имеет доступа извне, но получатели и сеттеры TStrings.

  • (не важно, но по какой-то причине) обычно я тот, кто использует мои классы.

Пожалуйста, объясните мне, действительно ли важно избегать создания объектов в конструкторах?

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

4b9b3361

Ответ 1

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

Основное преимущество передачи в зависимых объектах происходит, если вы хотите протестировать свой код. В вашем примере я не могу использовать поддельный класс Language. Я должен использовать фактический класс для проверки Person. Теперь я не могу контролировать, как язык ведет себя, чтобы убедиться, что Person работает правильно.

Это сообщение помогает объяснить, почему это плохо и потенциальные проблемы, которые он вызывает. http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/

UPDATE

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

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

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

Что касается создания объектов и их передачи, то это целая цель шаблона Factory http://www.oodesign.com/factory-pattern.html. Он создавал бы зависимости и вводил бы их для вас. Таким образом, вы сможете запросить его для объекта, который будет инициализироваться так, как вы хотите. Объекту Person не нужно решать, что ему нужно.

Ответ 2

Оригинальный комментарий кажется мне глупым.

Нет причин бояться классов, которые управляют ресурсами. Действительно, инкапсуляция, которую предоставляют классы, совершенна apt для этой задачи.

Если вы ограничиваетесь только тем, что используете ресурсы извне, то какие из них будут управлять этими ресурсами? Процедурный код спагетти? Хлоп!

Старайтесь не верить всему, что вы читаете в Интернете.

Ответ 3

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

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

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

<?php

class Person {
  public $mother_language;

  // We ask for a known interface we don't mind the implementation
  function __construct(Language_Interface $mother_language) 
  {
    $this->mother_language = $mother_language
  }

  static function factory()
  {
    return new Person(new Language('English'));
  }
}

$person = Person::factory();
$person = new Person(new Language('Spanish'); // Here you are free to inject your mocked object implementing the Language_Interface

Ответ 4

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

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

Ответ 5

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

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

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

Для меня это утверждение похоже на то, что он говорит: "Не используйте camelCase, используйте under_score". Я думаю, вы должны делать это, как бы то ни было. В конце концов, у вас есть собственный способ тестирования, не так ли?

Ответ 6

Если вы хотите установить значение по умолчанию, но улучшите гибкость/тестируемость, почему бы не сделать это так?

class Person
{
    protected $mother_language;

    function __construct(Language $language = null)
    {
         $this->mother_language = $language ?: new Language('English');
    }
}

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