Рассмотрим эти два примера:
Передача объекта в конструктор
class ExampleA
{
private $config;
public function __construct($config)
{
$this->config = $config;
}
}
$config = new Config;
$exampleA = new ExampleA($config);
Создание класса
class ExampleB
{
private $config;
public function __construct()
{
$this->config = new Config;
}
}
$exampleA = new ExampleA();
Как правильно обрабатывать добавление объекта в качестве свойства? Когда я должен использовать один поверх другого? Влияет ли юнит-тестирование на то, что я должен использовать?
Ответы:
Я думаю, что первый даст вам возможность создать
config
объект в другом месте и передать егоExampleA
. Если вам нужно внедрение зависимостей, это может быть полезно, поскольку вы можете гарантировать, что все экземпляры совместно используют один и тот же объект.С другой стороны, может быть, вам
ExampleA
нужен новый и чистыйconfig
объект, поэтому могут быть случаи, когда второй пример более уместен, например, случаи, когда каждый экземпляр может иметь другую конфигурацию.источник
Не забывайте про тестируемость!
Обычно, если поведение
Example
класса зависит от конфигурации, которую вы хотите проверить, не сохраняя / не изменяя внутреннее состояние экземпляра класса (т. Е. Вы хотите иметь простые тесты для правильного пути и для неправильной конфигурации без изменения свойства / членExample
класса).Поэтому я бы пошел с первым вариантом
ExampleA
источник
Если объект отвечает за управление временем жизни зависимости, тогда можно создать объект в конструкторе (и утилизировать его в деструкторе). *
Если объект не отвечает за управление временем жизни зависимости, он должен быть передан в конструктор и управляться извне (например, контейнером IoC).
В этом случае я не думаю, что ваш ClassA должен нести ответственность за создание $ config, кроме случаев, когда он также отвечает за его удаление или если конфигурация уникальна для каждого экземпляра ClassA.
* Чтобы помочь в тестируемости, конструктор может использовать ссылку на класс / метод фабрики, чтобы построить зависимость в своем конструкторе, увеличивая связность и тестируемость.
источник
У меня недавно была такая же дискуссия с нашей командой архитекторов, и есть некоторые тонкие причины сделать это так или иначе. Это в основном сводится к внедрению зависимости (как уже отмечали другие) и к тому, действительно ли у вас есть контроль над созданием объекта, который вы добавили в конструктор.
В вашем примере, что если ваш класс Config:
а) имеет нетривиальное распределение, например, из пула или фабричного метода.
б) может быть не выделен. Передача его в конструктор аккуратно избегает этой проблемы.
c) фактически является подклассом Config.
Передача объекта в конструктор дает большую гибкость.
источник
Ниже приведен неправильный ответ, но я оставлю его для изучения другими (см. Ниже)
В
ExampleA
, вы можете использовать один и тот жеConfig
экземпляр для нескольких классов. Однако, еслиConfig
во всем приложении должен быть только один экземпляр, рассмотрите возможность применения шаблона Singleton,Config
чтобы избежать нескольких экземпляровConfig
. И еслиConfig
это Singleton, вы можете сделать следующее:В
ExampleB
, с другой стороны, вы всегда получите отдельный экземплярConfig
для каждого экземпляраExampleB
.Какую версию вы должны применить, зависит от того, как приложение будет обрабатывать случаи
Config
:ExampleX
должен иметь отдельный экземплярConfig
, переходите кExampleB
;ExampleX
разделяет один (и только один) экземплярConfig
, используйтеExampleA with Config Singleton
;ExampleX
могут использовать разные экземплярыConfig
, придерживайтесьExampleA
.Почему преобразование
Config
в синглтон неправильно:Я должен признать, что я узнал о паттерне Singleton только вчера (читая книгу « First First», посвященную шаблонам проектирования). Наивно я пошел и применил его для этого примера, но, как многие отмечали, один путь - другой (некоторые были более загадочными и говорили только: «Вы делаете это неправильно!»), Это не очень хорошая идея. Итак, чтобы не допустить повторения той же ошибки, которую я только что совершил, ниже приведено краткое изложение причин, по которым паттерн Синглтон может быть вредным (на основе комментариев и того, что я обнаружил, погугляя)
Если
ExampleA
получить собственную ссылку наConfig
экземпляр, классы будут тесно связаны. Не будет никакого способа иметь экземплярExampleA
для использования другой версииConfig
(скажем, некоторого подкласса). Это ужасно, если вы хотите протестироватьExampleA
с использованием экземпляра макета,Config
поскольку нет способа предоставить егоExampleA
.Предположение о том, что будет один, и только один,
Config
может быть, имеет место сейчас , но вы не всегда можете быть уверены, что то же самое сохранится в будущем . Если в какой-то более поздний момент окажется, что несколько экземпляровConfig
будут желательны, нет способа достичь этого без переписывания кода.Даже если один-единственный-единственный экземпляр
Config
может быть верным на всю вечность, может случиться так, что вы захотите использовать некоторый подклассConfig
(хотя при этом у вас будет только один экземпляр). Но, поскольку код напрямую получает экземпляр с помощьюgetInstance()
ofConfig
, который являетсяstatic
методом, нет способа получить подкласс. Опять же, код должен быть переписан.Тот факт, что
ExampleA
использует,Config
будет скрыт, по крайней мере, при просмотре только APIExampleA
. Это может или не может быть плохой вещью, но лично я чувствую, что это чувствует себя недостатком; например, при ведении не существует простого способа выяснить, на какие классы будут влиять изменения,Config
не рассматривая реализацию любого другого класса.Даже если сам факт
ExampleA
использования SingletonConfig
не является проблемой сам по себе, он все равно может стать проблемой с точки зрения тестирования. Одиночные объекты будут нести состояние, которое будет сохраняться до завершения приложения. Это может быть проблемой при запуске модульных тестов, поскольку вы хотите, чтобы один тест был изолирован от другого (то есть выполнение одного теста не должно влиять на результат другого). Чтобы исправить это, объект Singleton должен быть уничтожен между каждым запуском теста (возможно, потребуется перезапустить все приложение), что может занять много времени (не говоря уже об утомительном и раздражающем).Сказав это, я рад, что я сделал эту ошибку здесь, а не в реализации реального приложения. На самом деле, я подумывал переписать свой последний код, чтобы использовать шаблон Singleton для некоторых классов. Хотя я мог бы легко отменить изменения (все хранится в SVN, конечно), я все равно потратил бы впустую время, делая это.
источник
ExampleA
иConfig
- что не очень хорошая вещь.Самое простое, что нужно сделать, это соединиться
ExampleA
сConfig
. Вы должны делать самое простое, если нет веских причин делать что-то более сложное.Одной из причин развязки
ExampleA
иConfig
будет улучшение тестируемостиExampleA
. Прямое соединение будет деградировать тестируемостиExampleA
еслиConfig
есть методы , которые являются медленными, сложными, или быстро развивается. Для тестирования метод медленный, если он выполняется более нескольких микросекунд. Если бы все методыConfig
были простыми и быстрыми, то я бы выбрал простой подход и прямо связалсяExampleA
с нимConfig
.источник
Ваш первый пример - пример шаблона внедрения зависимостей. Классу с внешней зависимостью передается зависимость конструктору, сеттеру и т. Д.
Этот подход приводит к слабосвязанному коду. Большинство людей считают, что слабая связь - это хорошая вещь, потому что вы можете легко заменить конфигурацию в тех случаях, когда один конкретный экземпляр объекта должен быть настроен иначе, чем другие, вы можете передать фиктивный объект конфигурации для тестирования и т. Д. на.
Второй подход ближе к шаблону создателя GRASP. В этом случае объект создает свои собственные зависимости. В результате получается тесно связанный код, это может ограничить гибкость класса и усложнить его тестирование. Если вам нужен один экземпляр класса, чтобы иметь зависимость, отличную от других, единственный вариант - это создать его подкласс.
Однако это может быть подходящим шаблоном в тех случаях, когда продолжительность жизни зависимого объекта определяется временем жизни зависимого объекта и когда зависимый объект не используется нигде, кроме объекта, который от него зависит. Обычно я бы посоветовал DI быть позицией по умолчанию, но вам не нужно полностью исключать другой подход, если вы знаете о его последствиях.
источник
Если ваш класс не предоставляет
$config
внешние классы, я бы создал его внутри конструктора. Таким образом, вы сохраняете свое внутреннее состояние частным.Если
$config
require требует, чтобы его собственное внутреннее состояние было правильно установлено (например, требуется подключение к базе данных или инициализированы некоторые внутренние поля) перед использованием, то имеет смысл отложить инициализацию до некоторого внешнего кода (возможно, фабричного класса) и внедрить его в конструктор Или, как уже отмечали другие, если это необходимо для совместного использования среди других объектов.источник
ExampleA отделен от конкретного класса Config, что хорошо , если объект получен, если не типа Config, а типа абстрактного суперкласса Config.
ExampleB сильно связан с конкретным классом Config, что плохо .
Создание объекта создает сильную связь между классами. Это должно быть сделано в классе Factory.
источник