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

问题描述 投票:0回答:5

我有一个

Factory Method
来实例化一个类。有没有办法阻止这个类直接实例化?

我看到的唯一选择是使用传递到

__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 oop design-patterns factory factory-pattern
5个回答
3
投票

有一些技巧可以做到这一点:

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

我并不是在推销任何东西。我个人所做的就是使用诸如

@internal
之类的内容来记录 API,然后按照合同将其留给客户。


1
投票

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

工厂

    <?php
    class Factory {

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

通过工厂实例化的类

     <?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

0
投票

最简单的方法是将基类定义为

abstract
abstract
类不能直接实例化,因此您必须在继承的类中重新定义它们的抽象成员:

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
类的更多信息:PHP:类抽象 - 手册


0
投票

对我来说,最好的方法是使用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 中,您可以定义可以在哪些类中实例化该类。

使用trait是因为该功能可以在不相关的不同工厂中使用。

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

详细内容我在《禁止在 PHP 中在工厂外创建对象》一文中描述过


0
投票

匿名类就是答案:

<?php

namespace Paranoid;

interface Wei
{
    function get_wei_str(): string;
}

class Factory {
    /**
     * makeWei
     *
     * @param  string $wei
     * @return Wei
     */
    static function makeWei($wei)
    {
        return new class($wei) implements Wei
        {
            private $wei;

            function __construct(string $wei)
            {
                $this->wei = $wei;
            }

            function get_wei_str(): string
            {
                return $this->wei;
            }
        };
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.