PHP中的静态类是否通过抽象关键字?

时间:2010-03-22 17:31:51

标签: php class static abstract

根据PHP manual,这样的课程:

abstract class Example {}

无法实例化。如果我需要一个没有实例的类,例如对于注册表模式:

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

简单地将类声明为抽象是否会被认为是好的风格?如果没有,与抽象类相比,隐藏构造函数作为受保护方法有什么好处?

提问的理由:据我所知,它可能会有一些滥用功能,因为手册中的抽象类更像是后续类的蓝图,具有实例化可能性。

更新:首先,感谢所有答案!但是很多答案听起来很相似:'你不能实例化一个抽象类,但对于注册表,为什么不使用单例模式?'

不幸的是,这或多或少完全是我的问题的重复。使用单例模式(a.k.a.隐藏__construct())的优势与仅仅声明它abstract并且不必担心这一点相比是什么? (例如,开发人员之间的强烈关注,abstract类实际上并未使用左右。)

10 个答案:

答案 0 :(得分:19)

如果你的课程不是要定义一些超类型,那么我不应该将其声明为 abstract

在你的情况下,我宁愿去上课:

  • __construct __clone 定义为私有方法
    • 所以班级不能从外面实现
  • 并且,这样,您的类可以创建自己的实例


现在,为什么要使用Singleton,而不仅仅是静态方法?我想,至少有几个原因是有效的:

  • 使用单例表示使用类的实例;可以更轻松地将非单例类转换为单例类:只需将__construct__clone设为私有,并添加一些getInstance方法。
  • 使用单例也意味着您可以访问可以使用普通实例的所有内容:$this,属性,...
  • 哦,第三个(不确定,但可能有它的重要性):PHP< 5.3,静态方法/数据的可能性较小:
  • Late Static Binding仅添加了PHP 5.3;当使用静态方法/类时,并没有经常使它变得更难;特别是在使用继承时。


这就是说,是的,有些代码是这样的:

abstract class MyClass {
    protected static $data;
    public static function setA($a) {
        self::$data['a'] = $a;
    }
    public static function getA() {
        return self::$data['a'];
    }
}

MyClass::setA(20);
var_dump(MyClass::getA());

会起作用......但感觉不太自然......这是一个非常简单的例子(参见我之前所说的Late Static Binding和魔术方法)。 / p>

答案 1 :(得分:3)

PHP语言允许您描述的内容,但它不是abstract类的预期用法。我不会使用static类的abstract方法。

这样做的缺点是:另一个开发人员可以扩展您的抽象类,然后实例化一个对象,这是您想要避免的。例如:

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

是的,如果您将抽象类交给另一位不符合您预期用途的开发人员,您只需要担心这一点,但这就是为什么您也要将该课程作为单身人士的原因。不合作的开发人员可以覆盖私有构造函数:

class Registry
{
  private function __construct() { }
}

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

如果您自己使用此类,则只需记住 not 来实例化该类。那么你就不需要任何一种机制来阻止它。因此,由于您将其设计为供其他人使用,因此您需要一些方法来阻止这些人规避您的预期用途。

所以我提供了这两种可能的选择:

  1. 坚持使用单例模式,并确保构造函数也是final,因此没有人可以扩展您的类并将构造函数更改为非私有:

    class Registry
    {
      private final function __construct() {
      }
    }
    
  2. 使您的注册表支持 静态和对象使用:

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

    然后您可以静态调用Registry::getInstance(),或者如果需要对象实例,可以调用new Registry()

    然后你可以做一些漂亮的事情,比如在全局注册表中存储一个新的注册表实例! : - )

    我在Zend_Registry

  3. 中将其作为Zend Framework的一部分实现

答案 2 :(得分:1)

正如其他人所说,你无法实例化一个抽象类。你可以在你的类中使用静态方法来防止实例化,但除非我有正当的理由,否则我不会真的这样做。

我现在可能有点偏离主题,但在你的例子中,你说你想要这个用于Registry模式类。你不想实例化它的原因是什么?为每个要使用的注册表创建一个Registry实例会不会更好?

类似的东西:

class Registry {
    private $_objects = array( );

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

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

在这种情况下,我甚至不会使用Singleton。

答案 3 :(得分:1)

将类设置为仅定义静态属性/方法的抽象将不会产生实际效果。您可以扩展类,实例化它,并在其上调用方法,它将更改静态类属性。显然很混乱。

摘要也具有误导性。 Abstract是定义一个实现某些功能的类,但需要更多行为(通过继承添加)才能正常运行。最重要的是,它通常是一个不应该与静态一起使用的功能。你几乎邀请程序员错误地使用它。

简短回答:私有构造函数会更具表现力并且不安全。

答案 4 :(得分:1)

OO中存在一些常见且公认的模式。以非常规的方式使用abstract可能会引起混淆(抱歉,我的一些示例是Java而不是PHP):

