As discussed in the comments, I think the reason this pattern is not working for you is that you have a poorly designed object hierarchy. In the example, ChildClass
is a "type of" ParentClass
, but also internally references a copy of ParentClass
to do some delegated work.
From the comments, what you have must look something like this:
class BasicLogger {
protected $delegated_logger;
public function __construct() {
// initialise $this->delegated_logger somehow
}
public function logMessage($message, $flags) {
{
$prepared_message = $this->prepareMessage($message, $flags);
$this->deliverMessage($prepared_message);
}
private function prepareMessage($message, $flags) {
// implementation here
}
protected function deliverMessage($prepared_message) {
// implementation here
}
}
class MailLogger extends BasicLogger {
protected function deliverMessage($prepared_message) {
// different implementation here
if ( $mail_sending_failed ) {
$this->delegated_logger->logMessage('Oops, MailLogger failed...');
}
}
}
However, BasicLogger
is actually performing multiple roles in the object hierarchy:
- defining the interface that all loggers should adhere to (here represented as a single
logMessage
method)
- providing a shared implementation of
prepareMessage
that all loggers will use, and an implementation of logMessage
that depends on it plus a deliverMessage
function
- providing a specific implementation of
deliverMessage
that will be completely over-written by child classes
- providing a mechanism for complex implementations to delegate to simpler implementations, without a way of distinguishing between the two
The first three roles should be separated into an interface
, an abstract
base class, and a simple implementation:
interface Logger {
public function logMessage($message, $flags = null);
}
abstract class BaseLogger implements Logger {
public function logMessage($message, $flags = null) {
{
$prepared_message = $this->prepareMessage($message, $flags);
$this->deliverMessage($prepared_message);
}
private function prepareMessage($message, $flags) {
// implementation here
}
abstract protected function deliverMessage($prepared_message);
}
class BasicTextLogger extends BaseLogger {
protected function deliverMessage($prepared_message) {
// implementation here
}
}
You can then use instances of BasicTextLogger
wherever you need, including in other implementations of BaseLogger
.
You might want to put the logic of having a delegated logger (the 4th role of my BasicLogger
above) into another class for reuse. BasicTextLogger
shouldn't inherit this behaviour, or you'll end up needing to provide a logger to a logger to a logger to a logger, ad infinitum.
abstract class ComplexLogger extends BaseLogger {
protected $delegated_logger;
public function __construct( Logger $delegated_logger ) {
if ( $delegated_logger instanceOf ComplexLogger ) {
throw new Exception('Attempted to delegate one complex logger to another; danger of recursion, so disallowed.');
} else {
$this->delegated_logger = $delegated_logger;
}
}
}
class MailLogger extends ComplexLogger {
protected function deliverMessage($prepared_message) {
// different implementation here
if ( $mail_sending_failed ) {
$this->delegated_logger->logMessage('Oops, MailLogger failed...');
}
}
}
This then allows you to perform Dependency Injection to provide your complex logger with a simple logger to delegate to:
$my_logger = new MailLogger( new BasicTextLogger() );
$my_logger->logMessage('Hello World!');
This may seem like a lot of different class
es and interface
s, but each now has a clear responsibility. You could put the whole $delegated_logger
logic into MailLogger
, but you'd have to copy and paste it if you had another complex logger later. You might also be able to ignore the Logger
interface, and just type-hint for classes deriving from the BaseLogger
class, but it's possible you'll want an implementation that doesn't use prepareMessage
at all - for instance, a DoNothingLogger
.