Static classes in PHP via abstract keyword?

2019-03-09 02:12发布

According to the PHP manual, a class like this:

abstract class Example {}

cannot be instantiated. If I need a class without instance, e.g. for a registry pattern:

class Registry {}
// and later:
echo Registry::$someValue;

would it be considered good style to simply declare the class as abstract? If not, what are the advantages of hiding the constructor as protected method compared to an abstract class?

Rationale for asking: As far as I see it, it could a bit of feature abuse, since the manual refers to abstract classes more as like blueprints for later classes with instantiation possibility.

Update: First of all, thanks for all the answers! But many answers sound quite alike: 'You cannot instantiate an abstract class, but for a registry, why not using a singleton pattern?'

Unfortunately, that was more or less exactly a repeat of my question. What is the advantage of using a singleton pattern (a.k.a. hiding __construct()) compared to just declaring it abstract and not having to worry about that? (Like, e.g., it is a strong connotation between developers, that abstract classes are not actually used or so.)

10条回答
欢心
2楼-- · 2019-03-09 02:47

I wouldnt use an abstract class. Id use something more akin to a singleton with a protected/private constructor as you suggest. There should be very few static properties other than $instance which is the actual registry instance. Recently ive become a fan of Zend Frameworks typical pattern which is something like this:

class MyRegistry {

  protected static $instance = null;

  public function __construct($options = null)
  {
  }

  public static function setInstance(MyRegistry $instance)
  {
    self::$instance = $instance;
  }

  public static function getInstance()
  {
     if(null === self::$instance) {
        self::$instance = new self;
     }

     return self::$instance;
  }
}

This way you get a singleton essentially but you can inject a configured instance to use. This is handy for testing purposes and inheritance.

查看更多
Lonely孤独者°
3楼-- · 2019-03-09 02:48

What you describe is permitted by the PHP language, but it's not the intended usage of an abstract class. I wouldn't use static methods of an abstract class.

Here's the downside of doing that: Another developer could extend your abstract class and then instantiate an object, which is what you want to avoid. Example:

class MyRegistry extends AbstractRegistry { }
$reg = new MyRegistry();

True, you only need to worry about this if you're handing off your abstract class to another developer who won't comply with your intended usage, but that's why you would make the class a singleton too. An uncooperative developer can override a private constructor:

class Registry
{
  private function __construct() { }
}

class MyRegistry extends Registry
{
  public function __construct() { } // change private to public
}

If you were using this class yourself, you would simply remember not to instantiate the class. Then you wouldn't need either mechanism to prevent it. So since you're designing this to be used by others, you need some way to prevent those people from circumventing your intended usage.

So I offer these two possible alternatives:

  1. Stick with the singleton pattern and make sure the constructor is also final so no one can extend your class and change the constructor to non-private:

    class Registry
    {
      private final function __construct() {
      }
    }
    
  2. Make your Registry support both static and object usage:

    class Registry
    {
      protected static $reg = null;
    
      public static function getInstance() {
        if (self::$reg === null) {
          self::$reg = new Registry();
        }
        return self::$reg;
      }
    }
    

    Then you can call Registry::getInstance() statically, or you can call new Registry() if you want an object instance.

    Then you can do nifty things like store a new registry instance inside your global registry! :-)

    I implemented this as part of Zend Framework, in Zend_Registry

查看更多
倾城 Initia
4楼-- · 2019-03-09 02:54

As other guys said, you cannot instantiate an abstract class. You could use static methods in your class to prevent instantiating, but I'm not really a fan of doing so unless I have a proper reason.

I might be little bit off-topic now, but in your example you said you wanted this for Registry pattern class. What is the reason you don't want to instantiate it? Wouldn't it better to create an instance of Registry for each registry you want to use?

Something like:

class Registry {
    private $_objects = array( );

    public function set( $name, $object ) {
        $this->_objects[ $name ] = $object;
    }

    public function get( $name ) {
        return $this->_objects[ $name ];
    }
}

I wouldn't even use Singleton in this case.

查看更多
beautiful°
5楼-- · 2019-03-09 02:56

The purpose of an abstract class is to define methods that are 1) meaningful to other classes and 2) not meaningful when not in the context of one of those classes.

To paraphase some of the php docs, imagine you are connecting to a database. Connecting to a database doesn't make much sense unless you have a particular kind of database to connect to. Yet connecting is something you will want to do regardless of the kind of database. Therefore, connecting can be defined in an abstract database class and inherited, and made meaningful by, say, a MYSQL class.

From your requirements, it sounds like you don't intend to do this but instead simply require a class without an instance. Whilst you could use an abstract class to enforce this behaviour, this seems hacky to me because it abuses the purpose of abstract classes. If I encounter an abstract class, I should be able to reasonably expect that this will have some abstract methods, for example, but your class will have none.

Therefore, a singleton seems like a better option.

However, if the reason you wish to have a class without an instance is simply so that you can call it anywhere then why do you even have a class at all? Why not just load every variable as a global and then you can just call it directly rather than through the class?

I think the best way to do this is to instantiate the class and then pass it around with dependency injection. If you are too lazy to do that (and fair enough if you are! Its your code, not mine.) then don't bother with the class at all.

UPDATE: It seems like you are conflicted between 2 needs: The need to do things quickly and the need to do things the right way. If you don't care about carrying a ton of global variables for the sake of saving yourself time, you will assumedly prefer using abstract over a singleton because it involves less typing. Pick which need is more important to you and stick with it.

The right way here is definitely not to use a Singleton or an abstract class and instead use dependency injection. The fast way is to have a ton of globals or an abstract class.

查看更多
神经病院院长
6楼-- · 2019-03-09 02:57

I would say it's a matter of coding habbits. When you think of an abstract class it is usually something you need to subclass in order to use. So declaring your class abstract is counter-intuitive.

Other than that is it just a matter of using self::$somevar in your methods if you make it abstract, rather than $this->somevar if you implement it as a singleton.

查看更多
成全新的幸福
7楼-- · 2019-03-09 02:58

From my understanding, a class without instance is something you shouldn't be using in an OOP program, because the whole (and sole) purpose of classes is to serve as blueprints for new objects. The only difference between Registry::$someValue and $GLOBALS['Registry_someValue'] is that the former looks 'fancier', but neither way is really object-oriented.

So, to answer your question, you don't want a "singleton class", you want a singleton object, optionally equipped with a factory method:

class Registry
{
    static $obj = null;

    protected function __construct() {
        ...
    }

    static function object() {
        return self::$obj ? self::$obj : self::$obj = new self;
    }
}

...

Registry::object()->someValue;

Clearly abstract won't work here.

查看更多
登录 后发表回答