让 PHP 子类继承静态属性的单独实例


Have PHP child classes inherit separate instances of a static property

标题可能不太清楚,所以这里有一个例子。

abstract Class A {
    public static $property = null;
}
Class B extends A {
}
Class C extends A {
}

我想在所有扩展 A 的类中都有一个"None"方法,并且此方法应该返回被调用类的实例 - 始终相同。所以:

B::None()  // Returns a "default" B
C::None()  // Returns a "default" C

为什么会这样:我有几个(简化)插槽,可能会也可能不会分配给几种类型的活动。所以我可以有一个冲浪插槽和一个游泳插槽。并且该插槽可能为空。在我的代码中,在报告时,我当然可以做类似的事情

if (Slot.Surfing == null) {
    println "Not surfing anywhere";
} else {
    println Slot.Surfing.Location;
}

但我根本不想检查,只是写

println Slot.Surfing.Location;

并将插槽预先分配给冲浪::无()。实际上,我会在超类中执行此操作,并让它自动分配None的"正确"实例。

这样,Slot.Surfing(nowhere)与Slot.Swimming(nowhere)是不同的空值,但现在对我来说实际上是一个功能

问题是,如果我真的想检查我在某个地方游泳,我必须确保

if (Slot.Surfing == Surfing::None()) {

工程。为此,None() 必须始终返回相同的对象。我可以在冲浪字段上运行检查,也许是一个非 i18n 的整数值......0 或 -1 是典型的选择...但是为此目的添加属性似乎设计不当。

注意:这与单例(反)模式有许多相似之处,但它实际上不是单例(见底部)。

但是,如果我在 A 中实现 None() 方法,我必须处理这样一个事实,即任何静态属性只会在 A 中"存在"一次,而不是在每个子类中。因此,我创建了一个 B 的默认实例,将其保存在 A 中,然后所有后续对 A 其他子类的调用都将找到并返回该实例 - C::None()将是 B 的一个实例。

我可以让 None() 方法每次都创建一个新的默认实例。这有效,但现在我有几个"默认"B,在某些情况下,两个都设置为 B::None() 的属性将非常正确地被视为不同

最后我想出了这个解决方法(PHP 5.3.+):

private static $nones = array(); // array of already created "null" instances
protected static function None() {
    // If I do not already have an instance for this class...
    if (!isset(self::$nones[$ChildName = get_called_class()])) {
        // ... I create a default instance.
        self::$nones[$ChildName] = new $ChildName(/*...*/);
    }
    // And I return the default instance.
    return self::$nones[$myClass];
}

我已经检查了Stack Overflow和其他地方的一些问题和答案,最相关的一个采用了相同的方法(请注意在被调用类名称上索引的$instance数组):

class A { 
    public static function getInstance(){
        // Maybe use this function to implement the singleton pattern ...
        return self::$instance[get_called_class()];
    }
    public function className(){
        return get_class(self::getInstance());
    }   
}

然而,也许因为我仍然是一个湿漉漉的OOPer,对我来说,这种方法闻起来很香。我认为应该有一种方法可以在父级中声明子静态属性,并从超类访问它(当然,那么我必须问自己:如果 A 声明一个"下游"静态属性,B 从 A 继承,C 从 B 继承,该属性位于何处,或者应该位于何处, 现在?——我没有令人满意的答案)。

附录 - 单例

上述方法在实践中与Singleton没有太大区别。似乎(感谢Touki指出我在那里)我可以通过依赖注入摆脱单例。然而,在这种情况下,它需要传递,比如说,None_C传递到所有可能需要引用 C 实例的默认值的方法。然后,我必须将None_C推送到我的Configuration对象中,并让它知道我可能声明的任何 A 子类。乍一看,这闻起来更臭(尽管实际上添加 A 的另一个子类相当于更改系统的配置......这将是更改Configuration的原因)。

博士

因此,长

话短说,假设上述方法确实有效,

  • 从 OOP 的角度来看,让父类维护其"活动"子类的静态数组是否可以接受?
  • 有没有更好和/或更清洁的方法?

如果记忆不是反对意见(现在很少反对),解决方案就是进一步进入 OOP 领域。

需要"儿童单身人士"的真正问题是我希望比较是可靠的。当然,两个不同的"空"对象虽然都是"空的",因此在这方面是相同的,但不是同一个对象。如果它们也具有相同的属性,则==运算符将返回 TRUE,但如果其中一个由于某种原因(即由于错误)而略有更改,==将返回 FALSE。在单例上下文中使用 PHP5 运算符===将解决此问题,但引入了更多的复杂性。

(对我来说不是那么明显)简单的解决方案是完全放弃比较并简单地替换:

if (Slot->Surfing == Surfing::None())

与合适的

Slot->Surfing->isNull() // or maybe isEmpty() depending on semantics

然后我可以在类中声明 isEmpty 方法,这表明我确实还需要一个更具体的等号比较运算符:

abstract Class A {
    ...
    public static function createNull() {
        $nulled = new A();
        $nulled->setNull();     // Whatever is needed to nullify an object
        return $nulled;
    }
    public function setNull() {
        $this->nullFlag = true; // Example
        return $this;
    }
    public function isNull() {
        return (true === ($this->nullFlag)); // Or whatever else
    }
    
    abstract function equals($obj);
    abstract function compareTo($obj);
}
Class Surfing extends A {
    public function equals($obj) {
        if (null == $obj) {
            return false;
        }
        if ($this === $obj) {
            return true;
        }
        if (!($obj instanceOf Surfing)) {
            return false;
        }
        // Properties
        foreach(array('property1','property2',...) as $val) {
            if ($this->$val !== $obj->$val) {
                return false;
        }
        // Maybe some other check
        ...
        return true;
    }
}
...
Slot->Surfing = Surfing::createNull();
...
if (Slot->Surfing->isNull()) {
    ...
}

这种方法的另一个很大优点是,现在每个活动都是一个独立的对象,可以安全地更新而无需更改其他实例(如果Slot->Surfing是一个空对象,要将其设置为我需要重新分配的其他内容,我不能简单地继续修改它 - 这实际上引入了可怕的耦合,并且具有巨大的微妙错误潜力)。