工厂方法:防止 class 直接实例化

Factory Method: Prevent a class from Direct Instancing

我有一个 Factory Method 来实例化一个 class。有没有办法防止这个 class 直接实例化?

我看到的唯一选择是使用传递给 __construct() 的参数,但这不是我想要的。

另一方面,将 __construct() 设为私有是理想的,但我不希望 MyClass 在没有实际需要的情况下扩展 Factory

大家怎么看?

工厂方法:

class Factory
{
    public static function instance()
    {
        return new MyClass(true);
    }
}

我的班级:

class MyClass
{
    public function __construct($isFactory = false)
    {
        if (!$isFactory) {
            throw new Exception('Use Factory::instance() to create an object');
        }
    }
}

本质上,您的代码应该是这样的:

工厂

    <?php
    class Factory {

        public static function instance(){
            return new MyClass(true); //HERE YOU ARE INSTANTIATING
        }
    }

CLASS 将通过工厂实例化

     <?php
    //NOT MyClass() <--- YOU ARE DEFINING.... NOT INSTANTIATING...
    class MyClass {

        public function __construct($isFactory = false) {
            if (!$isFactory) {
                throw new Exception('Use Factory::instance() to create an object');
            }
        }

        //...MORE METHODS
    }

你能试试这个吗?

    <?php
        class Factory
        {
            private static $FACTORY_GUARANTOR;      //ONLY SET DURING INSTANTIATION
            public static function instance($type) {
                if (class_exists($type)) {
                    self::$FACTORY_GUARANTOR = 1;
                    $instance = new $type();
                    self::$FACTORY_GUARANTOR = null;
                    return $instance;
                }
                else {
                    throw new Exception("Class not found...");
                }
            }

            //YOU CAN GET $FACTORYGUARANTOR EXTERNALLY BUT NEVER SET IT;
            public static function getGuarantor(){
                return self::$FACTORY_GUARANTOR;
            }
        }



        class MyClass {
            protected $property1;
            protected $property3;
            protected $property2;

            public function __construct() {
                // IF SOMEONE TRIES TO INSTANTIATE THE CLASS OUTSIDE OF THE FACTORY... BLOW A WHISTLE
                if(!Factory::getGuarantor()){
                    throw new Exception('Use Factory::instance() to create an object');
                }
                // IF THE PROGRAM MADE IT TO THIS POINT;
                // JUST INSTANTIATE THE CLASS BECAUSE MOST LIKELY IT IS COMING FROM THE FACTORY
                var_dump($this); // A LITTLE CONFIRMATION....
            }

            //...MORE METHODS
        }

        // TRY IT OUT:
        /*INSTANCE A: RIGHT*/   $theClass   = Factory::instance("MyClass"); //INSTANTIATES THE CLASS
        /*INSTANCE B: WRONG*/   $theClass   = new MyClass();                //THROWS AN EXCEPTION

有技巧可以做到这一点:

  • 滥用继承来使用 protected 构造函数
  • 将工厂方法放在class中,这样它就可以调用私有构造函数,这实际上不是hack。但是为什么不首先使用构造函数呢?
  • 使用反射访问私有构造函数

我不是在推销任何东西。我个人所做的是用 @internal 之类的东西记录 API,然后根据合同将其留给客户。

最简单的方法是将基数 class 定义为 abstractabstract classes不能直接实例化,所以你必须在继承的classes中重新定义它们的抽象成员:

abstract class Factory
{
    abstract public function foo();
}

class InheritedClass extends Factory
{
    public function foo()
    {
        // Do something
    }
}

// $obj1 = new Factory(); // Will produce an error
$obj1 = new InheritedClass(); // Will be executed successfully

您可以在此处阅读有关 abstract class 的更多信息:PHP: Class Abstraction - Manual

对我来说,最好的方法是使用 ReflectionClass:

class MyClass
{    
    public const FRIEND_CLASSES = [Factory::class];

    protected function __construct() {}
}

trait Constructor
{
    protected function createObject(string $className, array $args = [])
    {
        if (!in_array(static::class, $className::FRIEND_CLASSES)) {
            throw new \Exception("Call to private or protected {$className}::__construct() from invalid context");
        }
        $reflection = new ReflectionClass($className);
        $constructor = $reflection->getConstructor();
        $constructor->setAccessible(true);
        $object = $reflection->newInstanceWithoutConstructor();
        $constructor->invokeArgs($object, $args);
        return $object;
    }
}

class Factory
{
    use Constructor;

    public function MyClass(): MyClass
    {
        return $this->createObject(MyClass::class);
    }
}

在常量 FRIEND_CLASSES 中,您可以定义在 class 中可以实例化 class。

使用 trait 是因为此功能可用于不相关的不同工厂。

如果您需要将参数放入 class 的构造函数中,请将它们作为 createObject 的第二个参数。

我在文章“Forbidding of creating objects outside factory in PHP

中描述的细节