2

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

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

Class B extends A {

}

Class C extends A {

}

我想在所有扩展 A 的类中都有一个“无”方法,并且这个方法应该返回一个实例——总是相同的——被调用的类。所以:

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

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

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

但我根本不想检查,只写

println Slot.Surfing.Location;

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

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

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

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

作品。为此, None() 必须始终返回相同的对象。我可以在 Surfing 字段上运行检查,也许是一个非 i18n-ed 整数值...... 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 和来自 B 的 C,该物业现在住在哪里,或者应该住在哪里? ——我没有令人满意的答案)。

附录 - 单身人士

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

TL;博士

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

  • 从 OOP 的角度来看,是否让父类维护其“活动”子代的静态数组?
  • 有更好和/或更清洁的方法吗?
4

1 回答 1

0

如果内存不是反对意见(现在很少反对),那么解决方案就是在 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是一个空对象,要将其设置为我需要重新分配的其他东西,我不能简单地继续并修改它——这实际上引入了可怕的耦合,并且有巨大的潜在错误)。

于 2013-09-16T11:09:25.420 回答