Dangers of mutual dependencies
Much like most people, I try work out my class dependencies through a top-down ‘waterfall’-ish approach. By attempting this, I think allows me to keep the structure very clear and understandable.
excuse my non-existent UML skills
In this example the Ingredient class (and subclasses) is Never aware of any Recipe classes, but only the other way round.
I try to apply the same model to instantiated objects and packages (groups of classes). When an object encapsulates another object, I attempt to make sure the sub-object object is not aware of the parent. When I design packages, I attempt to make sure 2 packages don’t require ‘each other’.
An example where this could be a problem is the following. Say, I have a ‘Database’ package. I want to log every database error to a ‘Log’ package. The ‘Log’ package has a couple of implementations, such as ‘Log_File’, ‘Log_Syslog’, but now I added ‘Log_Database’ to log any problems to the database.
Strictly speaking these two packages can no longer be separated and will always have to be used/downloaded together. As a bonus a database-error could occur while logging, resulting in an endless loop (or segmentation fault if you’re using PHP).
Another example of a mutual dependency:
file1.php:
<?php
include 'file2.php';
?>
file2.php:
<?php
include 'file1.php';
?>
You get the idea ;)
However, these types of situations are sometimes simply unavoidable (that’s why we have include_once). When they are needed, they should be implemented with care and consideration. The problem with the Log class could be fixed if the Log_Database is aware of errors thrown by itself, and this could be repackaged by creating by separating the Log_Database in a new package, depending on both the ‘Connection’ and ‘Log’ classes.
Editors note: this post was part of a much larger article around designing plug-in systems, but I lost inspiration and decided to post just this part.
Comments
Giorgio Sironi •
IMHO to keep it simple the logger should not be aware of a connection of a connection interface: if connection is lost, an error is thrown and is logged with another query that will trigger another error etc...However, what about design the logger as a decorator?
class Log_Database implements Connection
{
public function __construct($conn) { ... }
public function __call($method, ...) {
try {
$this->conn->$method(...);
} catch (Exception $e) {
// write logs but assuring connection is open
}
}
}
This way the log package depends on the connection package, but not the other war. Sure it can be other solutions...