Построение одноэлементного класса
Здесь возникает этический вопрос.
Я планирую использовать в своем новом проекте несколько классов менеджеров, которые будут выполнять различные задачи по всему проекту. Эти классы являются одиночными, но требуют построения на основе параметров.
Что касается того, когда/где должно произойти это строительство, у меня смешанные чувства. До сих пор у меня есть следующие варианты:
Вариант А
Легко просто передать эти параметры методу getInstance
, имея значение по умолчанию null
. При самом первом вызове будут использоваться параметры, и любые дополнительные вызовы полностью игнорируют их.
Хотя это работает, делать это кажется довольно нелогичным по следующим причинам:
Это делает документацию неясной.
getInstance
' первый параметр должен иметь типCollection
, но может бытьnull
... что здесь происходит? Вы можете утверждать, что написание строки об этом в описании прояснит это, но я бы предпочел, чтобы разъяснение было ненужный.Кажется неправильным передавать
getInstance
любые параметры построения. Это связано с тем, что имя метода не содержит явного намека на построение, что делает неясным, что это произойдет.
Вариант В
Я думаю о методе setup
. Этот метод принимает все параметры, вызывает конструктор класса и изменяет внутреннее состояние класса на initialized
.
При вызове метода getInstance
до setup
, он выбросит NotInitializedException
. После вызова программы установки любые дополнительные вызовы setup
приведут к PreviouslyInitializedException
.
После вызова setup
становится доступным getInstance
.
Лично мне этот вариант больше нравится. Но это кажется чрезмерным.
Какой вариант вы предпочитаете? И почему?
5 answers
Я бы, вероятно, попытался отказаться от одноэлементного подхода и передать классы менеджеров всему, что в них нуждается.
$manager = new Manager( $collection, $var, $var2 );
$other_class = New OtherClass( $manager );
//or
$other_class = New OtherClass;
$other_class->manager = $manager;
//or
$other_class = New OtherClass;
$other_class->setManager( $manager );
Используйте внедрение зависимостей для передачи объекта Manager
. Не используйте одноэлементный шаблон. По общему мнению, его использование создает глобальное состояние и делает ваш API обманчивым.
Введите экземпляр Manager
в любой класс, которому он нужен, с помощью конструктора. Каждый класс не должен пытаться создать экземпляр Manager
самостоятельно , единственный способ классы получают экземпляр Manager
, получая его из конструктора.
class NeedsManager
{
protected $manager;
public function __construct(Manager $manager)
{
$this->manager = $manager;
}
}
Вам не нужно применять один экземпляр Manager
. Просто не создавайте его более одного раза. Если все ваши классы, которым нужен экземпляр Manager
, получат то, что им нужно, от конструктора и никогда не попытаются создать его самостоятельно, это гарантирует, что в вашем приложении будет только один экземпляр.
Как насчет варианта 3. Если они являются настоящими синглетонами, настройте файлы свойств для их параметров для использования с getInstance без аргументов.
Если это не подходит, возможно, вы неправильно используете одноэлементный шаблон.
Вы рассматриваете возможность использования шаблона заводского проектирования. Фабрики - это объекты, которые действуют как причудливые конструкторы для других объектов. В вашем случае вы перенесете настройку и получите установку на завод. Статья в вики довольно хороша - http://en.wikipedia.org/wiki/Factory_method_pattern
class SingletonFoo {
//properties, etc
static $singleton = NULL;
private function __constructor(){}
static function getInstance(){
if(NULL === self::$singleton) {
self::$singleton = new SingletonFoo();
}
return self::$singleton;
}
}
class FooFactory {
static $SingletonFoo = null;
static function setup($args){
if( !(NULL === self::$SingletonFoo)){
throw new AlreadyInstantiatedException();
}
self::$SingletonFoo = SingletonFoo::getInstance();
//Do stuff with $args to build SingletonFoo
return self::$SingletonFoo;
}
static function getInstance(){
if(NULL === self::$SingletonFoo) {
throw new NotInstantiatedException();
}
return self::$SingletonFoo;
}
}
Не используйте Синглтон, используйте Диспетчер ресурсов (или Контейнер службы, или контейнер DI):
class ResourceManager
{
protected static $resource;
public static function setResource($resource)
{
if (!empty(self::$resource)) //resource should not be overwritten
{
if ($resource!=self::$resource) return false;
else return true;
}
self::$resource = $resource;
return true;
}
public static function getResource()
{
return self::$resource;
}
}
Диспетчер ресурсов позволяет вам устанавливать любые пользовательские классы для модульного тестирования (например, внедрение зависимостей), вы можете просто получать необходимые ресурсы, не запрашивая их в конструкторе (мне нравится DI, но иногда удобнее использовать пустые конструкторы).
Готовый к использованию вариант: http://symfony.com/doc/current/book/service_container.html (Мне не нравится перемещать логику из код для конфигураций, но в автономном модуле это выглядит приемлемо).