PHP/OOP method overriding the DRY way

前端 未结 4 458
梦如初夏
梦如初夏 2021-01-14 10:15

I\'m curious if there is a \"better\" design for the following behavior:



        
相关标签:
4条回答
  • 2021-01-14 10:38

    I dont think this is better, but this is one possible way.

    class abstract Foo {
        public function foo() {
            // Foo-specific foo stuff.
            $this->_foo();
        }
    
        // Might be abstract, might be an empty implementation
        protected abstract function _foo();
    }
    
    class Bar extends Foo {
        protected function _foo() {
            // Bar-specific foo stuff.
        }
    }
    

    Personally, I prefer the way you have it because I think it is more readable. It also means the child does not have to have its own implementation of foo(). Seems more OOP. However, if you require each child class to have its own added implementation of foo() this may do the trick for you.

    0 讨论(0)
  • 2021-01-14 10:47

    I found a better general way avoiding closures and other ugly tricks.

    class A {
        /**************************************************************/
        // Chain caller helpers, defined in base class only 
        // (single point of maintenance)
    
        protected $_chain_params; 
    
        final public function chain_call($method_name, $params){
            $class = get_class($this);  // get last child classname
            $chain = array($class);
    
            while ($class !== 'A'){    // get all parents classname
                $class = get_parent_class($class);
                $chain[] = $class;
            }
    
                // Call reversed chain
            $this->_chain_params = $params;
            for ($k = count($chain) - 1; $k >= 0; $k--){
                $class = $chain[$k];
                $refl = new \ReflectionMethod($class, $method_name);
                if ($refl->class === $class)
                    $ret = call_user_func_array(array($this, 
                                                      $class.'::'.$method_name), 
                                                      $this->_chain_params);
            }
            return $ret;
        }
    
        final protected function chain_modify_params($params){
            $this->_chain_params = $params;
        }
        /*************************************************************/
    
        // Methods overrided by child classes:
        public function foo($a, $b){
            echo "A foo fired with params a=$a b=$b <br>";
        }
    
        protected function bar($a, &$b){
            echo "A bar fired with params a=$a b=$b <br>";
            return 1000;
        }
    }
    
     // Child classes extending base class. NOTE: no need to smell the code!
    
    class B extends A {
        public function foo($a, $b){
            echo "B foo fired with params a=$a b=$b <br>";
        }
    
        protected function bar($a, &$b){
            echo "B bar fired with params a=$a b=$b <br>";
            return 2000;
        }
    }
    
    class C extends B {
        public function foo($a, $b){
            echo "C foo fired with params a=$a b=$b <br>";
        }
    
        protected function bar($a, &$b){
            echo "C bar fired with params a=$a b=$b <br>";
    
            $a++;  // override param value
            $b++;  // override referenced param value
            echo " - C modify => a=$a b=$b <br>";
    
            // reflect changed parameters to the next child class in chain ;)
            $this->chain_modify_params(array($a, &$b));
    
            return 3000;
        }
    }
    
    class D extends C {
        public function foo($a, $b){
            echo "D foo fired with params a=$a b=$b <br>";
        }
    
        protected function bar($a, &$b){
            echo "D bar fired with params a=$a b=$b <br>";
            return 4000;
        }
    }
    
    $d = new D();
    
    echo 'Call "foo" directly... <br>';
    $d->foo(10, 20);
    
    echo '<br> Call "foo" in chain mode... <br>';
    $d->chain_call('foo', array(10, 20));
    
    echo '<br> More complex example: call "bar" in chain mode,'.
         'passing $k by reference, '.
         'and getting last method result... <br><br>';
    
    $k = 40;
    $ret = $d->chain_call('bar', array(30, &$k));
    
    echo "<br> D->bar() return: " . $ret;
    echo "<br>k = $k";
    

    Result:

    Call "foo" directly... 
    D foo fired with params a=10 b=20 
    
    Call "foo" in chain mode... 
    A foo fired with params a=10 b=20 
    B foo fired with params a=10 b=20 
    C foo fired with params a=10 b=20 
    D foo fired with params a=10 b=20 
    
    More complex example: call "bar" in chain mode, 
    passing $k by reference, and getting last method result... 
    
    A bar fired with params a=30 b=40 
    B bar fired with params a=30 b=40 
    C bar fired with params a=30 b=40 
     - C modify => a=31 b=41 
    D bar fired with params a=31 b=41 
    
    D->bar() return: 4000
    k = 41
    
    0 讨论(0)
  • 2021-01-14 10:47

    There's no way in PHP to enforce this specifically;

    But, if Foo::foo must always execute before any subclass::foo, and you don't care about the results; perhaps the actual contents of the methods are badly designed.

    If you always must initialize something, perhaps you can do it in the constructor, if you're logging every call, perhaps you need a decorator.

    Here's another option that may work:

    class Foo {
    
      function doFoo() {
    
        // the code that 'must always run' goes here
        ...
        ...
        ...
        // and now we're calling the 'overridden' method.
        foo();
    
      }
    
      protected function foo() {
         // move along, nothing to see here
      }
    
    
    }
    
    class Bar extends Foo {
    
      protected function foo() {
         // Bar-specific foo stuff. 
      }
    
    }
    
    class Baz extends Foo {
    
      protected function foo() {
         // Baz-specific foo stuff. 
      }
    
    }
    

    The flaw here is that there's no 'multiple inheritance' or chaining.

    But yea, maybe you actually need some kind of pub-sub pattern.. or who knows?

    You're asking how you can implement your solution to a design problem, you should specifically ask how to solve your design problem.

    0 讨论(0)
  • 2021-01-14 10:55

    As long as you're overwriting your methods in subclasses, there is no way in any language I know of to enforce the behavior of the parent's method. If you're writing code just for your app, you should be able to trust your own code to call parent::foo(). But if you're writing a library, framework or API that others will build on, there is value to your idea. Ruby on Rails makes good use of that kind of behavior using callbacks.

    Okay, so don't define any foo methods. Instead, use __call and an array of closures as callbacks. My PHP is really rusty, so I forget some of the specifics.

    class Foo {
      // I forget how to make a class variable in PHP, but this should be one.
      // You could define as many callback chains as you like.
      $callbacks = array('foo_callback_chain' => []);
    
      // This should be a class function. Again, forget how.
      function add_callback($name, $callback) {
        $callbacks[$name.'_callback_chain'][] = $callback;
      }
    
      // Add your first callback
      add_callback('foo', function() {
        // do foo stuff
      })
    
      def method__call($method, $args) {
        // Actually, you might want to call them in reverse order, as that would be more similar
        foreach ( $callbacks[$method_name.'_callback_chain'] as $method ) {
          $method();
        }
      }
    }
    

    Then in your child classes, just append more callbacks with ""add_callback". This isn't appropriate for everything, but it works very well in some cases. (More about closures at http://php.net/manual/en/functions.anonymous.php.)

    0 讨论(0)
提交回复
热议问题