Блокировка возможности создавать классы напрямую, минуя фабрику

В базовом классе для всех моделей в нашей системе MVC я создал фабричный метод BaseCLass::getNew(), который возвращает экземпляр запрошенного дочернего класса при вызове через SomeChildClass::getNew().

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

 new SomeChildClass

создаст исключение при создании, и можно будет использовать только классы, созданные фабрикой.

Любые идеи, как это может быть достигнуто?

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

редактировать: я не могу сделать свой конструктор закрытым, так как конструктор фреймворка в классе, который я наследую, является общедоступным, и php не позволит мне этого.


person shealtiel    schedule 27.03.2011    source источник


Ответы (4)


Сделав класс частным конструктором.

Обновление — решение, отвечающее заявленным вами требованиям.

class Base {
    private static $constructorToken = null;

    protected static function getConstructorToken() {
        if (self::$constructorToken === null) {
            self::$constructorToken = new stdClass;
        }

        return self::$constructorToken;
    }
}

class Derived extends Base {
    public function __construct($token) {
        if ($token !== parent::getConstructorToken()) {
            die ("Attempted to construct manually");
        }
    }

    public static function makeMeOne() {
        return new Derived(parent::getConstructorToken());
    }
}

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

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

person Jon    schedule 27.03.2011
comment
Любой способ обеспечить это с уровня базового класса? Я бы хотел избежать определения этих частных конструкторов для всех подклассов. - person shealtiel; 27.03.2011
comment
Я не могу сделать свой конструктор защищенным, так как конструктор в классе фреймворка, который я наследую, является общедоступным, и php не позволит мне этого. - person shealtiel; 27.03.2011
comment
@gidireich: Тогда вам следует переосмыслить, идете ли вы правильным путем. Я могу думать о вуду для достижения заявленной цели, но это то, что вы не должны быть пойманы мертвым, занимаясь этим. Может быть, вы можете удовлетворить свои бизнес-требования, не требуя, чтобы все строительство проходило по заводским методам? - person Jon; 27.03.2011
comment
Да, давай, дай мне свое вуду! Между прочим, в настоящее время я думаю о вуду debug_backtrace, но давайте послушаем ваши - person shealtiel; 27.03.2011
comment
@gidireich: Я тоже об этом думал ;) - person Jon; 27.03.2011
comment
Можете ли вы объяснить мне, почему точно это вуду? - person shealtiel; 27.03.2011
comment
@gidireich: Потому что это просто плохо на многих уровнях. В любом случае, если бы мне задали этот вопрос в интервью, я бы интерпретировал его как то, что этот парень готов сделать что угодно, даже если это неправильно, чтобы заставить код работать, и немедленно не рекомендует нанимать . С другой стороны, я придумал кое-что не такое уж плохое; проверить это. - person Jon; 27.03.2011
comment
В этот момент я бы либо вытащил файл библиотеки фреймворка и сам отредактировал видимость, либо просто извинился перед своим боссом, что пока нет функции языка, и просто ждал трейтов. - person bob-the-destroyer; 27.03.2011
comment
Параметр конструктора - хорошее направление, и я даже не нашел необходимости использовать магию stdClass. Просто передача согласованного значения из фабричного метода на практике на 100% безопасна, поскольку ни один разработчик не вызовет конструктор напрямую случайно. - person shealtiel; 27.03.2011

Сделав конструктор дочернего класса protected. Родительский класс будет иметь доступ ко всем защищенным методам потомка. Любая попытка напрямую создать дочерний элемент (т.е.: new child) приведет к фатальной ошибке.

<?php

class factory
{

    static public function create()
    {
        return new child;
    }

}

class child extends factory
{

    protected function __construct()
    {
        echo 'Ok';
    }

}

$c = factory::create(); // Ok
$c2 = new child; // fatal error

?>

Хотя этот метод не позволит вам вместо этого генерировать исключение :(

Если тогда это абсолютно необходимо, на ум приходит только функция debug_backtrace() (помимо использование синглтона для самого дочернего элемента или принудительные шаблоны пула объектов с использованием и передачей GUID, сгенерированных фабрикой и проверенных дочерним элементом). В дочернем конструкторе посмотрите на 2-е значение массива, чтобы убедиться, что «функция» === «создать» и «класс» === «фабрика». Выдать исключение, если не соответствует. Сначала я не предлагал этого, только потому, что я подозреваю, что использование debug_backtrace может привести к снижению производительности.

person bob-the-destroyer    schedule 27.03.2011
comment
Это не сработает для меня, так как конструктор my parent уже общедоступен, и я не могу его изменить. В вашем примере это означает, что фабрика расширяет ParentOfFactoryAFrameworkClass - person shealtiel; 27.03.2011
comment
@gidireich: извините, пропустил эту часть. Попытка повысить видимость дочернего метода действительно приведет к фатальной ошибке. Я не знаю, как это преодолеть, кроме того, что я только что добавил в свой ответ. - person bob-the-destroyer; 27.03.2011
comment
Собственно, в этом контексте подходит решение debug_trace. Код, вызывающий его, может быть помечен для выполнения только в процессе разработки, а не в рабочей среде, что достаточно для любых практических нужд. - person shealtiel; 27.03.2011

Объявите конструктор класса закрытым, и его можно будет вызывать только из собственных методов класса, таких как getNew().

person meagar    schedule 27.03.2011
comment
Я не могу сделать свой конструктор приватным/защищенным, так как конструктор в классе фреймворка, который я наследую, является общедоступным, и php не позволит мне этого. - person shealtiel; 27.03.2011
comment
@gid Затем напишите класс-оболочку, который содержит экземпляр класса фреймворка, а не наследуется напрямую от класса. Выберите композицию вместо наследование. Не существует внешнего способа предотвратить создание экземпляра класса с помощью new; вы обязательно должны иметь возможность изменять функциональность класса из определения класса. - person meagar; 27.03.2011

есть несколько способов реализовать это

  • сделать магию частного использования родительского класса
  • пользовательская магическая функция __autoload; проверьте тип класса и через ошибку с недопустимым сообщением

http://php.net/manual/en/function.is-a.php

person Pramendra Gupta    schedule 27.03.2011
comment
сделать родительский класс частным использованием магии - не понимаю, что это значит, можете пояснить? - person shealtiel; 27.03.2011
comment
что касается второго предложения, что вы подразумеваете под проверкой класса? Это обязательно будет какой-то подкласс моего класса, но как эта информация мне поможет? - person shealtiel; 27.03.2011