【问题标题】:Static classes in PHP via abstract keyword?PHP中的静态类通过抽象关键字?
【发布时间】:2011-01-30 11:09:11
【问题描述】:

根据PHP manual,这样一个类:

abstract class Example {}

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

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

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

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

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

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

【问题讨论】:

  • 你能回顾一下我的帖子吗,我想我已经提到了一个明显的优势。
  • 感谢您的所有回答!不幸的是,赏金的结束是在相当忙碌的时候,所以我无法对答案做出反应,因为这是他们应得的。我终于接受了帕斯卡的回答,因为这是第一次,而且在他的编辑之后也似乎是最全面和最完整的。但是,我阅读了所有内容并学到了很多东西。
  • 我想知道是谁发了所有的-1。需要解释一下吗?

标签: php class static abstract


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

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

      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 中将它作为 Zend 框架的一部分实现了

      【讨论】:

        【解决方案4】:

        据我了解,不应该在 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 在这里不起作用。

        【讨论】:

        • 在 PHP 中,没有实例的类非常有用。我将它们用作命名空间;它们可以被自动加载,它们不需要被声明为全局的以便在函数中使用,如果你为类选择一个好名字,代码更容易阅读。它成功地让我对一个非常简单的实现感到满意。好不好/我们应该使用它们吗?我真的不在乎,因为它非常容易使用、理解并且非常强大。
        【解决方案5】:

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

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

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

        【讨论】:

          【解决方案6】:

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

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

          • __construct__clone 定义为私有方法
            • 因此无法从外部实例化该类
          • 而且,通过这种方式,您的类可以创建自己的实例


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

          • 使用单例意味着使用类的实例;将非单例类转换为单例类更容易:只需将__construct__clone 设为私有,并添加一些getInstance 方法。
          • 使用单例还意味着您可以访问可用于普通实例的所有内容:$this、属性...
          • 哦,第三个 (不确定,但可能有它的重要性):使用 PHP
          • __callStatic 仅在 PHP 5.3 中引入
          • 没有__getStatic, __setStatic, ...
          • 其他几个Magic methods 也一样!
        • 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());
          

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

          【讨论】:

          • 所以,它只是一种风格?
          【解决方案7】:

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

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

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

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

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

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

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

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

          【讨论】:

            【解决方案8】:

            我不会使用抽象类。正如您所建议的,我会使用更类似于带有受保护/私有构造函数的单例的东西。除了$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;
              }
            }
            

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

            【讨论】:

            • 你还应该在其中添加private function __clone(){},这样就无法克隆它。
            【解决方案9】:

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

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

            类似:

            class Registry {
                private $_objects = array( );
            
                public function set( $name, $object ) {
                    $this->_objects[ $name ] = $object;
                }
            
                public function get( $name ) {
                    return $this->_objects[ $name ];
                }
            }
            

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

            【讨论】:

            • 不实例化它的原因:懒惰。如果我要实例化它,我必须在任何函数中将该实例声明为全局变量。如果我只使用静态类方法/成员,那么 Registry 类本身就是全局的。
            • 全局变量是邪恶的!尝试使用其他选项,例如策略模式:en.wikipedia.org/wiki/Strategy_pattern
            • @Boldewyn:我必须同意 Ondrej 的观点,全局变量是邪恶的,如果可能的话应该避免(是的,这也适用于我在下面建议的 Singleton)。有一些方法可以避免在每个类中实例化注册表的需要。除了 Ondrej 的建议之外,另一种方法是只实例化一次,然后使用依赖注入将其注入到任何需要使用它的类中。
            • 好吧,我的意思是不同的。我的意思是,通过一个简单的类,我可以立即在任何函数中执行 Registry::get('abc'),而对于单例,我必须先以某种方式获取它,无论是作为全局变量还是通过一些 Registry::get_instance() 在访问之前它。
            【解决方案10】:

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

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

            【讨论】:

              猜你喜欢
              • 2010-12-23
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-07-27
              • 2011-06-21
              • 1970-01-01
              • 2011-11-15
              • 1970-01-01
              相关资源
              最近更新 更多