21

根据PHP 手册,这样的类:

abstract class Example {}

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

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

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

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

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

不幸的是,这或多或少完全重复了我的问题。与仅声明它而不必担心这一点相比,使用单例模式(又名隐藏)有什么优势?(例如,开发人员之间的一个强烈含义是,类实际上并没有被使用。)__construct()abstractabstract

4

10 回答 10

19

如果你的类不打算定义一些超类型,它不应该被声明为abstract,我会说。

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

  • __constructand定义__clone为私有方法
    • 所以类不能从外部实例化
  • 而且,通过这种方式,您的类可以创建自己的实例


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

  • 使用单例意味着使用类的实例;使得将非单例类转换为单例类变得更加容易:只需要制作__construct__clone私有,并添加一些getInstance方法。
  • 使用单例还意味着您可以访问可以与普通实例一起使用的所有内容:$this、属性、...
  • 哦,第三个(不确定,但可能有它的重要性):使用 PHP < 5.3,静态方法/数据的可能性较小:
  • 后期静态绑定仅在 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());

会起作用...但是感觉不太自然...这是一个非常简单的示例(请参阅我之前所说的后期静态绑定和魔术方法)

于 2010-03-22T17:38:29.433 回答
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
}

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

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

  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 Framework 的一部分实现,在Zend_Registry

于 2010-04-17T01:33:58.083 回答
1

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

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

就像是:

class Registry {
    private $_objects = array( );

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

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

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

于 2010-03-22T18:07:45.193 回答
1

将一个类设置为只定义静态属性/方法的抽象类不会产生真正的效果。你可以扩展类,实例化它,然后调用它的方法,它会改变静态类的属性。显然非常混乱。

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

简短的回答:私有构造函数将更具表现力且故障安全。

于 2010-04-17T16:32:44.427 回答
1

OO 中有一些常见且广为人知的模式。以abstract非常规的方式使用可能会导致混淆(抱歉,我的一些示例使用 Java 而不是 PHP):

  • 抽象类- 一个旨在概念化共同祖先的类,但实际实例并不意味着存在(例如 shape 是矩形和三角形的抽象超类)。
    通常通过以下方式实现:
    • 在类上使用abstract修饰符以防止直接实例化,但允许从类派生
  • 实用程序类- 不代表解决方案空间中的对象的类,而是有用的静态操作/方法的集合,例如 Java 中的数学类。
    通常通过以下方式实现:
    • 使类不可派生,例如Javafinal在类上使用修饰符,并且
    • 防止直接实例化 - 不提供构造函数并隐藏或禁用任何隐式或默认构造函数(和复制构造函数)
  • 单例类- 确实代表解决方案空间中的对象的类,但其实例化受到控制或限制,通常是为了确保只有一个实例。
    通常通过以下方式实现:
    • 使类不可派生,例如Javafinal在类上使用修饰符,并且
    • 防止直接实例化 - 不提供构造函数并隐藏或禁用任何隐式或默认构造函数(和复制构造函数),以及
    • 提供获取实例的特定方法 - 一种静态方法(通常getInstance()),它返回唯一的实例或有限数量的实例之一
于 2010-04-20T20:28:43.493 回答
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;
  }
}

这样你基本上得到一个单例,但你可以注入一个配置的实例来使用。这对于测试和继承来说很方便。

于 2010-03-22T17:39:05.003 回答
0

abstract真的是为了表示一个“蓝图”,正如你所说的,用于类继承。

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

于 2010-03-22T17:40:48.643 回答
0

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

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

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

因此,单例似乎是一个更好的选择。

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

我认为最好的方法是实例化类,然后通过依赖注入传递它。如果你懒得这样做(如果你是那么公平!它是你的代码,不是我的。)那么根本不要打扰课程。

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

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

于 2010-04-14T22:37:57.400 回答
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行不通。

于 2010-04-19T22:08:39.653 回答
0

我会说这是编码习惯的问题。当您想到抽象类时,通常需要对其进行子类化才能使用。所以声明你的类抽象是违反直觉的。

除此之外,如果将其抽象化,则只需在方法中使用 self::$somevar 即可,如果将其实现为单例,则无需使用 $this->somevar。

于 2010-04-20T08:44:22.667 回答