【问题标题】:OOP (PHP) - Force overridden method to call according parent-methodOOP (PHP) - 强制重写的方法根据父方法调用
【发布时间】:2015-10-04 18:48:53
【问题描述】:

我对这个用例有一个普遍的问题:我有一个班级A。这个类有一个非抽象方法doStuffCallback(),它可以被覆盖,但不是每个子类都需要。但是:我想确保如果方法被覆盖,子类方法必须调用父母方法。

例子:

abstract class A {
    private function doStuff() {
        $this->doStuffCallback();
    }

    protected function doStuffCallback() {
        // IMPORTANT CODE HERE
    }
}

class B extends A {
    protected function doStuffCallback() {
        parent::doStuffCallback(); // I want to enforce this because the parents method code is important

        // ALSO IMPORTANT CODE
    }
}

因为被覆盖的方法做同样的事情,所以为相同的职责定义两个方法和一个调用两者的私有帮助方法会非常难看。像这样:

abstract class A {
    private function doStuff() {
        $this->callDoStuffCallback();
    }

    private function callDoStuffCallback() {
        $this->internalDoStuffCallback();
        $this->doStuffCallback();

        // This is VERY ugly
    }

    private function internalDoStuffCallback() {
        // IMPORTANT CODE HERE
    }

    protected function doStuffCallback() {}
}

class B extends A {
    protected function doStuffCallback() {
        // IMPORTANT CODE
    }
}

这真是丑陋而费力。所以我的问题是:PHP 中有没有办法强制重写的方法调用父方法?

【问题讨论】:

    标签: php oop abstraction


    【解决方案1】:

    没有。 PHP 中没有这样的语言特性;这种限制在大多数子类型“OO”语言中是不可能的。

    相反,程序必须依赖明确的文档合同;并希望进行单元测试以确保一致性。


    还可以使用守卫,这样,在某个时候,当使用父类上的方法时,如果“当前状态”无效(例如,某某方法有还没有被调用)。这也可以通过使子类调用(如文档合同中定义的)某些特殊方法而不是简单地被覆盖的超级方法来变得更加明确。但是,这在任何类型系统之外。

    虽然可以使用self:: 范围(例如,调用非重写方法,而该方法调用重写方法),但这将涉及进一步的魔法(例如,某些堆栈状态)以避免无限递归循环;并且很容易不小心忽略使用。

    我的建议是调用一个(私有)方法,该方法调用这个“可能被覆盖”的方法,与任何适用的逻辑相关,如示例中所示(尽管希望有更多特定于任务的驯服)。然后(受保护的)覆盖方法不需要或不需要处理任何特殊逻辑本身;它也不意味着直接在父类建立的上下文之外调用——它就是它目前声称的,一个特殊的回调。

    【讨论】:

    • 感谢您的回答!我认为完全一样。我相信我们现在使用的 OOP 还不完整。还有很多重要但没有处理的方面。
    【解决方案2】:

    我倾向于不同意“这很丑”。这是处理此用例的标准方式,也是Template Method Pattern 的变体。

    现在我只是猜测,因为您没有提供真实示例,但如果您说这两种方法“做同样的事情”,那么您的设计可能有问题。如果他们做同样的事情,如果子类以不同的方式做同样的事情,为什么还要调用父实现呢?在我看来,该方法实际上做了不止一件事,您也许可以将其分解为可以单独覆盖的几个部分(或不覆盖,然后将它们设为私有或最终)。

    【讨论】:

    • 是的,“同一件事”在这里不是正确的术语。这些方法具有相同的职责,但子类方法对其进行了扩展以满足其自身的额外要求。
    【解决方案3】:

    我知道这是一个老话题,但我问自己同样的问题,我所做的是:

    abstract class A {
        private function doStuff() {
            $this->doStuffCallback();
        }
    
        final protected function doStuffCallback() {
            // IMPORTANT CODE HERE
    
            $this->callNewFunction();
        }
    
        abstract protected function callNewFunction();
    } 
    
    class B extends A {
        protected function callNewFunction() {
            // ALSO IMPORTANT CODE
        }
    }
    

    所以基本上我会将您希望为每个孩子强制执行代码的函数标记为“最终”,然后调用一个新的“抽象”函数来强制孩子实现它。如果您不想强制使用新的“抽象”功能,请不要将其抽象化。

    编辑:这基本上是@Fabian Schmengler 的回答,但你的例子更具体。

    【讨论】:

    • 是的,这种方法也称为Template method pattern。但如前所述,我不想强​​制所有派生类型来实现此方法。只有当他们想要覆盖特定方法时,我才想确保父母兄弟姐妹也被调用。
    【解决方案4】:

    不,你可以访问,你可以使用父类的方法,像这样

    <?php
    
    class A {
    
       function s1($p1) {
          echo 's1: '.$p1;
       }
    }
    
    
    class B extends A {
    
        public function callParent($method, $p1) {
            parent::$method($p1);
        }
    }
    
    
    $b = new B();
    
    $b->callParent('s1', 'param1');
    

    或替换扩展魔术方法__call等https://github.com/StagnantIce/php_extend_magic/blob/master/AExtendClass.php

    【讨论】:

    • 这如何解决我的问题?我认为这是一个不太聪明的解决方案,而不仅仅是期望子类使用文档进行正确的实现(覆盖时调用父方法)。我的意思是你的例子要求类的每个客户,而不仅仅是实现的类本身,都必须以反射方式调用父方法。这是我见过的最违反直觉和不安全的方法。没有冒犯:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-09
    • 1970-01-01
    • 2019-05-18
    • 2011-02-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多