dongpanbo4727
2017-09-20 13:17
浏览 21
已采纳

PHP OOP类和函数

I am not sure how to name this, but here it goes. Lets suppose i have the following

class A {
    public function aa() {
       $this->bb();
    }
    public function bb() {

    }
}

class B extends a {

}

class C {
   __construct(B $service) {
       $this->service = $service;
   }
   public function aa() {

       $this->service->aa();
   }
}

My call in code will be

$C = new C(new B());
$C->aa();

So this will basically execute A:aa() which is what i want. As you can see, in A::aa() AA::bb() is called.

What I need. When AA::bb() is called i want to execute some code defined in class C, but I am not allowed to change the A class. I can only change the B class or the C class.

My idea was to add a listener in the B class and overwrite the bb() function like this

class B extends a {
    public $listener;
    bb() {
       parent::bb();
       $this->listener();
    }
}

class C {
   __construct(B $service) {
       $this->service = $service;
   }
   public function aa() {

       $this->service->listener = function() { }
       $this->service->aa();
   }
}

But I don't like this idea a lot, doesn't look like a good one. What are my options here?

Again, I CANNOT change the A class and i can only call the C class.

PHP version is 5.3

  • 写回答
  • 好问题 提建议
  • 关注问题
  • 收藏
  • 邀请回答

2条回答 默认 最新

  • dosi8657 2017-09-20 13:31
    已采纳

    You have two options. Extend or decorate.

    First one would be kinda what you have already written, though, I would not use public visibility for the listener:

    class Foo extends A {
        private $listener;
        public function setListener(callable $func) {
            $this->listener = $func;
        }
        public function bb() {
            call_user_func($this->listener);
            return parent:bb();
        }
    }
    

    In the example I passed the listener via setter injection, but you can also use constructor injection and pass the $listened in the overloaded __construct() method. When you extend a class, the "interface restriction" does not aply to the constructor's signature.


    The other approach is to use a decorator:

    class Foo {
        private $target;
        public function __construct(A $target) {
            $this->target = $target;
        }
    
        public function bb($callback) {
            $callback();
            return $this->target->bb();
        }
    
        public function __call($method, $arguments) {
            return call_user_func_array( 
                    array( $this->target, $method ),
                    $arguments
                );
        }
    }
    

    The second approach would let you alter the interface.

    Which option you pick depend on the exact functionality you actually need to implement. The decorator is a solution for, when you need drastic change in the objects behavior - for example, it is really good for adding access control.

    已采纳该答案
    评论
    解决 无用
    打赏 举报
  • dongpo5239 2017-09-20 13:52

    I understand that you want to execute code in C after code in A completes. You cannot change A.

    As written, C::aa calls A::aa, which calls A::bb and the stack unwinds. Why not just do the work in C::aa after the service call finishes?

    class C {
       public function aa() {
           $this->service->aa();
           // whatever you want to do
       }
    }
    

    If, on the other hand, you need to call code after A::aa is called but before A::bb is called then the example you posted would suffice with clarity:

    class B extends a {
        public $listener;
        public function bb() {
           call_user_func($this->listener);
           parent::bb();
        }
    }
    

    Note the use of call_user_func, which is necessary for PHP 5.3 to call an anonymous function stored in a member variable.

    评论
    解决 无用
    打赏 举报

相关推荐 更多相似问题