  • 抽象类 - 一个用于概念化共同祖先的类,但其实际实例并不存在(例如,形状是矩形和三角形的抽象超类)。
    通常由以下人员实施:
    • 在类上使用abstract修饰符以防止直接实例化,但允许从类派生
  • 实用程序类 - 一个不代表解决方案空间中的对象的类,而是一组有用的静态操作/方法,例如Java中的数学课 通常由以下人员实施:
    • 使类不可导出,例如Java在类上使用final修饰符,
    • 阻止直接实例化 - 不提供构造函数并隐藏或禁用任何隐式或默认构造函数(以及复制构造函数)
  • 单例类 - 一个表示解决方案空间中的对象的类,但其实例化受到控制或限制,通常是为了确保只有一个实例。
    通常由以下人员实施:
    • 使类不可导出,例如Java在类上使用final修饰符,
    • 阻止直接实例化 - 不提供构造函数并隐藏或禁用任何隐式或默认构造函数(以及复制构造函数),并且
    • 提供获取实例的特定方法 - 静态方法(通常为getInstance()),返回唯一的实例或有限数量的实例之一

答案 5 :(得分:0)

我不会使用抽象类。我建议你使用类似于具有受保护/私有构造函数的单例的东西。除了$instance之外,应该存在非常少的静态属性,这是实际的注册表实例。最近,我成为Zend Frameworks典型模式的粉丝,就像这样:

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;
  }
}

这种方式基本上可以获得单例,但是您可以注入要使用的已配置实例。这对于测试目的和继承来说非常方便。

答案 6 :(得分:0)

abstract实际上是为了表示一个“蓝图”,就像你所说的那样,用于类继承。

注册表通常遵循单例模式,这意味着它们将在私有变量中实例化它们。将其定义为abstract会阻止此工作。

答案 7 :(得分:0)

抽象类的目的是定义1)对其他类有意义的方法; 2)当不在其中一个类的上下文中时没有意义。

要介绍一些php文档,假设您正在连接到数据库。除非您有特定类型的数据库要连接,否则连接到数据库没有多大意义。然而,无论数据库类型如何,连接都是您想要做的事情。因此,可以在抽象数据库类中定义连接并继承,并通过MYSQL类使其有意义。

根据您的要求,听起来您不打算这样做,而只需要一个没有实例的课程。虽然你可以使用抽象类来强制执行这种行为,但这对我来说似乎很糟糕,因为它滥用了抽象类的目的。如果我遇到一个抽象类,我应该能够合理地期望这将有一些抽象方法,例如,但是你的类将没有。

因此,单身人士似乎是一个更好的选择。

但是,如果您希望拥有一个没有实例的课程的原因只是为了让您可以在任何地方调用它,那么为什么您甚至可以拥有一个课程呢?为什么不直接将每个变量加载为全局变量然后你可以直接调用它而不是通过类?

我认为最好的方法是实例化类,然后通过依赖注入传递它。如果你太懒了(如果你的话,那就太公平了!这是你的代码,而不是我的代码。)然后根本不打算上课。

更新:您似乎在两个需求之间存在冲突:需要快速做事并且需要以正确的方式做事。如果你不关心为了节省自己的时间而携带大量的全局变量,你会更喜欢使用抽象而不是单例,因为它涉及较少的输入。选择哪个需求对您来说更重要并坚持下去。

这里正确的方法绝对不是使用Singleton或抽象类而是使用依赖注入。快速的方法是拥有大量的全局或抽象类。

答案 8 :(得分:0)

根据我的理解,没有实例的类是你不应该在OOP程序中使用的东西,因为类的整个(和唯一的)目的是作为新对象的蓝图。 Registry::$someValue$GLOBALS['Registry_someValue']之间的唯一区别是前者看起来“更加漂亮”,但两种方式都不是真正的面向对象。

所以,要回答你的问题,你不需要一个“单例类”,你需要一个单例对象,可选择配备一个工厂方法:

class Registry
{
    static $obj = null;

    protected function __construct() {
        ...
    }

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

...

Registry::object()->someValue;

显然abstract在这里不起作用。

答案 9 :(得分:0)

我想说这是编码habbits的问题。当你想到一个抽象类时,通常需要子类才能使用它。因此,宣布你的班级抽象是违反直觉的。

除此之外,只是在你的方法中使用self :: $ somevar而不是$ this-> somevar,如果你把它作为单例实现的话。