【问题标题】:Make abstract class method only publicly accessible from child classes使抽象类方法只能从子类公开访问
【发布时间】:2021-02-21 17:58:55
【问题描述】:

首先,我们谈论的是 PHP 7.4.10,但任何一般的英特尔都可以使用!

总结问题:我想在抽象类中定义一个静态方法,该方法只能从扩展抽象类的子类公开调用,而不能从抽象类本身调用。对不起,如果我在这里太简单了,但我确实一直在寻找几个小时的答案,甚至找不到关于该主题的任何讨论。

让我们考虑以下示例(cmets 中的解释)。我希望能够打电话给Apple::printName()Pear::printName(),但不能打电话给Fruit::printName()

abstract class Fruit
{
    /*
     * Oblige every child class to define a string name, nothing unusual
     */
    protected abstract static function name() : string;

    /*
     * The problem is with the access modifier of this method here
     *** 
     * If it is public, everything is fine with Apple::printName() and Pear::printName(), 
     * but one can call Fruit::printName() from outside,
     * resulting in PHP Error: Cannot call abstract method Fruit::name()
     * this is still sort of okay, since an error will be thrown anyway, 
     * but I don't want the runtime to even enter the method's body
     * I'd like to get an access restriction error.
     *** 
     * If it is protected, then we automatically can't call Apple::printName nor Pear::printName()
     ***
     * So, is there a way to define the parent static method only publicly accessible from child classes without copying code?
     */
    public static function printName()
    {
        return "My name is: " . static::name();
    }
}

class Apple extends Fruit
{
    protected static function name() : string
    {
        return "apple";
    }
}

class Pear extends Fruit
{
    protected static function name() : string
    {
        return "pear";
    }
}

echo Apple::printName(); //prints "My name is: apple"
echo Pear::printName(); //prints "My name is: pear"
echo Fruit::printName(); //PHP Error: Cannot call abstract method Fruit::name() at line...

对于如何实现所需行为,我也愿意接受任何替代方法。

【问题讨论】:

    标签: php encapsulation abstraction php-7.4


    【解决方案1】:

    所以在阅读了所有答案后,我想出了一个变通的解决方案,最终决定支持 @chilliNUT 的建议。我最终按照以下方式进行:

    abstract class Fruit
    {
        protected abstract static function name() : string;
    
        /*
         * Basically, if you call the default method from any child (not necessarily a direct child) class, no problems,
         * but if you call it directly from the baseclass, you get the somewhat acceptable exception :)
         */
        public static function printName()
        {
            if(!is_subclass_of(static::class, self::class))
            {
                throw new \BadMethodCallException("Can't call base Fruit class methods directly!");
            }
    
            return "My name is: " . static::name();
        }
    }
    
    //those are abstract now, just to match the intention of them being utility singletons
    abstract class Apple extends Fruit
    {
        protected static function name() : string
        {
            return "apple";
        }
    }
    
    abstract class Pear extends Fruit
    {
        protected static function name() : string
        {
            return "pear";
        }
    }
    

    无论如何,从@Rain 的回答中,我可以看出为什么这往往是一种不好的做法,实际上不是继续发展的最佳方向,所以我会考虑不同的策略来解决我的问题'正在努力实现。我的好奇心在一天结束时得到了满足!谢谢大家的参与!

    【讨论】:

    • 不客气。不错的解决方案,但我认为您甚至不希望运行时进入方法的主体:D
    • 嗯,你看,这是最接近“甚至不进入”的 - 它在进入后立即停止! :D 重点是我不想执行不可预测的行为并根据方法的主体以及可能会或可能不会破坏其中的内容而得到不可预测的错误。
    【解决方案2】:
    public static function printName() {}
    

    这是一个具体方法的定义。有人可能会认为调用这个方法是可以的,因为它是非抽象的公共方法。那么,如果Fruit 对它没有任何作用,为什么还要拥有它呢?

    所以一种解决方案是只在那些真正需要它的人中定义traituse 吗?

    trait Printer { 
        
        public static function printName()
        {
            return "My name is: " . static::name();
        }   
    }
    
    abstract class Fruit
    {
        protected abstract static function name() : string;
    }
    
    class Apple extends Fruit
    {
        use Printer;
        protected static function name() : string
        {
            return "apple";
        }
    }
    
    class Pear extends Fruit
    {
        use Printer;
        protected static function name() : string
        {
            return "pear";
        }
    }
    

    但是,如果您的 Fruit 只是您在评论中提到的子类的定义,并且您碰巧拥有 PHP 8 :) 那么也许不要将 printName 设为静态

    public function printName()
    {
        return "My name is: " . static::name();
    }
    

    $a = new Apple();
    $p = new Pear();
    
    echo $a->printName();
    echo $p->printName();
    

    现在echo Fruit::printName(); 将为您提供您一直在寻找的致命错误。

    致命错误:未捕获错误:非静态方法 Fruit::printName() 不能静态调用

    【讨论】:

    • 感谢您的回答!我也想到了这种情况,但有两件事对我来说似乎并不那么复杂和最佳: - 特征 Printer 可以包含在不一定包含静态 name() 方法的类中; - 我必须将use Printer; 添加到每个已经 扩展Fruit 的类中,这在某种程度上是一种代码复制形式,虽然非常少并且可以说已经足够好。无论如何,感谢您的建议,这可能确实是最好的选择,但是如果存在其他实现上述行为的方法,我仍然感兴趣。 :)
    • 并回答您最初的问题:Fruit 将其作为默认功能提供给其所有子类,这是必需的,因为它们实现了abstract static function name() : string,而这又是在printName() 方法中使用。
    • 我明白你的意思,虽然原则包括 objects 类型,我的问题是关于静态实现。我同意这证明违反了 Liskov 原则。但是假设我想要 100 个水果子类,其中只有 2 个需要对静态 printName() 进行不同的实现。我仍然希望为他们提供默认实现,但也希望能够覆盖它。或者这种愿望本身是否违反了 Liskov 原则?
    • 我看到了你的更新,但我实际上对静态实现更感兴趣,因为这些是我试图派生的实用程序类。非常感谢您的努力!
    【解决方案3】:

    您可以检查您的实例是否是子类,如果不是则保释

    abstract class A1 {
        public static function childrenOnly() {
            return is_subclass_of(new static, 'A1');
        }
    }
    
    class A2 extends A1 {
    }
    
    Al::childrenOnly(); // errors out
    A2::childrenOnly(); // true
    

    【讨论】:

    • 这可能是一个不错的小解决方法,您将它放在父类中一次并完成。我最终制作了ApplePear 抽象(因为它们是实用程序单例)并在Fruit 中实施您的建议。您可以看到我的结论。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多