php,如何防止直接类实例化?

时间:2016-08-12 11:07:01

标签: php

假设我有一个非常普通的课程:

class Money
{
    public __construct($actualCountry)
    {
        $this->actualCountry = $actualCountry;
    }

    public function getValute()
    {
        return according to actual country
    }
}

这个类需要创建一次,所以我有一个全局工厂:

final class Factory
{
    private $money;

    public function getMoney()
    {
        if ($this->money == null)
        {
            $this->money = new Money(Config::getCountryCode());
        }
        return $this->money;
    }
}

每当我们想要使用时:

Factory::getMoney()->

但今天我看到我的同事试图这样做:

(new Money(Config::getCountryCode()))->getValute();

这显然是错误的,不需要多次出现。但那么一个班级本身怎么说“嘿,不要实例化我,使用工厂”?

我无法将其设置为单身,因为每次都是:

Money::getInstance(Config::getCountryCode());

没有意义。

但真正的问题并不是因为它可能存在多个 - 这就是我总是从配置中传递当前国家的方式。什么是Config成为GlobalConfig?这就是为什么工厂要避免大量的参数传递(如果Money会有更多的参数怎么办?)

4 个答案:

答案 0 :(得分:3)

我认为你应该再考虑一下Singleton模式。它更适合你想要的逻辑。

<?php
class Money
{
    private static $instance;

    private function __construct($countryCode)
    {
        #your code here...
    }

    /**
     * Do not include parameter for getInstance.
     * Make the call internally.
     * Now when you have to change Config to GlobalConfig will be painless.
     */
    public static function getInstance()
    {
        if (null === self::$instance) {
            return self::$instance = new Money(Config::getCountryCode());
        }

        return  self::$instance;
    }
}

答案 1 :(得分:2)

您可以将构造函数设置为private或protected以防止直接实例化。 (参见提及单身模式的其他答案)。

如果你不能/不想使用单例模式,你必须接受构造函数是公共的,或者使用一些闭包绑定魔法来绕过它。

免责声明:这只是一个概念证明。我不会在任何生产代码中使用它。在PHP支持名称空间/类可见性/朋友类之前,没有很好的方法可以实现这一点。

class Factory
{
    private static $money;

    static function getMoney()
    {
        if (null === self::$money) {
            self::$money = self::createInstance('Money', [Config::getCountryCode()]);
        }

        return self::$money;
    }

    private static function createInstance($class, array $args)
    {   
        // bind a closure to the given class and call it
        return call_user_func(Closure::bind(function () use ($class, $args) {
            // create a new instance without calling the constructor
            $object = (new ReflectionClass($class))->newInstanceWithoutConstructor();

            // call the constructor manually from this context (which has access to it now)
            call_user_func_array([$object, '__construct'], $args);

            // return the constructed object
            return $object;
        }, null, $class));
    }
}

使用工厂:

var_dump(Factory::getMoney());

输出:

  

对象(金钱)#3(1){     [ “actualCountry”: “金钱”:私人] =&GT;     string(10)“some_value”   }

尝试直接实例化

输出:

new Money();
  

致命错误:从...在线...

中的无效上下文调用私有Money :: __ construct()

答案 2 :(得分:1)

没有办法完全阻止外部实例化,因为要使Factory工作,构造函数必须直接或间接地公开访问。

您可以通过将构造函数设置为私有来隐藏构造函数,并向类中添加静态工厂方法:

class Money
{
    private function __construct($actualCountry)
    {
        $this->actualCountry = $actualCountry;
    }

    public static function fromFactory($actualCountry)
    {
        return new static($actualCountry);
    }

    public function getValute()
    {
        // return according to actual country
    }
}

final class Factory
{
    private $money;

    public function getMoney()
    {
        if ($this->money == null)
        {
            $this->money = Money::fromFactory(Config::getCountryCode());
        }
        return $this->money;
    }
}

或者你可以改变构造函数以要求一个(类型的)第二个参数,这是工厂使用的。但是,如果您的工厂使用静态方法,这将不起作用,就像在您的示例中一样:

class Money
{
    public function __construct($actualCountry, Factory $factory)
    {
        $this->actualCountry = $actualCountry;
    }

    public function getValue()
    {
        // return according to actual country
    }
}

final class Factory
{
    private $money;

    public function getMoney()
    {
        if ($this->money == null)
        {
            $this->money = new Money(Config::getCountryCode(), $this);
        }
        return $this->money;
    }
}

答案 3 :(得分:0)

如果我正确地得到你的漂移,你需要的是Abstract Class

根据需要,无法直接初始化抽象类。

  

定义为抽象的类可能无法实例化,.....